diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..73d0f2945 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Default behavior +* text=auto + +# Enforce LF line endings on source files +*.h text eol=lf +*.cpp text eol=lf diff --git a/debian/gitfromlog.sh b/debian/gitfromlog.sh index 726fd4e95..92edf7e5d 100644 --- a/debian/gitfromlog.sh +++ b/debian/gitfromlog.sh @@ -1,3 +1,3 @@ -# A simple script to extract the Git hash from an auto-generated debian/changelog - +# A simple script to extract the Git hash from an auto-generated debian/changelog + grep -Po '(?<=-)(([a-z0-9])\w+)(?=\+)' -m 1 $1 \ No newline at end of file diff --git a/decoders/audio/ffmpegaudiodecoder.cpp b/decoders/audio/ffmpegaudiodecoder.cpp index 8211826e5..8f0fd8d3b 100644 --- a/decoders/audio/ffmpegaudiodecoder.cpp +++ b/decoders/audio/ffmpegaudiodecoder.cpp @@ -1,6 +1,6 @@ -#include "ffmpegaudiodecoder.h" - -FFmpegAudioDecoder::FFmpegAudioDecoder() -{ - -} +#include "ffmpegaudiodecoder.h" + +FFmpegAudioDecoder::FFmpegAudioDecoder() +{ + +} diff --git a/decoders/audio/ffmpegaudiodecoder.h b/decoders/audio/ffmpegaudiodecoder.h index 8cac0a930..d07945684 100644 --- a/decoders/audio/ffmpegaudiodecoder.h +++ b/decoders/audio/ffmpegaudiodecoder.h @@ -1,17 +1,17 @@ -#ifndef FFMPEGAUDIODECODER_H -#define FFMPEGAUDIODECODER_H - -#include "decoders/ffmpegdecoder.h" - -/** - * @brief The FFmpegAudioDecoder class - * - * The role of an audio decoder is to simply convert audio to - */ -class FFmpegAudioDecoder : public FFmpegDecoder -{ -public: - FFmpegAudioDecoder(); -}; - -#endif // FFMPEGAUDIODECODER_H +#ifndef FFMPEGAUDIODECODER_H +#define FFMPEGAUDIODECODER_H + +#include "decoders/ffmpegdecoder.h" + +/** + * @brief The FFmpegAudioDecoder class + * + * The role of an audio decoder is to simply convert audio to + */ +class FFmpegAudioDecoder : public FFmpegDecoder +{ +public: + FFmpegAudioDecoder(); +}; + +#endif // FFMPEGAUDIODECODER_H diff --git a/decoders/frame.cpp b/decoders/frame.cpp index dec60f5b9..6c470d695 100644 --- a/decoders/frame.cpp +++ b/decoders/frame.cpp @@ -1,89 +1,89 @@ -#include "frame.h" - -Frame::Frame() : - frame_(nullptr) -{ -} - -Frame::Frame(AVFrame *f) : - frame_(f) -{ -} - -Frame::Frame(const Frame &f) : - frame_(nullptr) -{ -} - -Frame::Frame(Frame &&f) : - frame_(f.frame_) -{ - f.frame_ = nullptr; -} - -Frame &Frame::operator=(const Frame &f) -{ - frame_ = nullptr; - return *this; -} - -Frame &Frame::operator=(Frame &&f) -{ - if (&f != this) { - frame_ = f.frame_; - f.frame_ = nullptr; - } - - return *this; -} - -Frame::~Frame() -{ - FreeChild(); -} - -void Frame::SetAVFrame(AVFrame *f, AVRational timebase) -{ - FreeChild(); - - f = frame_; - timestamp_ = rational(timebase.num*f->pts, timebase.den); -} - -const int &Frame::width() -{ - return frame_->width; -} - -const int &Frame::height() -{ - return frame_->height; -} - -const rational &Frame::timestamp() -{ - return timestamp_; -} - -const int &Frame::format() -{ - return frame_->format; -} - -uint8_t **Frame::data() -{ - return frame_->data; -} - -int *Frame::linesize() -{ - return frame_->linesize; -} - -void Frame::FreeChild() -{ - if (frame_ != nullptr) { - av_frame_free(&frame_); - frame_ = nullptr; - } -} +#include "frame.h" + +Frame::Frame() : + frame_(nullptr) +{ +} + +Frame::Frame(AVFrame *f) : + frame_(f) +{ +} + +Frame::Frame(const Frame &f) : + frame_(nullptr) +{ +} + +Frame::Frame(Frame &&f) : + frame_(f.frame_) +{ + f.frame_ = nullptr; +} + +Frame &Frame::operator=(const Frame &f) +{ + frame_ = nullptr; + return *this; +} + +Frame &Frame::operator=(Frame &&f) +{ + if (&f != this) { + frame_ = f.frame_; + f.frame_ = nullptr; + } + + return *this; +} + +Frame::~Frame() +{ + FreeChild(); +} + +void Frame::SetAVFrame(AVFrame *f, AVRational timebase) +{ + FreeChild(); + + f = frame_; + timestamp_ = rational(timebase.num*f->pts, timebase.den); +} + +const int &Frame::width() +{ + return frame_->width; +} + +const int &Frame::height() +{ + return frame_->height; +} + +const rational &Frame::timestamp() +{ + return timestamp_; +} + +const int &Frame::format() +{ + return frame_->format; +} + +uint8_t **Frame::data() +{ + return frame_->data; +} + +int *Frame::linesize() +{ + return frame_->linesize; +} + +void Frame::FreeChild() +{ + if (frame_ != nullptr) { + av_frame_free(&frame_); + frame_ = nullptr; + } +} diff --git a/decoders/frame.h b/decoders/frame.h index f2bb98160..0a21d436e 100644 --- a/decoders/frame.h +++ b/decoders/frame.h @@ -1,96 +1,96 @@ -#ifndef FRAME_H -#define FRAME_H - -#include - -#include "global/rational.h" - -/** - * @brief The Frame class - * - * Abstraction from AVFrame. Currently a simple AVFrame wrapper. - * - * This class does not support copying at this time. - */ -class Frame -{ -public: - // Normal constructor - Frame(); - - // AVFrame constructor - Frame(AVFrame* f); - - // Copy constructor - Frame(const Frame& f); - - // Move constructor - Frame(Frame&& f); - - // Copy assignment operator - Frame& operator=(const Frame& f); - - // Move assignment operator - Frame& operator=(Frame&& f); - - // Destructor - ~Frame(); - - /** - * @brief Set frame child - * - * This class currently primarily functions as a wrapper for AVFrame for use outside of the Decoder classes. - * The internal AVFrame is set here. This class will also take ownership of the AVFrame and automatically - * clear it when deconstructed. - * - * @param f - */ - void SetAVFrame(AVFrame* f, AVRational timebase); - - /** - * @brief Get frame's width in pixels - */ - const int& width(); - - /** - * @brief Get frame's height in pixels - */ - const int& height(); - - /** - * @brief Get frame's timestamp. - * - * This timestamp is always a rational that will equate to the time in seconds. - */ - const rational& timestamp(); - - /** - * @brief Get frame's format - * - * @return - * - * Currently this will either be an AVPixelFormat (video) or an AVSampleFormat (audio). - */ - const int& format(); - - /** - * @brief Get the data buffer of this frame - */ - uint8_t** data(); - - /** - * @brief Get the linesize information for this frame - */ - int* linesize(); - -private: - - void FreeChild(); - - AVFrame* frame_; - rational timestamp_; -}; - -using FramePtr = std::shared_ptr; - -#endif // FRAME_H +#ifndef FRAME_H +#define FRAME_H + +#include + +#include "global/rational.h" + +/** + * @brief The Frame class + * + * Abstraction from AVFrame. Currently a simple AVFrame wrapper. + * + * This class does not support copying at this time. + */ +class Frame +{ +public: + // Normal constructor + Frame(); + + // AVFrame constructor + Frame(AVFrame* f); + + // Copy constructor + Frame(const Frame& f); + + // Move constructor + Frame(Frame&& f); + + // Copy assignment operator + Frame& operator=(const Frame& f); + + // Move assignment operator + Frame& operator=(Frame&& f); + + // Destructor + ~Frame(); + + /** + * @brief Set frame child + * + * This class currently primarily functions as a wrapper for AVFrame for use outside of the Decoder classes. + * The internal AVFrame is set here. This class will also take ownership of the AVFrame and automatically + * clear it when deconstructed. + * + * @param f + */ + void SetAVFrame(AVFrame* f, AVRational timebase); + + /** + * @brief Get frame's width in pixels + */ + const int& width(); + + /** + * @brief Get frame's height in pixels + */ + const int& height(); + + /** + * @brief Get frame's timestamp. + * + * This timestamp is always a rational that will equate to the time in seconds. + */ + const rational& timestamp(); + + /** + * @brief Get frame's format + * + * @return + * + * Currently this will either be an AVPixelFormat (video) or an AVSampleFormat (audio). + */ + const int& format(); + + /** + * @brief Get the data buffer of this frame + */ + uint8_t** data(); + + /** + * @brief Get the linesize information for this frame + */ + int* linesize(); + +private: + + void FreeChild(); + + AVFrame* frame_; + rational timestamp_; +}; + +using FramePtr = std::shared_ptr; + +#endif // FRAME_H diff --git a/dialogs/aboutdialog.cpp b/dialogs/aboutdialog.cpp index c116bfe83..9501e1483 100644 --- a/dialogs/aboutdialog.cpp +++ b/dialogs/aboutdialog.cpp @@ -1,66 +1,66 @@ -/*** - - 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 "aboutdialog.h" - -#include -#include -#include - -#include "global/global.h" - -AboutDialog::AboutDialog(QWidget *parent) : - QDialog(parent) -{ - setWindowTitle("About Olive"); - - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setSpacing(20); - - // Construct About text - QLabel* label = - new QLabel(QString("" - "

" - "

" - "" - "https://www.olivevideoeditor.org/" - "

" - "

%1

" // AppName (version identifier) - "

%2

" // First statement - "

%3

" // Second statement - "").arg(olive::AppName, - tr("Olive is a non-linear video editor. This software is free and " - "protected by the GNU GPL."), - tr("Olive Team is obliged to inform users that Olive source code is " - "available for download from its website.")), this); - - // Set text formatting - label->setAlignment(Qt::AlignCenter); - label->setTextInteractionFlags(Qt::TextSelectableByMouse); - label->setCursor(Qt::IBeamCursor); - label->setWordWrap(true); - layout->addWidget(label); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this); - buttons->setCenterButtons(true); - layout->addWidget(buttons); - - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); -} +/*** + + 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 "aboutdialog.h" + +#include +#include +#include + +#include "global/global.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent) +{ + setWindowTitle("About Olive"); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(20); + + // Construct About text + QLabel* label = + new QLabel(QString("" + "

" + "

" + "" + "https://www.olivevideoeditor.org/" + "

" + "

%1

" // AppName (version identifier) + "

%2

" // First statement + "

%3

" // Second statement + "").arg(olive::AppName, + tr("Olive is a non-linear video editor. This software is free and " + "protected by the GNU GPL."), + tr("Olive Team is obliged to inform users that Olive source code is " + "available for download from its website.")), this); + + // Set text formatting + label->setAlignment(Qt::AlignCenter); + label->setTextInteractionFlags(Qt::TextSelectableByMouse); + label->setCursor(Qt::IBeamCursor); + label->setWordWrap(true); + layout->addWidget(label); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this); + buttons->setCenterButtons(true); + layout->addWidget(buttons); + + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); +} diff --git a/dialogs/aboutdialog.h b/dialogs/aboutdialog.h index d50a1b751..ae7e22b7c 100644 --- a/dialogs/aboutdialog.h +++ b/dialogs/aboutdialog.h @@ -1,48 +1,48 @@ -/*** - - 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 ABOUTDIALOG_H -#define ABOUTDIALOG_H - -#include - -/** - * @brief The AboutDialog class - * - * The About dialog (accessible through Help > About). Contains license and version information. This can be run from - * anywhere - */ -class AboutDialog : public QDialog -{ - Q_OBJECT -public: - /** - * @brief AboutDialog Constructor - * - * Creates About dialog. - * - * @param parent - * - * QWidget parent object. Usually this will be MainWindow. - */ - explicit AboutDialog(QWidget *parent = nullptr); -}; - -#endif // ABOUTDIALOG_H +/*** + + 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 ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +/** + * @brief The AboutDialog class + * + * The About dialog (accessible through Help > About). Contains license and version information. This can be run from + * anywhere + */ +class AboutDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @brief AboutDialog Constructor + * + * Creates About dialog. + * + * @param parent + * + * QWidget parent object. Usually this will be MainWindow. + */ + explicit AboutDialog(QWidget *parent = nullptr); +}; + +#endif // ABOUTDIALOG_H diff --git a/dialogs/actionsearch.cpp b/dialogs/actionsearch.cpp index 6803aea81..75515278f 100644 --- a/dialogs/actionsearch.cpp +++ b/dialogs/actionsearch.cpp @@ -1,244 +1,244 @@ -/*** - - 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 "actionsearch.h" - -#include -#include -#include -#include - -#include "ui/mainwindow.h" - -ActionSearch::ActionSearch(QWidget *parent) : - QDialog(parent) -{ - // ActionSearch requires a parent widget - Q_ASSERT(parent != nullptr); - - // Set styling (object name is required for CSS specific to this object) - setObjectName("ASDiag"); - setStyleSheet("#ASDiag{border: 2px solid #808080;}"); - - // Size proportionally to the parent (usually MainWindow). - resize(parent->width()/3, parent->height()/3); - - // Show dialog as a "popup", which will make the dialog close if the user clicks out of it. - setWindowFlags(Qt::Popup); - - QVBoxLayout* layout = new QVBoxLayout(this); - - // Construct the main entry text field. - ActionSearchEntry* entry_field = new ActionSearchEntry(this); - - // Set the main entry field font size to 1.2x its standard font size. - QFont entry_field_font = entry_field->font(); - entry_field_font.setPointSize(qRound(entry_field_font.pointSize()*1.2)); - entry_field->setFont(entry_field_font); - - // Set placeholder text for the main entry field - entry_field->setPlaceholderText(tr("Search for action...")); - - // Connect signals/slots - connect(entry_field, SIGNAL(textChanged(const QString&)), this, SLOT(search_update(const QString &))); - connect(entry_field, SIGNAL(returnPressed()), this, SLOT(perform_action())); - - // moveSelectionUp() and moveSelectionDown() are emitted when the user pressed up or down on the text field. - // We override it here to select the upper or lower item in the list. - connect(entry_field, SIGNAL(moveSelectionUp()), this, SLOT(move_selection_up())); - connect(entry_field, SIGNAL(moveSelectionDown()), this, SLOT(move_selection_down())); - layout->addWidget(entry_field); - - // Construct list of actions - list_widget = new ActionSearchList(this); - - // Set list's font to 1.2x its standard font size - QFont list_widget_font = list_widget->font(); - list_widget_font.setPointSize(qRound(list_widget_font.pointSize()*1.2)); - list_widget->setFont(list_widget_font); - - layout->addWidget(list_widget); - - connect(list_widget, SIGNAL(dbl_click()), this, SLOT(perform_action())); - - // Instantly focus on the entry field to allow for fully keyboard operation (if this popup was initiated by keyboard - // shortcut for example). - entry_field->setFocus(); -} - -void ActionSearch::search_update(const QString &s, const QString &p, QMenu *parent) { - - // This function is recursive, using the `parent` parameter to loop through a menu's items. It functions in two - // modes - the parent being NULL, meaning it'll get MainWindow's menubar and loop over its menus, and the parent - // referring to a menu at which point it'll loop over its actions (and call itself recursively if it finds any - // submenus). - - if (parent == nullptr) { - - // If parent is NULL, we'll pull from the MainWindow's menubar and call this recursively on all of its submenus - // (and their submenus). - - // We'll clear all the current items in the list since if we're here, we're just starting. - list_widget->clear(); - - QList menus = olive::MainWindow->menuBar()->actions(); - - // Loop through all menus from the menubar and run this function on each one. - for (int i=0;imenu(); - - search_update(s, p, menu); - } - - // Once we're here, all the recursion/item retrieval is complete. We auto-select the first item for better - // keyboard-exclusive functionality. - if (list_widget->count() > 0) { - list_widget->item(0)->setSelected(true); - } - - } else { - - // Parent was not NULL, so we loop over the actions in the menu we were given in `parent`. - - // The list shows a '>' delimited hierarchy of the menus in which this action came from. We construct it here by - // adding the current menu's text to the existing hierarchy (passed in `p`). - QString menu_text; - if (!p.isEmpty()) menu_text += p + " > "; - menu_text += parent->title().replace("&", ""); // Strip out any &s used in menu action names - - // Loop over the menu's actions - QList actions = parent->actions(); - for (int i=0;iisSeparator()) { - - if (a->menu() != nullptr) { - - // If the action is a menu, run this function recursively on it - search_update(s, menu_text, a->menu()); - - } else { - - // This is a valid non-separator non-menu action, so check it against the currently entered string. - - // Strip out all &s from the action's name - QString comp = a->text().replace("&", ""); - - // See if the action's name contains any of the currently entered string - if (comp.contains(s, Qt::CaseInsensitive)) { - - // If so, we add it to the list widget. - QListWidgetItem* item = new QListWidgetItem(QString("%1\n(%2)").arg(comp, menu_text), list_widget); - - // Add a pointer to the original QAction in the item's data - item->setData(Qt::UserRole+1, reinterpret_cast(a)); - - list_widget->addItem(item); - - } - - } - } - } - } -} - -void ActionSearch::perform_action() { - - // Loop over all the items in the list and if we find one that's selected, we trigger it. - QList selected_items = list_widget->selectedItems(); - if (list_widget->count() > 0 && selected_items.size() > 0) { - - QListWidgetItem* item = selected_items.at(0); - - // Get QAction pointer from item's data - QAction* a = reinterpret_cast(item->data(Qt::UserRole+1).value()); - - a->trigger(); - - } - - // Close this popup - accept(); - -} - -void ActionSearch::move_selection_up() { - - // Here we loop over all the items to find the currently selected one, and then select the one above it. We start - // iterating at 1 (instead of 0) to efficiently ignore the first item (since the selection can't go below the very - // bottom item). - - int lim = list_widget->count(); - for (int i=1;iitem(i)->isSelected()) { - list_widget->item(i-1)->setSelected(true); - list_widget->scrollToItem(list_widget->item(i-1)); - break; - } - } -} - -void ActionSearch::move_selection_down() { - - // Here we loop over all the items to find the currently selected one, and then select the one below it. We limit it - // one entry before count() to efficiently ignore the item at the end (since the selection can't go below the very - // bottom item). - - int lim = list_widget->count()-1; - for (int i=0;iitem(i)->isSelected()) { - list_widget->item(i+1)->setSelected(true); - list_widget->scrollToItem(list_widget->item(i+1)); - break; - } - } -} - -ActionSearchEntry::ActionSearchEntry(QWidget *parent) : QLineEdit(parent) {} - -void ActionSearchEntry::keyPressEvent(QKeyEvent * event) { - - // Listen for up/down, otherwise pass the key event to the base class. - - switch (event->key()) { - case Qt::Key_Up: - emit moveSelectionUp(); - break; - case Qt::Key_Down: - emit moveSelectionDown(); - break; - default: - QLineEdit::keyPressEvent(event); - } - -} - -ActionSearchList::ActionSearchList(QWidget *parent) : QListWidget(parent) {} - -void ActionSearchList::mouseDoubleClickEvent(QMouseEvent *) { - - // Indiscriminately emit a signal on any double click - emit dbl_click(); - -} +/*** + + 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 "actionsearch.h" + +#include +#include +#include +#include + +#include "ui/mainwindow.h" + +ActionSearch::ActionSearch(QWidget *parent) : + QDialog(parent) +{ + // ActionSearch requires a parent widget + Q_ASSERT(parent != nullptr); + + // Set styling (object name is required for CSS specific to this object) + setObjectName("ASDiag"); + setStyleSheet("#ASDiag{border: 2px solid #808080;}"); + + // Size proportionally to the parent (usually MainWindow). + resize(parent->width()/3, parent->height()/3); + + // Show dialog as a "popup", which will make the dialog close if the user clicks out of it. + setWindowFlags(Qt::Popup); + + QVBoxLayout* layout = new QVBoxLayout(this); + + // Construct the main entry text field. + ActionSearchEntry* entry_field = new ActionSearchEntry(this); + + // Set the main entry field font size to 1.2x its standard font size. + QFont entry_field_font = entry_field->font(); + entry_field_font.setPointSize(qRound(entry_field_font.pointSize()*1.2)); + entry_field->setFont(entry_field_font); + + // Set placeholder text for the main entry field + entry_field->setPlaceholderText(tr("Search for action...")); + + // Connect signals/slots + connect(entry_field, SIGNAL(textChanged(const QString&)), this, SLOT(search_update(const QString &))); + connect(entry_field, SIGNAL(returnPressed()), this, SLOT(perform_action())); + + // moveSelectionUp() and moveSelectionDown() are emitted when the user pressed up or down on the text field. + // We override it here to select the upper or lower item in the list. + connect(entry_field, SIGNAL(moveSelectionUp()), this, SLOT(move_selection_up())); + connect(entry_field, SIGNAL(moveSelectionDown()), this, SLOT(move_selection_down())); + layout->addWidget(entry_field); + + // Construct list of actions + list_widget = new ActionSearchList(this); + + // Set list's font to 1.2x its standard font size + QFont list_widget_font = list_widget->font(); + list_widget_font.setPointSize(qRound(list_widget_font.pointSize()*1.2)); + list_widget->setFont(list_widget_font); + + layout->addWidget(list_widget); + + connect(list_widget, SIGNAL(dbl_click()), this, SLOT(perform_action())); + + // Instantly focus on the entry field to allow for fully keyboard operation (if this popup was initiated by keyboard + // shortcut for example). + entry_field->setFocus(); +} + +void ActionSearch::search_update(const QString &s, const QString &p, QMenu *parent) { + + // This function is recursive, using the `parent` parameter to loop through a menu's items. It functions in two + // modes - the parent being NULL, meaning it'll get MainWindow's menubar and loop over its menus, and the parent + // referring to a menu at which point it'll loop over its actions (and call itself recursively if it finds any + // submenus). + + if (parent == nullptr) { + + // If parent is NULL, we'll pull from the MainWindow's menubar and call this recursively on all of its submenus + // (and their submenus). + + // We'll clear all the current items in the list since if we're here, we're just starting. + list_widget->clear(); + + QList menus = olive::MainWindow->menuBar()->actions(); + + // Loop through all menus from the menubar and run this function on each one. + for (int i=0;imenu(); + + search_update(s, p, menu); + } + + // Once we're here, all the recursion/item retrieval is complete. We auto-select the first item for better + // keyboard-exclusive functionality. + if (list_widget->count() > 0) { + list_widget->item(0)->setSelected(true); + } + + } else { + + // Parent was not NULL, so we loop over the actions in the menu we were given in `parent`. + + // The list shows a '>' delimited hierarchy of the menus in which this action came from. We construct it here by + // adding the current menu's text to the existing hierarchy (passed in `p`). + QString menu_text; + if (!p.isEmpty()) menu_text += p + " > "; + menu_text += parent->title().replace("&", ""); // Strip out any &s used in menu action names + + // Loop over the menu's actions + QList actions = parent->actions(); + for (int i=0;iisSeparator()) { + + if (a->menu() != nullptr) { + + // If the action is a menu, run this function recursively on it + search_update(s, menu_text, a->menu()); + + } else { + + // This is a valid non-separator non-menu action, so check it against the currently entered string. + + // Strip out all &s from the action's name + QString comp = a->text().replace("&", ""); + + // See if the action's name contains any of the currently entered string + if (comp.contains(s, Qt::CaseInsensitive)) { + + // If so, we add it to the list widget. + QListWidgetItem* item = new QListWidgetItem(QString("%1\n(%2)").arg(comp, menu_text), list_widget); + + // Add a pointer to the original QAction in the item's data + item->setData(Qt::UserRole+1, reinterpret_cast(a)); + + list_widget->addItem(item); + + } + + } + } + } + } +} + +void ActionSearch::perform_action() { + + // Loop over all the items in the list and if we find one that's selected, we trigger it. + QList selected_items = list_widget->selectedItems(); + if (list_widget->count() > 0 && selected_items.size() > 0) { + + QListWidgetItem* item = selected_items.at(0); + + // Get QAction pointer from item's data + QAction* a = reinterpret_cast(item->data(Qt::UserRole+1).value()); + + a->trigger(); + + } + + // Close this popup + accept(); + +} + +void ActionSearch::move_selection_up() { + + // Here we loop over all the items to find the currently selected one, and then select the one above it. We start + // iterating at 1 (instead of 0) to efficiently ignore the first item (since the selection can't go below the very + // bottom item). + + int lim = list_widget->count(); + for (int i=1;iitem(i)->isSelected()) { + list_widget->item(i-1)->setSelected(true); + list_widget->scrollToItem(list_widget->item(i-1)); + break; + } + } +} + +void ActionSearch::move_selection_down() { + + // Here we loop over all the items to find the currently selected one, and then select the one below it. We limit it + // one entry before count() to efficiently ignore the item at the end (since the selection can't go below the very + // bottom item). + + int lim = list_widget->count()-1; + for (int i=0;iitem(i)->isSelected()) { + list_widget->item(i+1)->setSelected(true); + list_widget->scrollToItem(list_widget->item(i+1)); + break; + } + } +} + +ActionSearchEntry::ActionSearchEntry(QWidget *parent) : QLineEdit(parent) {} + +void ActionSearchEntry::keyPressEvent(QKeyEvent * event) { + + // Listen for up/down, otherwise pass the key event to the base class. + + switch (event->key()) { + case Qt::Key_Up: + emit moveSelectionUp(); + break; + case Qt::Key_Down: + emit moveSelectionDown(); + break; + default: + QLineEdit::keyPressEvent(event); + } + +} + +ActionSearchList::ActionSearchList(QWidget *parent) : QListWidget(parent) {} + +void ActionSearchList::mouseDoubleClickEvent(QMouseEvent *) { + + // Indiscriminately emit a signal on any double click + emit dbl_click(); + +} diff --git a/dialogs/actionsearch.h b/dialogs/actionsearch.h index af82320ad..bf3457432 100644 --- a/dialogs/actionsearch.h +++ b/dialogs/actionsearch.h @@ -1,170 +1,170 @@ -/*** - - 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 ACTIONSEARCH_H -#define ACTIONSEARCH_H - -#include -#include -#include -#include - -class ActionSearchList; - -/** - * @brief The ActionSearch class - * - * A popup window (accessible through Help > Action Search) that allows users to search for a menu command by typing - * rather than browsing through the menu bar. This can be created from anywhere provided olive::MainWindow is valid. - */ -class ActionSearch : public QDialog -{ - Q_OBJECT -public: - /** - * @brief ActionSearch Constructor - * - * Create ActionSearch popup. - * - * @param parent - * - * QWidget parent. Usually MainWindow. - */ - ActionSearch(QWidget* parent); -private slots: - /** - * @brief Update the list of actions according to a search query - * - * This function adds/removes actions in the action list according to a given search query entered by the user. - * - * To loop over the menubar and all of its menus and submenus, this function will call itself recursively. As such - * some of its parameters do not need to be set externally, as these will be set by the function itself as it calls - * itself. - * - * @param s - * - * The search text. This is the only parameter that should be set externally. - * - * @param p - * - * The current parent hierarchy. In most cases, this should be left as nullptr when called externally. - * search_update() will fill this automatically as it needs while calling itself recursively. - * - * @param parent - * - * The current menu to loop over. In most cases, this should be left as nullptr when called externally. - * search_update() will fill this automatically as it needs while calling itself recursively. - */ - void search_update(const QString& s, const QString &p = nullptr, QMenu *parent = nullptr); - - /** - * @brief Perform the currently selected action - * - * Usually triggered by pressing Enter on the ActionSearchEntry field, this will trigger whatever action is currently - * highlighted and then close this popup. If no entries are highlighted (i.e. the list is empty), no action is - * triggered and the popup closes anyway. - */ - void perform_action(); - - /** - * @brief Move selection up - * - * A slot for pressing up on the ActionSearchEntry field. Moves the selection in the list up once. If the - * selection is already at the top of the list, this is a no-op. - */ - void move_selection_up(); - - /** - * @brief Move selection down - * - * A slot for pressing down on the ActionSearchEntry field. Moves the selection in the list down once. If the - * selection is already at the bottom of the list, this is a no-op. - */ - void move_selection_down(); -private: - /** - * @brief Main widget that shows the list of commands - */ - ActionSearchList* list_widget; -}; - -/** - * @brief The ActionSearchList class - * - * Simple wrapper around QListWidget that emits a signal when an item is double clicked that ActionSearch connects - * to a slot that triggers the currently selected action. - */ -class ActionSearchList : public QListWidget { - Q_OBJECT -public: - /** - * @brief ActionSearchList Constructor - * @param parent - * - * Usually ActionSearch. - */ - ActionSearchList(QWidget* parent); -protected: - /** - * @brief Override of QListWidget's double click event that emits a signal. - */ - void mouseDoubleClickEvent(QMouseEvent *); -signals: - /** - * @brief Signal emitted when a QListWidget item is double clicked. - */ - void dbl_click(); -}; - -/** - * @brief The ActionSearchEntry class - * - * Simple wrapper around QLineEdit that emits signals when the up or down arrow keys are pressed so that ActionSearch - * can connect them to moving the current selection up or down. - */ -class ActionSearchEntry : public QLineEdit { - Q_OBJECT -public: - /** - * @brief ActionSearchEntry - * @param parent - * - * Usually ActionSearch. - */ - ActionSearchEntry(QWidget* parent); -protected: - /** - * @brief Override of QLineEdit's key press event that listens for up/down key presses. - * @param event - */ - void keyPressEvent(QKeyEvent * event); -signals: - /** - * @brief Emitted when the user presses the up arrow key. - */ - void moveSelectionUp(); - - /** - * @brief Emitted when the user presses the down arrow key. - */ - void moveSelectionDown(); -}; - -#endif // ACTIONSEARCH_H +/*** + + 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 ACTIONSEARCH_H +#define ACTIONSEARCH_H + +#include +#include +#include +#include + +class ActionSearchList; + +/** + * @brief The ActionSearch class + * + * A popup window (accessible through Help > Action Search) that allows users to search for a menu command by typing + * rather than browsing through the menu bar. This can be created from anywhere provided olive::MainWindow is valid. + */ +class ActionSearch : public QDialog +{ + Q_OBJECT +public: + /** + * @brief ActionSearch Constructor + * + * Create ActionSearch popup. + * + * @param parent + * + * QWidget parent. Usually MainWindow. + */ + ActionSearch(QWidget* parent); +private slots: + /** + * @brief Update the list of actions according to a search query + * + * This function adds/removes actions in the action list according to a given search query entered by the user. + * + * To loop over the menubar and all of its menus and submenus, this function will call itself recursively. As such + * some of its parameters do not need to be set externally, as these will be set by the function itself as it calls + * itself. + * + * @param s + * + * The search text. This is the only parameter that should be set externally. + * + * @param p + * + * The current parent hierarchy. In most cases, this should be left as nullptr when called externally. + * search_update() will fill this automatically as it needs while calling itself recursively. + * + * @param parent + * + * The current menu to loop over. In most cases, this should be left as nullptr when called externally. + * search_update() will fill this automatically as it needs while calling itself recursively. + */ + void search_update(const QString& s, const QString &p = nullptr, QMenu *parent = nullptr); + + /** + * @brief Perform the currently selected action + * + * Usually triggered by pressing Enter on the ActionSearchEntry field, this will trigger whatever action is currently + * highlighted and then close this popup. If no entries are highlighted (i.e. the list is empty), no action is + * triggered and the popup closes anyway. + */ + void perform_action(); + + /** + * @brief Move selection up + * + * A slot for pressing up on the ActionSearchEntry field. Moves the selection in the list up once. If the + * selection is already at the top of the list, this is a no-op. + */ + void move_selection_up(); + + /** + * @brief Move selection down + * + * A slot for pressing down on the ActionSearchEntry field. Moves the selection in the list down once. If the + * selection is already at the bottom of the list, this is a no-op. + */ + void move_selection_down(); +private: + /** + * @brief Main widget that shows the list of commands + */ + ActionSearchList* list_widget; +}; + +/** + * @brief The ActionSearchList class + * + * Simple wrapper around QListWidget that emits a signal when an item is double clicked that ActionSearch connects + * to a slot that triggers the currently selected action. + */ +class ActionSearchList : public QListWidget { + Q_OBJECT +public: + /** + * @brief ActionSearchList Constructor + * @param parent + * + * Usually ActionSearch. + */ + ActionSearchList(QWidget* parent); +protected: + /** + * @brief Override of QListWidget's double click event that emits a signal. + */ + void mouseDoubleClickEvent(QMouseEvent *); +signals: + /** + * @brief Signal emitted when a QListWidget item is double clicked. + */ + void dbl_click(); +}; + +/** + * @brief The ActionSearchEntry class + * + * Simple wrapper around QLineEdit that emits signals when the up or down arrow keys are pressed so that ActionSearch + * can connect them to moving the current selection up or down. + */ +class ActionSearchEntry : public QLineEdit { + Q_OBJECT +public: + /** + * @brief ActionSearchEntry + * @param parent + * + * Usually ActionSearch. + */ + ActionSearchEntry(QWidget* parent); +protected: + /** + * @brief Override of QLineEdit's key press event that listens for up/down key presses. + * @param event + */ + void keyPressEvent(QKeyEvent * event); +signals: + /** + * @brief Emitted when the user presses the up arrow key. + */ + void moveSelectionUp(); + + /** + * @brief Emitted when the user presses the down arrow key. + */ + void moveSelectionDown(); +}; + +#endif // ACTIONSEARCH_H diff --git a/dialogs/advancedvideodialog.cpp b/dialogs/advancedvideodialog.cpp index 91a0428e2..1119a3832 100644 --- a/dialogs/advancedvideodialog.cpp +++ b/dialogs/advancedvideodialog.cpp @@ -1,107 +1,107 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "advancedvideodialog.h" - -#include -#include -#include -#include - -#include - -extern "C" { -#include -#include -} - -AdvancedVideoDialog::AdvancedVideoDialog(QWidget *parent, - AVCodecID encoding_codec, - VideoCodecParams &iparams) : - QDialog(parent), - params_(iparams) -{ - setWindowTitle(tr("Advanced Video Settings")); - - // use variable for row to assist adding new fields to the grid layout - int row = 0; - - // get encoder information for this codec from FFmpeg - AVCodec* codec_info = avcodec_find_encoder(static_cast(encoding_codec)); - - // set up grid layout for dialog - QGridLayout* layout = new QGridLayout(this); - - // create row for codec pixel formats - layout->addWidget(new QLabel(tr("Pixel Format:")), row, 0); - pix_fmt_combo_ = new QComboBox(); - - // loop through available pixel formats for this codec - int pix_fmt_index = 0; - while (codec_info->pix_fmts[pix_fmt_index] != -1) { // AVCodec->pix_fmts is terminated by "-1" - - // get the name of the pixel format and add it to the combobox (with the pixel format constant) - pix_fmt_combo_->addItem(av_get_pix_fmt_name(codec_info->pix_fmts[pix_fmt_index]), - codec_info->pix_fmts[pix_fmt_index]); - - // if the user has already selected a pixel format, set the combobox to it as well - if (codec_info->pix_fmts[pix_fmt_index] == params_.pix_fmt) { - pix_fmt_combo_->setCurrentIndex(pix_fmt_combo_->count()-1); - } - - pix_fmt_index++; - } - - layout->addWidget(pix_fmt_combo_, row, 1); - - row++; - - // create row for multithreading thread count - layout->addWidget(new QLabel(tr("Threads:")), row, 0); - - thread_spinbox_ = new QSpinBox(); - - // with the thread count, "0" is considered automatics - thread_spinbox_->setMinimum(0); - thread_spinbox_->setSpecialValueText("Auto"); - - // load current thread value - thread_spinbox_->setValue(params_.threads); - - layout->addWidget(thread_spinbox_); - - row++; - - // buttons - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - buttons->setCenterButtons(true); - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); - layout->addWidget(buttons, row, 0, 1, 2); -} - -void AdvancedVideoDialog::accept() { - // store settings back into struct - - params_.pix_fmt = pix_fmt_combo_->currentData().toInt(); - params_.threads = thread_spinbox_->value(); - - QDialog::accept(); -} +/*** + + 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 "advancedvideodialog.h" + +#include +#include +#include +#include + +#include + +extern "C" { +#include +#include +} + +AdvancedVideoDialog::AdvancedVideoDialog(QWidget *parent, + AVCodecID encoding_codec, + VideoCodecParams &iparams) : + QDialog(parent), + params_(iparams) +{ + setWindowTitle(tr("Advanced Video Settings")); + + // use variable for row to assist adding new fields to the grid layout + int row = 0; + + // get encoder information for this codec from FFmpeg + AVCodec* codec_info = avcodec_find_encoder(static_cast(encoding_codec)); + + // set up grid layout for dialog + QGridLayout* layout = new QGridLayout(this); + + // create row for codec pixel formats + layout->addWidget(new QLabel(tr("Pixel Format:")), row, 0); + pix_fmt_combo_ = new QComboBox(); + + // loop through available pixel formats for this codec + int pix_fmt_index = 0; + while (codec_info->pix_fmts[pix_fmt_index] != -1) { // AVCodec->pix_fmts is terminated by "-1" + + // get the name of the pixel format and add it to the combobox (with the pixel format constant) + pix_fmt_combo_->addItem(av_get_pix_fmt_name(codec_info->pix_fmts[pix_fmt_index]), + codec_info->pix_fmts[pix_fmt_index]); + + // if the user has already selected a pixel format, set the combobox to it as well + if (codec_info->pix_fmts[pix_fmt_index] == params_.pix_fmt) { + pix_fmt_combo_->setCurrentIndex(pix_fmt_combo_->count()-1); + } + + pix_fmt_index++; + } + + layout->addWidget(pix_fmt_combo_, row, 1); + + row++; + + // create row for multithreading thread count + layout->addWidget(new QLabel(tr("Threads:")), row, 0); + + thread_spinbox_ = new QSpinBox(); + + // with the thread count, "0" is considered automatics + thread_spinbox_->setMinimum(0); + thread_spinbox_->setSpecialValueText("Auto"); + + // load current thread value + thread_spinbox_->setValue(params_.threads); + + layout->addWidget(thread_spinbox_); + + row++; + + // buttons + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->setCenterButtons(true); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + layout->addWidget(buttons, row, 0, 1, 2); +} + +void AdvancedVideoDialog::accept() { + // store settings back into struct + + params_.pix_fmt = pix_fmt_combo_->currentData().toInt(); + params_.threads = thread_spinbox_->value(); + + QDialog::accept(); +} diff --git a/dialogs/advancedvideodialog.h b/dialogs/advancedvideodialog.h index 5ea80ea91..4ac09cae0 100644 --- a/dialogs/advancedvideodialog.h +++ b/dialogs/advancedvideodialog.h @@ -1,80 +1,80 @@ -/*** - - 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 ADVANCEDVIDEODIALOG_H -#define ADVANCEDVIDEODIALOG_H - -#include -#include -#include - -#include "rendering/exportthread.h" - -/** - * @brief The AdvancedVideoDialog class - * - * A dialog for interfacing with VideoCodecParams, a struct for more advanced video settings sometimes specific to - * one codec. Primarily a companion to ExportDialog which will provide the VideoCodecParams reference, - */ -class AdvancedVideoDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief AdvancedVideoDialog Constructor - * - * @param parent - * - * QWidget parent. Usually ExportDialog. - * - * @param encoding_codec - * - * The AVCodecID of the selected export codec. - * - * @param iparams - * - * A VideoCodecParams struct containing the extra codec data. - */ - AdvancedVideoDialog(QWidget* parent, - AVCodecID encoding_codec, - VideoCodecParams& iparams); - -public slots: - /** - * @brief Overridden accept for saving the UI data into the provided VideoCodecParams struct. - */ - virtual void accept() override; -private: - /** - * @brief Internal reference to VideoCodecParams struct provided by ExportDialog. - */ - VideoCodecParams& params_; - - /** - * @brief ComboBox to show available pixel formats for this codec - */ - QComboBox* pix_fmt_combo_; - - /** - * @brief SpinBox for multithreading settings - */ - QSpinBox* thread_spinbox_; -}; - -#endif // ADVANCEDVIDEODIALOG_H +/*** + + 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 ADVANCEDVIDEODIALOG_H +#define ADVANCEDVIDEODIALOG_H + +#include +#include +#include + +#include "rendering/exportthread.h" + +/** + * @brief The AdvancedVideoDialog class + * + * A dialog for interfacing with VideoCodecParams, a struct for more advanced video settings sometimes specific to + * one codec. Primarily a companion to ExportDialog which will provide the VideoCodecParams reference, + */ +class AdvancedVideoDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief AdvancedVideoDialog Constructor + * + * @param parent + * + * QWidget parent. Usually ExportDialog. + * + * @param encoding_codec + * + * The AVCodecID of the selected export codec. + * + * @param iparams + * + * A VideoCodecParams struct containing the extra codec data. + */ + AdvancedVideoDialog(QWidget* parent, + AVCodecID encoding_codec, + VideoCodecParams& iparams); + +public slots: + /** + * @brief Overridden accept for saving the UI data into the provided VideoCodecParams struct. + */ + virtual void accept() override; +private: + /** + * @brief Internal reference to VideoCodecParams struct provided by ExportDialog. + */ + VideoCodecParams& params_; + + /** + * @brief ComboBox to show available pixel formats for this codec + */ + QComboBox* pix_fmt_combo_; + + /** + * @brief SpinBox for multithreading settings + */ + QSpinBox* thread_spinbox_; +}; + +#endif // ADVANCEDVIDEODIALOG_H diff --git a/dialogs/clippropertiesdialog.cpp b/dialogs/clippropertiesdialog.cpp index 41ba45064..05028f346 100644 --- a/dialogs/clippropertiesdialog.cpp +++ b/dialogs/clippropertiesdialog.cpp @@ -1,145 +1,145 @@ -/*** - - 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 -#include -#include - -#include "panels/panels.h" -#include "undo/undo.h" - -ClipPropertiesDialog::ClipPropertiesDialog(QWidget *parent, QVector clips) : - QDialog(parent) -{ - setWindowTitle((clips.size() == 1) ? - tr("\"%1\" Properties").arg(clips.at(0)->name()) : - tr("Multiple Clip Properties")); - - clips_ = clips; - - QGridLayout* layout = new QGridLayout(this); - - int row = 0; - - // Clip Name field - layout->addWidget(new QLabel(tr("Name:")), row, 0); - - clip_name_field_ = new QLineEdit(); - - layout->addWidget(clip_name_field_, row, 1); - - row++; - - // Clip Duration field - layout->addWidget(new QLabel(tr("Duration:")), row, 0); - - duration_field_ = new LabelSlider(); - duration_field_->SetDisplayType(LabelSlider::FrameNumber); - duration_field_->SetMinimum(1); - layout->addWidget(duration_field_, row, 1); - - row++; - - // Dialog buttons (OK and Cancel) - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - buttons->setCenterButtons(true); - layout->addWidget(buttons, row, 0, 1, 2); - - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); - - // analyze list of clips for default values - - bool all_clips_have_same_name = true; - bool all_clips_have_same_duration = true; - - for (int i=1;iname() != clips.at(i)->name()) { - all_clips_have_same_name = false; - } - if (clips.at(i-1)->length() != clips.at(i)->length()) { - all_clips_have_same_duration = false; - } - } - - Clip* first_clip = clips_.first(); - - if (all_clips_have_same_name) { - // if there's only one clip selected, set all defaults to that clip's properties - clip_name_field_->setText(first_clip->name()); - } else { - // if there are multiple clips, use different properties - clip_name_field_->setPlaceholderText(tr("(multiple)")); - } - - // it's assumed all the clips come from the same sequence - duration_field_->SetFrameRate(first_clip->track()->sequence()->frame_rate()); - - if (all_clips_have_same_duration) { - duration_field_->SetDefault(first_clip->length()); - duration_field_->SetValue(first_clip->length()); - duration_field_->SetMaximum(first_clip->media_length()); - } else { - duration_field_->SetDefault(qSNaN()); - duration_field_->SetValue(qSNaN()); - } -} - -void ClipPropertiesDialog::accept() -{ - const QString& clip_name = clip_name_field_->text(); - double clip_duration = duration_field_->value(); - - ComboAction* ca = new ComboAction(); - - for (int i=0;iname()) { - ca->append(new RenameClipCommand(clip, clip_name)); - } - - // If the user entered a clip duration (and the duration has changed), create a "clip move" command - if (!qIsNaN(clip_duration)) { - long clip_duration_rounded = qRound(clip_duration); - - if (clip->length() != clip_duration_rounded) { - clip->Move(ca, - clip->timeline_in(), - clip->timeline_in() + clip_duration_rounded, - clip->clip_in(), - clip->track()); - } - - } - } - - if (ca->hasActions()) { - olive::undo_stack.push(ca); - update_ui(false); - } else { - delete ca; - } - - QDialog::accept(); -} +/*** + + 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 +#include +#include + +#include "panels/panels.h" +#include "undo/undo.h" + +ClipPropertiesDialog::ClipPropertiesDialog(QWidget *parent, QVector clips) : + QDialog(parent) +{ + setWindowTitle((clips.size() == 1) ? + tr("\"%1\" Properties").arg(clips.at(0)->name()) : + tr("Multiple Clip Properties")); + + clips_ = clips; + + QGridLayout* layout = new QGridLayout(this); + + int row = 0; + + // Clip Name field + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + clip_name_field_ = new QLineEdit(); + + layout->addWidget(clip_name_field_, row, 1); + + row++; + + // Clip Duration field + layout->addWidget(new QLabel(tr("Duration:")), row, 0); + + duration_field_ = new LabelSlider(); + duration_field_->SetDisplayType(LabelSlider::FrameNumber); + duration_field_->SetMinimum(1); + layout->addWidget(duration_field_, row, 1); + + row++; + + // Dialog buttons (OK and Cancel) + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->setCenterButtons(true); + layout->addWidget(buttons, row, 0, 1, 2); + + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + + // analyze list of clips for default values + + bool all_clips_have_same_name = true; + bool all_clips_have_same_duration = true; + + for (int i=1;iname() != clips.at(i)->name()) { + all_clips_have_same_name = false; + } + if (clips.at(i-1)->length() != clips.at(i)->length()) { + all_clips_have_same_duration = false; + } + } + + Clip* first_clip = clips_.first(); + + if (all_clips_have_same_name) { + // if there's only one clip selected, set all defaults to that clip's properties + clip_name_field_->setText(first_clip->name()); + } else { + // if there are multiple clips, use different properties + clip_name_field_->setPlaceholderText(tr("(multiple)")); + } + + // it's assumed all the clips come from the same sequence + duration_field_->SetFrameRate(first_clip->track()->sequence()->frame_rate()); + + if (all_clips_have_same_duration) { + duration_field_->SetDefault(first_clip->length()); + duration_field_->SetValue(first_clip->length()); + duration_field_->SetMaximum(first_clip->media_length()); + } else { + duration_field_->SetDefault(qSNaN()); + duration_field_->SetValue(qSNaN()); + } +} + +void ClipPropertiesDialog::accept() +{ + const QString& clip_name = clip_name_field_->text(); + double clip_duration = duration_field_->value(); + + ComboAction* ca = new ComboAction(); + + for (int i=0;iname()) { + ca->append(new RenameClipCommand(clip, clip_name)); + } + + // If the user entered a clip duration (and the duration has changed), create a "clip move" command + if (!qIsNaN(clip_duration)) { + long clip_duration_rounded = qRound(clip_duration); + + if (clip->length() != clip_duration_rounded) { + clip->Move(ca, + clip->timeline_in(), + clip->timeline_in() + clip_duration_rounded, + clip->clip_in(), + clip->track()); + } + + } + } + + if (ca->hasActions()) { + olive::undo_stack.push(ca); + update_ui(false); + } else { + delete ca; + } + + QDialog::accept(); +} diff --git a/dialogs/clippropertiesdialog.h b/dialogs/clippropertiesdialog.h index 0a08d9de8..e6716162d 100644 --- a/dialogs/clippropertiesdialog.h +++ b/dialogs/clippropertiesdialog.h @@ -1,72 +1,72 @@ -/*** - - 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 - -#include -#include - -#include "timeline/clip.h" -#include "ui/labelslider.h" - -/** - * @brief The ClipPropertiesDialog class - * - * A dialog for setting Clip properties, accessible by right clicking a Clip and clicking "Properties". This can be - * run from anywhere provided it's given a valid array of Clip objects. - */ -class ClipPropertiesDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief ClipPropertiesDialog Constructor - * @param parent - * - * Parent widget. - * - * @param clips - * - * Array of Clip objects to set the properties of. - */ - ClipPropertiesDialog(QWidget* parent, QVector clips); -protected: - /** - * @brief Accept override. Saves the current properties to the array of Clips. - */ - virtual void accept() override; -private: - /** - * @brief Internal clip array (set in the constructor) - */ - QVector clips_; - - /** - * @brief Widget for setting the Clip names - */ - QLineEdit* clip_name_field_; - - /** - * @brief Widget for setting the Clip durations - */ - LabelSlider* duration_field_; -}; - -#endif // CLIPPROPERTIESDIALOG_H +/*** + + 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 + +#include +#include + +#include "timeline/clip.h" +#include "ui/labelslider.h" + +/** + * @brief The ClipPropertiesDialog class + * + * A dialog for setting Clip properties, accessible by right clicking a Clip and clicking "Properties". This can be + * run from anywhere provided it's given a valid array of Clip objects. + */ +class ClipPropertiesDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief ClipPropertiesDialog Constructor + * @param parent + * + * Parent widget. + * + * @param clips + * + * Array of Clip objects to set the properties of. + */ + ClipPropertiesDialog(QWidget* parent, QVector clips); +protected: + /** + * @brief Accept override. Saves the current properties to the array of Clips. + */ + virtual void accept() override; +private: + /** + * @brief Internal clip array (set in the constructor) + */ + QVector clips_; + + /** + * @brief Widget for setting the Clip names + */ + QLineEdit* clip_name_field_; + + /** + * @brief Widget for setting the Clip durations + */ + LabelSlider* duration_field_; +}; + +#endif // CLIPPROPERTIESDIALOG_H diff --git a/dialogs/debugdialog.cpp b/dialogs/debugdialog.cpp index 817ee452d..64aab959e 100644 --- a/dialogs/debugdialog.cpp +++ b/dialogs/debugdialog.cpp @@ -1,63 +1,63 @@ -/*** - - 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 "debugdialog.h" - -#include -#include -#include -#include - -#include "global/debug.h" - -DebugDialog* olive::DebugDialog = nullptr; - -DebugDialog::DebugDialog(QWidget *parent) : QDialog(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - textEdit = new QTextEdit(this); - textEdit->setWordWrapMode(QTextOption::NoWrap); - layout->addWidget(textEdit); - - Retranslate(); -} - -void DebugDialog::Retranslate() -{ - setWindowTitle(tr("Debug Log")); -} - -void DebugDialog::update_log() { - textEdit->setHtml(get_debug_str()); - textEdit->verticalScrollBar()->setValue(textEdit->verticalScrollBar()->maximum()); -} - -void DebugDialog::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::LanguageChange) { - Retranslate(); - } else { - QDialog::changeEvent(e); - } -} - -void DebugDialog::showEvent(QShowEvent *) { - update_log(); -} +/*** + + 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 "debugdialog.h" + +#include +#include +#include +#include + +#include "global/debug.h" + +DebugDialog* olive::DebugDialog = nullptr; + +DebugDialog::DebugDialog(QWidget *parent) : QDialog(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + textEdit = new QTextEdit(this); + textEdit->setWordWrapMode(QTextOption::NoWrap); + layout->addWidget(textEdit); + + Retranslate(); +} + +void DebugDialog::Retranslate() +{ + setWindowTitle(tr("Debug Log")); +} + +void DebugDialog::update_log() { + textEdit->setHtml(get_debug_str()); + textEdit->verticalScrollBar()->setValue(textEdit->verticalScrollBar()->maximum()); +} + +void DebugDialog::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + Retranslate(); + } else { + QDialog::changeEvent(e); + } +} + +void DebugDialog::showEvent(QShowEvent *) { + update_log(); +} diff --git a/dialogs/debugdialog.h b/dialogs/debugdialog.h index d6bade443..229e1bd90 100644 --- a/dialogs/debugdialog.h +++ b/dialogs/debugdialog.h @@ -1,79 +1,79 @@ -/*** - - 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 DEBUGDIALOG_H -#define DEBUGDIALOG_H - -#include -#include - -/** - * @brief The DebugDialog class - * - * A dialog to display the current debug output. This dialog is omnipresent and shown and hidden when the user wants - * to see it. For efficiency, it will not update if it's hidden. - */ -class DebugDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief DebugDialog Constructor - * @param parent - * - * Parent widget. Usually MainWindow. - */ - DebugDialog(QWidget* parent = nullptr); - - /** - * @brief Retranslate window title - * - * Sets title based on the current translation. - */ - void Retranslate(); -public slots: - /** - * @brief Update the visual log with the debug text from get_debug_str() - */ - void update_log(); -protected: - /** - * @brief Overrides change event to trigger Retranslate() on a LanguageChange event. - */ - virtual void changeEvent(QEvent* e) override; - /** - * @brief Overrides show event to trigger an update of the visual log (the visual log does not update while the - * debug dialog is hidden). - */ - virtual void showEvent(QShowEvent* event) override; -private: - /** - * @brief Display widget for the debug dialog. - */ - QTextEdit* textEdit; -}; - -namespace olive { -/** - * @brief Omnipresent instance of DebugDialog to be shown or hidden as the user wants - */ -extern DebugDialog* DebugDialog; -} - -#endif // DEBUGDIALOG_H +/*** + + 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 DEBUGDIALOG_H +#define DEBUGDIALOG_H + +#include +#include + +/** + * @brief The DebugDialog class + * + * A dialog to display the current debug output. This dialog is omnipresent and shown and hidden when the user wants + * to see it. For efficiency, it will not update if it's hidden. + */ +class DebugDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief DebugDialog Constructor + * @param parent + * + * Parent widget. Usually MainWindow. + */ + DebugDialog(QWidget* parent = nullptr); + + /** + * @brief Retranslate window title + * + * Sets title based on the current translation. + */ + void Retranslate(); +public slots: + /** + * @brief Update the visual log with the debug text from get_debug_str() + */ + void update_log(); +protected: + /** + * @brief Overrides change event to trigger Retranslate() on a LanguageChange event. + */ + virtual void changeEvent(QEvent* e) override; + /** + * @brief Overrides show event to trigger an update of the visual log (the visual log does not update while the + * debug dialog is hidden). + */ + virtual void showEvent(QShowEvent* event) override; +private: + /** + * @brief Display widget for the debug dialog. + */ + QTextEdit* textEdit; +}; + +namespace olive { +/** + * @brief Omnipresent instance of DebugDialog to be shown or hidden as the user wants + */ +extern DebugDialog* DebugDialog; +} + +#endif // DEBUGDIALOG_H diff --git a/dialogs/demonotice.cpp b/dialogs/demonotice.cpp index 818208211..2289cfe41 100644 --- a/dialogs/demonotice.cpp +++ b/dialogs/demonotice.cpp @@ -1,62 +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 . - -***/ - -#include "demonotice.h" - -#include -#include -#include - -DemoNotice::DemoNotice(QWidget *parent) : - QDialog(parent) -{ - setWindowTitle(tr("Welcome to Olive!")); - - QVBoxLayout* vlayout = new QVBoxLayout(this); - - QHBoxLayout* layout = new QHBoxLayout(); - layout->setMargin(10); - layout->setSpacing(20); - - QLabel* icon = new QLabel("" - "

" - "", this); - layout->addWidget(icon); - - QLabel* text = new QLabel("

" - "" - + tr("Welcome to Olive!") - + "

" - + tr("Olive is a free open-source video editor released under the GNU GPL. If you have paid for this software, you have been scammed.") - + "

" - + tr("This software is currently in ALPHA which means it is unstable and very likely to crash, have bugs, and have missing features. We offer no warranty so use at your own risk. Please report any bugs or feature requests at %1").arg("www.olivevideoeditor.org") - + "

" - + tr("Thank you for trying Olive and we hope you enjoy it!") - + "

", this); - text->setWordWrap(true); - layout->addWidget(text); - - vlayout->addLayout(layout); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this); - buttons->setCenterButtons(true); - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - vlayout->addWidget(buttons); -} +/*** + + 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 "demonotice.h" + +#include +#include +#include + +DemoNotice::DemoNotice(QWidget *parent) : + QDialog(parent) +{ + setWindowTitle(tr("Welcome to Olive!")); + + QVBoxLayout* vlayout = new QVBoxLayout(this); + + QHBoxLayout* layout = new QHBoxLayout(); + layout->setMargin(10); + layout->setSpacing(20); + + QLabel* icon = new QLabel("" + "

" + "", this); + layout->addWidget(icon); + + QLabel* text = new QLabel("

" + "" + + tr("Welcome to Olive!") + + "

" + + tr("Olive is a free open-source video editor released under the GNU GPL. If you have paid for this software, you have been scammed.") + + "

" + + tr("This software is currently in ALPHA which means it is unstable and very likely to crash, have bugs, and have missing features. We offer no warranty so use at your own risk. Please report any bugs or feature requests at %1").arg("www.olivevideoeditor.org") + + "

" + + tr("Thank you for trying Olive and we hope you enjoy it!") + + "

", this); + text->setWordWrap(true); + layout->addWidget(text); + + vlayout->addLayout(layout); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this); + buttons->setCenterButtons(true); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + vlayout->addWidget(buttons); +} diff --git a/dialogs/demonotice.h b/dialogs/demonotice.h index 39f14dc0b..a716da84c 100644 --- a/dialogs/demonotice.h +++ b/dialogs/demonotice.h @@ -1,47 +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 DEMONOTICE_H -#define DEMONOTICE_H - -#include - -/** - * @brief The DemoNotice class - * - * Simple dialog shown on startup to introduce Olive as alpha software (in release builds). Can be run from anywhere, - * but there should be no reason to create it outside of the application launch. - * - * To be phased out as Olive gains maturity. - */ -class DemoNotice : public QDialog -{ - Q_OBJECT -public: - /** - * @brief DemoNotice Constructor - * @param parent - * - * QWidget parent. Usually MainWindow. - */ - explicit DemoNotice(QWidget *parent = nullptr); -}; - -#endif // DEMONOTICE_H +/*** + + 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 DEMONOTICE_H +#define DEMONOTICE_H + +#include + +/** + * @brief The DemoNotice class + * + * Simple dialog shown on startup to introduce Olive as alpha software (in release builds). Can be run from anywhere, + * but there should be no reason to create it outside of the application launch. + * + * To be phased out as Olive gains maturity. + */ +class DemoNotice : public QDialog +{ + Q_OBJECT +public: + /** + * @brief DemoNotice Constructor + * @param parent + * + * QWidget parent. Usually MainWindow. + */ + explicit DemoNotice(QWidget *parent = nullptr); +}; + +#endif // DEMONOTICE_H diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 2dac273b3..afd8b9321 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -1,807 +1,807 @@ -/*** - - 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 "exportdialog.h" - -extern "C" { -#include -} - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "global/global.h" -#include "dialogs/advancedvideodialog.h" -#include "panels/panels.h" -#include "ui/viewerwidget.h" -#include "rendering/renderfunctions.h" -#include "rendering/audio.h" -#include "rendering/exportthread.h" -#include "ui/mainwindow.h" - -enum ExportFormats { - FORMAT_3GPP, - FORMAT_AIFF, - FORMAT_APNG, - FORMAT_AVI, - FORMAT_DNXHD, - FORMAT_AC3, - FORMAT_FLV, - FORMAT_GIF, - FORMAT_IMG, - FORMAT_MP2, - FORMAT_MP3, - FORMAT_MPEG1, - FORMAT_MPEG2, - FORMAT_MPEG4, - FORMAT_MPEGTS, - FORMAT_MKV, - FORMAT_OGG, - FORMAT_MOV, - FORMAT_WAV, - FORMAT_WEBM, - FORMAT_WMV, - FORMAT_SIZE -}; - -ExportDialog::ExportDialog(QWidget *parent, Sequence* sequence) : - QDialog(parent), - sequence_(sequence) -{ - setWindowTitle(tr("Export \"%1\"").arg(sequence->name())); - setup_ui(); - - rangeCombobox->setCurrentIndex(0); - if (sequence->using_workarea) { - rangeCombobox->setEnabled(true); - rangeCombobox->setCurrentIndex(1); - } - - format_strings.resize(FORMAT_SIZE); - format_strings[FORMAT_3GPP] = "3GPP"; - format_strings[FORMAT_AIFF] = "AIFF"; - format_strings[FORMAT_APNG] = "Animated PNG"; - format_strings[FORMAT_AVI] = "AVI"; - format_strings[FORMAT_DNXHD] = "DNxHD"; - format_strings[FORMAT_AC3] = "Dolby Digital (AC3)"; - format_strings[FORMAT_FLV] = "FLV"; - format_strings[FORMAT_GIF] = "GIF"; - format_strings[FORMAT_IMG] = "Image Sequence"; - format_strings[FORMAT_MP2] = "MP2 Audio"; - format_strings[FORMAT_MP3] = "MP3 Audio"; - format_strings[FORMAT_MPEG1] = "MPEG-1 Video"; - format_strings[FORMAT_MPEG2] = "MPEG-2 Video"; - format_strings[FORMAT_MPEG4] = "MPEG-4 Video"; - format_strings[FORMAT_MPEGTS] = "MPEG-TS"; - format_strings[FORMAT_MKV] = "Matroska MKV"; - format_strings[FORMAT_OGG] = "Ogg"; - format_strings[FORMAT_MOV] = "QuickTime MOV"; - format_strings[FORMAT_WAV] = "WAVE Audio"; - format_strings[FORMAT_WEBM] = "WebM"; - format_strings[FORMAT_WMV] = "Windows Media"; - - for (int i=0;iaddItem(format_strings[i]); - } - formatCombobox->setCurrentIndex(FORMAT_MPEG4); - - // default to sequence's native dimensions - 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; -} - -void ExportDialog::add_codec_to_combobox(QComboBox* box, enum AVCodecID codec) { - QString codec_name; - - AVCodec* codec_info = avcodec_find_encoder(codec); - - if (codec_info == nullptr) { - codec_name = tr("Unknown codec name %1").arg(static_cast(codec)); - } else { - codec_name = codec_info->long_name; - } - - box->addItem(codec_name, codec); -} - -void ExportDialog::format_changed(int index) { - vcodecCombobox->clear(); - acodecCombobox->clear(); - - int default_vcodec = 0; - int default_acodec = 0; - - switch (index) { - case FORMAT_3GPP: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); - - default_vcodec = 1; - break; - case FORMAT_AIFF: - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - break; - case FORMAT_APNG: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_APNG); - break; - case FORMAT_AVI: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MJPEG); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MSVIDEO1); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_RAWVIDEO); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_HUFFYUV); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_DVVIDEO); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_FLAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - - default_vcodec = 3; - default_acodec = 5; - break; - case FORMAT_DNXHD: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_DNXHD); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - break; - case FORMAT_AC3: - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_EAC3); - break; - case FORMAT_FLV: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_FLV1); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - break; - case FORMAT_GIF: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_GIF); - break; - case FORMAT_IMG: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_BMP); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MJPEG); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_JPEG2000); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_PSD); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_PNG); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_TIFF); - - default_vcodec = 4; - break; - case FORMAT_MP2: - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - break; - case FORMAT_MP3: - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - break; - case FORMAT_MPEG1: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG1VIDEO); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - - default_acodec = 1; - break; - case FORMAT_MPEG2: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG2VIDEO); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - - default_acodec = 1; - break; - case FORMAT_MPEG4: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - - default_vcodec = 1; - break; - case FORMAT_MPEGTS: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG2VIDEO); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - - default_acodec = 2; - break; - case FORMAT_MKV: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_EAC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_FLAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_OPUS); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_VORBIS); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WAVPACK); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV1); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV2); - - default_vcodec = 1; - break; - case FORMAT_OGG: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_THEORA); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_OPUS); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_VORBIS); - - default_acodec = 1; - break; - case FORMAT_MOV: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_QTRLE); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MJPEG); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_PRORES); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - - default_vcodec = 2; - break; - case FORMAT_WAV: - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); - break; - case FORMAT_WEBM: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_VP8); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_VP9); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_OPUS); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_VORBIS); - - default_vcodec = 1; - break; - case FORMAT_WMV: - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_WMV1); - add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_WMV2); - - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV1); - add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV2); - - default_vcodec = 1; - default_acodec = 1; - break; - default: - qCritical() << "Invalid format selection - this is a bug, please inform the developers"; - } - - vcodecCombobox->setCurrentIndex(default_vcodec); - acodecCombobox->setCurrentIndex(default_acodec); - - bool video_enabled = vcodecCombobox->count() != 0; - bool audio_enabled = acodecCombobox->count() != 0; - videoGroupbox->setChecked(video_enabled); - audioGroupbox->setChecked(audio_enabled); - videoGroupbox->setEnabled(video_enabled); - audioGroupbox->setEnabled(audio_enabled); -} - -void ExportDialog::export_thread_finished() { - // Determine if the export succeeded - bool succeeded = (progressBar->value() == 100); - - // If it failed and we didn't cancel it, it must have errored out. Show an error message. - if (!succeeded && !export_thread_->WasInterrupted()) { - QMessageBox::critical( - this, - tr("Export Failed"), - tr("Export failed - %1").arg(export_thread_->GetError()), - QMessageBox::Ok - ); - } - - // Clear audio buffer - clear_audio_ibuffer(); - - // Re-enable/disable UI widgets based on the rendering state - 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(); - - // Update the application UI - update_ui(false); - - // Disconnect cancel button from export thread - disconnect(renderCancel, SIGNAL(clicked(bool)), export_thread_, SLOT(Interrupt())); - - // Free the export thread - export_thread_->deleteLater(); - - // If the export succeeded, close the dialog - if (succeeded) { - accept(); - } -} - -void ExportDialog::prep_ui_for_render(bool r) { - export_button->setEnabled(!r); - cancel_button->setEnabled(!r); - videoGroupbox->setEnabled(!r); - audioGroupbox->setEnabled(!r); - renderCancel->setEnabled(r); -} - -void ExportDialog::StartExport() { - if (widthSpinbox->value()%2 == 1 || heightSpinbox->value()%2 == 1) { - QMessageBox::critical( - this, - tr("Invalid dimensions"), - tr("Export width and height must both be even numbers/divisible by 2."), - QMessageBox::Ok - ); - return; - } - - QString ext; - switch (formatCombobox->currentIndex()) { - case FORMAT_3GPP: - ext = "3gp"; - break; - case FORMAT_AIFF: - ext = "aiff"; - break; - case FORMAT_APNG: - ext = "apng"; - break; - case FORMAT_AVI: - ext = "avi"; - break; - case FORMAT_DNXHD: - ext = "mxf"; - break; - case FORMAT_AC3: - ext = "ac3"; - break; - case FORMAT_FLV: - ext = "flv"; - break; - case FORMAT_GIF: - ext = "gif"; - break; - case FORMAT_IMG: - switch (vcodecCombobox->currentData().toInt()) { - case AV_CODEC_ID_BMP: - ext = "bmp"; - break; - case AV_CODEC_ID_MJPEG: - ext = "jpg"; - break; - case AV_CODEC_ID_JPEG2000: - ext = "jp2"; - break; - case AV_CODEC_ID_PSD: - ext = "psd"; - break; - case AV_CODEC_ID_PNG: - ext = "png"; - break; - case AV_CODEC_ID_TIFF: - ext = "tif"; - break; - default: - qCritical() << "Invalid codec selection for an image sequence"; - QMessageBox::critical( - this, - tr("Invalid codec"), - tr("Couldn't determine output parameters for the selected codec. This is a bug, please contact the developers."), - QMessageBox::Ok - ); - return; - } - break; - case FORMAT_MP3: - ext = "mp3"; - break; - case FORMAT_MPEG1: - if (videoGroupbox->isChecked() && !audioGroupbox->isChecked()) { - ext = "m1v"; - } else if (!videoGroupbox->isChecked() && audioGroupbox->isChecked()) { - ext = "m1a"; - } else { - ext = "mpg"; - } - break; - case FORMAT_MPEG2: - if (videoGroupbox->isChecked() && !audioGroupbox->isChecked()) { - ext = "m2v"; - } else if (!videoGroupbox->isChecked() && audioGroupbox->isChecked()) { - ext = "m2a"; - } else { - ext = "mpg"; - } - break; - case FORMAT_MPEG4: - if (videoGroupbox->isChecked() && !audioGroupbox->isChecked()) { - ext = "m4v"; - } else if (!videoGroupbox->isChecked() && audioGroupbox->isChecked()) { - ext = "m4a"; - } else { - ext = "mp4"; - } - break; - case FORMAT_MPEGTS: - ext = "ts"; - break; - case FORMAT_MKV: - if (!videoGroupbox->isChecked()) { - ext = "mka"; - } else { - ext = "mkv"; - } - break; - case FORMAT_OGG: - ext = "ogg"; - break; - case FORMAT_MOV: - ext = "mov"; - break; - case FORMAT_WAV: - ext = "wav"; - break; - case FORMAT_WEBM: - ext = "webm"; - break; - case FORMAT_WMV: - if (videoGroupbox->isChecked()) { - ext = "wmv"; - } else { - ext = "wma"; - } - break; - default: - qCritical() << "Invalid format - this is a bug, please inform the developers"; - QMessageBox::critical( - this, - tr("Invalid format"), - tr("Couldn't determine output format. This is a bug, please contact the developers."), - QMessageBox::Ok - ); - return; - } - QString filename = QFileDialog::getSaveFileName( - this, - tr("Export Media"), - "", - format_strings[formatCombobox->currentIndex()] + " (*." + ext + ")" - ); - if (!filename.isEmpty()) { - if (!filename.endsWith("." + ext, Qt::CaseInsensitive)) { - filename += "." + ext; - } - - if (formatCombobox->currentIndex() == FORMAT_IMG) { - int ext_location = filename.lastIndexOf('.'); - if (ext_location > filename.lastIndexOf('/')) { - filename.insert(ext_location, 'd'); - filename.insert(ext_location, '5'); - filename.insert(ext_location, '0'); - filename.insert(ext_location, '%'); - } - } - - // 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) { - params.video_codec = vcodecCombobox->currentData().toInt(); - params.video_width = widthSpinbox->value(); - params.video_height = heightSpinbox->value(); - params.video_frame_rate = framerateSpinbox->value(); - params.video_compression_type = compressionTypeCombobox->currentData().toInt(); - params.video_bitrate = videobitrateSpinbox->value(); - } - params.audio_enabled = audioGroupbox->isChecked(); - if (params.audio_enabled) { - params.audio_codec = acodecCombobox->currentData().toInt(); - params.audio_sampling_rate = samplingRateSpinbox->value(); - params.audio_bitrate = audiobitrateSpinbox->value(); - } - - params.start_frame = 0; - params.end_frame = sequence_->GetEndFrame(); // entire sequence - if (rangeCombobox->currentIndex() == 1) { - params.start_frame = qMax(sequence_->workarea_in, params.start_frame); - params.end_frame = qMin(sequence_->workarea_out, params.end_frame); - } - - // Create export thread - export_thread_ = new ExportThread(params, vcodec_params, this); - - // Connect export thread signals/slots - connect(export_thread_, SIGNAL(finished()), this, SLOT(export_thread_finished())); - connect(export_thread_, SIGNAL(ProgressChanged(int, qint64)), this, SLOT(update_progress_bar(int, qint64))); - connect(renderCancel, SIGNAL(clicked(bool)), export_thread_, SLOT(Interrupt())); - - // Close all effects in effect controls (prevents UI threading issues) - panel_effect_controls->Clear(); - - // Close all currently open clips - sequence_->Close(); - - olive::Global->set_export_state(true); - - olive::Global->save_autorecovery_file(); - - prep_ui_for_render(true); - - total_export_time_start = QDateTime::currentMSecsSinceEpoch(); - - export_thread_->start(); - } -} - -void ExportDialog::update_progress_bar(int value, qint64 remaining_ms) { - if (value == 100) { - // if value is 100%, show total render time rather than remaining - remaining_ms = QDateTime::currentMSecsSinceEpoch() - total_export_time_start; - } - - // convert ms to H:MM:SS - int seconds = qFloor(remaining_ms*0.001)%60; - int minutes = qFloor(remaining_ms/60000)%60; - int hours = qFloor(remaining_ms/3600000); - - if (value == 100) { - // show value as "total" - progressBar->setFormat(tr("%p% (Total: %1:%2:%3)").arg(QString::number(hours), - QString::number(minutes).rightJustified(2, '0'), - QString::number(seconds).rightJustified(2, '0'))); - } else { - // show value as "remaining" - progressBar->setFormat(tr("%p% (ETA: %1:%2:%3)").arg(QString::number(hours), - QString::number(minutes).rightJustified(2, '0'), - QString::number(seconds).rightJustified(2, '0'))); - } - - progressBar->setValue(value); -} - -void ExportDialog::vcodec_changed(int index) { - compressionTypeCombobox->clear(); - - if (vcodecCombobox->count() > 0) { - if (vcodecCombobox->itemData(index) == AV_CODEC_ID_H264 - || vcodecCombobox->itemData(index) == AV_CODEC_ID_H265) { - compressionTypeCombobox->setEnabled(true); - compressionTypeCombobox->addItem(tr("Quality-based (Constant Rate Factor)"), COMPRESSION_TYPE_CFR); - // compressionTypeCombobox->addItem("File size-based (Two-Pass)", COMPRESSION_TYPE_TARGETSIZE); - // compressionTypeCombobox->addItem("Average bitrate (Two-Pass)", COMPRESSION_TYPE_TARGETBR); - } else { - compressionTypeCombobox->addItem(tr("Constant Bitrate"), COMPRESSION_TYPE_CBR); - compressionTypeCombobox->setCurrentIndex(0); - compressionTypeCombobox->setEnabled(false); - } - - // set default pix_fmt for this codec - AVCodec* codec_info = avcodec_find_encoder(static_cast(vcodecCombobox->itemData(index).toInt())); - if (codec_info == nullptr) { - QMessageBox::critical(this, - tr("Invalid Codec"), - tr("Failed to find a suitable encoder for this codec. Export will likely fail.")); - } else { - vcodec_params.pix_fmt = codec_info->pix_fmts[0]; - if (vcodec_params.pix_fmt == -1) { - QMessageBox::critical(this, - tr("Invalid Codec"), - tr("Failed to find pixel format for this encoder. Export will likely fail.")); - } - } - } -} - -void ExportDialog::comp_type_changed(int) { - videobitrateSpinbox->setToolTip(""); - videobitrateSpinbox->setMinimum(0); - videobitrateSpinbox->setMaximum(99.99); - switch (compressionTypeCombobox->currentData().toInt()) { - case COMPRESSION_TYPE_CBR: - case COMPRESSION_TYPE_TARGETBR: - videoBitrateLabel->setText(tr("Bitrate (Mbps):")); - 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(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; - case COMPRESSION_TYPE_TARGETSIZE: - videoBitrateLabel->setText(tr("Target File Size (MB):")); - videobitrateSpinbox->setValue(100); - break; - } -} - -void ExportDialog::open_advanced_video_dialog() { - AdvancedVideoDialog avd(this, static_cast(vcodecCombobox->currentData().toInt()), vcodec_params); - avd.exec(); -} - -void ExportDialog::setup_ui() { - QVBoxLayout* verticalLayout = new QVBoxLayout(this); - - QHBoxLayout* format_layout = new QHBoxLayout(); - - format_layout->addWidget(new QLabel(tr("Format:"), this)); - - formatCombobox = new QComboBox(); - format_layout->addWidget(formatCombobox); - - verticalLayout->addLayout(format_layout); - - QHBoxLayout* range_layout = new QHBoxLayout(); - - range_layout->addWidget(new QLabel(tr("Range:"), this)); - - rangeCombobox = new QComboBox(this); - rangeCombobox->addItem(tr("Entire Sequence")); - rangeCombobox->addItem(tr("In to Out")); - - range_layout->addWidget(rangeCombobox); - - verticalLayout->addLayout(range_layout); - - videoGroupbox = new QGroupBox(this); - videoGroupbox->setTitle(tr("Video")); - videoGroupbox->setFlat(false); - videoGroupbox->setCheckable(true); - - QGridLayout* videoGridLayout = new QGridLayout(videoGroupbox); - - videoGridLayout->addWidget(new QLabel(tr("Codec:"), this), 0, 0, 1, 1); - vcodecCombobox = new QComboBox(videoGroupbox); - videoGridLayout->addWidget(vcodecCombobox, 0, 1, 1, 1); - - videoGridLayout->addWidget(new QLabel(tr("Width:"), this), 1, 0, 1, 1); - widthSpinbox = new QSpinBox(videoGroupbox); - widthSpinbox->setMaximum(16777216); - videoGridLayout->addWidget(widthSpinbox, 1, 1, 1, 1); - - videoGridLayout->addWidget(new QLabel(tr("Height:"), this), 2, 0, 1, 1); - heightSpinbox = new QSpinBox(videoGroupbox); - heightSpinbox->setMaximum(16777216); - videoGridLayout->addWidget(heightSpinbox, 2, 1, 1, 1); - - videoGridLayout->addWidget(new QLabel(tr("Frame Rate:"), this), 3, 0, 1, 1); - framerateSpinbox = new QDoubleSpinBox(videoGroupbox); - framerateSpinbox->setMaximum(60); - framerateSpinbox->setValue(0); - videoGridLayout->addWidget(framerateSpinbox, 3, 1, 1, 1); - - videoGridLayout->addWidget(new QLabel(tr("Compression Type:"), this), 4, 0, 1, 1); - compressionTypeCombobox = new QComboBox(videoGroupbox); - videoGridLayout->addWidget(compressionTypeCombobox, 4, 1, 1, 1); - - videoBitrateLabel = new QLabel(videoGroupbox); - videoGridLayout->addWidget(videoBitrateLabel, 5, 0, 1, 1); - videobitrateSpinbox = new QDoubleSpinBox(videoGroupbox); - videobitrateSpinbox->setMaximum(100); - videobitrateSpinbox->setValue(2); - videoGridLayout->addWidget(videobitrateSpinbox, 5, 1, 1, 1); - - QPushButton* advanced_video_button = new QPushButton(tr("Advanced")); - connect(advanced_video_button, SIGNAL(clicked(bool)), this, SLOT(open_advanced_video_dialog())); - videoGridLayout->addWidget(advanced_video_button, 6, 1); - - verticalLayout->addWidget(videoGroupbox); - - audioGroupbox = new QGroupBox(this); - audioGroupbox->setTitle(tr("Audio")); - audioGroupbox->setCheckable(true); - - QGridLayout* audioGridLayout = new QGridLayout(audioGroupbox); - - audioGridLayout->addWidget(new QLabel(tr("Codec:"), this), 0, 0, 1, 1); - acodecCombobox = new QComboBox(audioGroupbox); - audioGridLayout->addWidget(acodecCombobox, 0, 1, 1, 1); - - audioGridLayout->addWidget(new QLabel(tr("Sampling Rate:"), this), 1, 0, 1, 1); - samplingRateSpinbox = new QSpinBox(audioGroupbox); - samplingRateSpinbox->setMaximum(96000); - samplingRateSpinbox->setValue(0); - audioGridLayout->addWidget(samplingRateSpinbox, 1, 1, 1, 1); - - audioGridLayout->addWidget(new QLabel(tr("Bitrate (Kbps/CBR):"), this), 3, 0, 1, 1); - audiobitrateSpinbox = new QSpinBox(audioGroupbox); - audiobitrateSpinbox->setMaximum(320); - audiobitrateSpinbox->setValue(256); - audioGridLayout->addWidget(audiobitrateSpinbox, 3, 1, 1, 1); - - verticalLayout->addWidget(audioGroupbox); - - QHBoxLayout* progressLayout = new QHBoxLayout(); - progressBar = new QProgressBar(this); - progressBar->setFormat("%p% (ETA: 0:00:00)"); - progressBar->setEnabled(false); - progressBar->setValue(0); - progressLayout->addWidget(progressBar); - - renderCancel = new QPushButton(this); - renderCancel->setIcon(QIcon(":/icons/error.svg")); - renderCancel->setEnabled(false); - progressLayout->addWidget(renderCancel); - - verticalLayout->addLayout(progressLayout); - - QHBoxLayout* buttonLayout = new QHBoxLayout(); - buttonLayout->addStretch(); - - export_button = new QPushButton(this); - export_button->setText("Export"); - connect(export_button, SIGNAL(clicked(bool)), this, SLOT(StartExport())); - - buttonLayout->addWidget(export_button); - - cancel_button = new QPushButton(this); - cancel_button->setText("Cancel"); - connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); - - buttonLayout->addWidget(cancel_button); - - buttonLayout->addStretch(); - - verticalLayout->addLayout(buttonLayout); - - connect(formatCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(format_changed(int))); - connect(compressionTypeCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(comp_type_changed(int))); - connect(vcodecCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(vcodec_changed(int))); -} +/*** + + 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 "exportdialog.h" + +extern "C" { +#include +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "global/global.h" +#include "dialogs/advancedvideodialog.h" +#include "panels/panels.h" +#include "ui/viewerwidget.h" +#include "rendering/renderfunctions.h" +#include "rendering/audio.h" +#include "rendering/exportthread.h" +#include "ui/mainwindow.h" + +enum ExportFormats { + FORMAT_3GPP, + FORMAT_AIFF, + FORMAT_APNG, + FORMAT_AVI, + FORMAT_DNXHD, + FORMAT_AC3, + FORMAT_FLV, + FORMAT_GIF, + FORMAT_IMG, + FORMAT_MP2, + FORMAT_MP3, + FORMAT_MPEG1, + FORMAT_MPEG2, + FORMAT_MPEG4, + FORMAT_MPEGTS, + FORMAT_MKV, + FORMAT_OGG, + FORMAT_MOV, + FORMAT_WAV, + FORMAT_WEBM, + FORMAT_WMV, + FORMAT_SIZE +}; + +ExportDialog::ExportDialog(QWidget *parent, Sequence* sequence) : + QDialog(parent), + sequence_(sequence) +{ + setWindowTitle(tr("Export \"%1\"").arg(sequence->name())); + setup_ui(); + + rangeCombobox->setCurrentIndex(0); + if (sequence->using_workarea) { + rangeCombobox->setEnabled(true); + rangeCombobox->setCurrentIndex(1); + } + + format_strings.resize(FORMAT_SIZE); + format_strings[FORMAT_3GPP] = "3GPP"; + format_strings[FORMAT_AIFF] = "AIFF"; + format_strings[FORMAT_APNG] = "Animated PNG"; + format_strings[FORMAT_AVI] = "AVI"; + format_strings[FORMAT_DNXHD] = "DNxHD"; + format_strings[FORMAT_AC3] = "Dolby Digital (AC3)"; + format_strings[FORMAT_FLV] = "FLV"; + format_strings[FORMAT_GIF] = "GIF"; + format_strings[FORMAT_IMG] = "Image Sequence"; + format_strings[FORMAT_MP2] = "MP2 Audio"; + format_strings[FORMAT_MP3] = "MP3 Audio"; + format_strings[FORMAT_MPEG1] = "MPEG-1 Video"; + format_strings[FORMAT_MPEG2] = "MPEG-2 Video"; + format_strings[FORMAT_MPEG4] = "MPEG-4 Video"; + format_strings[FORMAT_MPEGTS] = "MPEG-TS"; + format_strings[FORMAT_MKV] = "Matroska MKV"; + format_strings[FORMAT_OGG] = "Ogg"; + format_strings[FORMAT_MOV] = "QuickTime MOV"; + format_strings[FORMAT_WAV] = "WAVE Audio"; + format_strings[FORMAT_WEBM] = "WebM"; + format_strings[FORMAT_WMV] = "Windows Media"; + + for (int i=0;iaddItem(format_strings[i]); + } + formatCombobox->setCurrentIndex(FORMAT_MPEG4); + + // default to sequence's native dimensions + 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; +} + +void ExportDialog::add_codec_to_combobox(QComboBox* box, enum AVCodecID codec) { + QString codec_name; + + AVCodec* codec_info = avcodec_find_encoder(codec); + + if (codec_info == nullptr) { + codec_name = tr("Unknown codec name %1").arg(static_cast(codec)); + } else { + codec_name = codec_info->long_name; + } + + box->addItem(codec_name, codec); +} + +void ExportDialog::format_changed(int index) { + vcodecCombobox->clear(); + acodecCombobox->clear(); + + int default_vcodec = 0; + int default_acodec = 0; + + switch (index) { + case FORMAT_3GPP: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); + + default_vcodec = 1; + break; + case FORMAT_AIFF: + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + break; + case FORMAT_APNG: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_APNG); + break; + case FORMAT_AVI: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MJPEG); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MSVIDEO1); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_RAWVIDEO); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_HUFFYUV); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_DVVIDEO); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_FLAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + + default_vcodec = 3; + default_acodec = 5; + break; + case FORMAT_DNXHD: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_DNXHD); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + break; + case FORMAT_AC3: + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_EAC3); + break; + case FORMAT_FLV: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_FLV1); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + break; + case FORMAT_GIF: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_GIF); + break; + case FORMAT_IMG: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_BMP); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MJPEG); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_JPEG2000); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_PSD); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_PNG); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_TIFF); + + default_vcodec = 4; + break; + case FORMAT_MP2: + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + break; + case FORMAT_MP3: + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + break; + case FORMAT_MPEG1: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG1VIDEO); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + + default_acodec = 1; + break; + case FORMAT_MPEG2: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG2VIDEO); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + + default_acodec = 1; + break; + case FORMAT_MPEG4: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + + default_vcodec = 1; + break; + case FORMAT_MPEGTS: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG2VIDEO); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + + default_acodec = 2; + break; + case FORMAT_MKV: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_EAC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_FLAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_OPUS); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_VORBIS); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WAVPACK); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV1); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV2); + + default_vcodec = 1; + break; + case FORMAT_OGG: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_THEORA); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_OPUS); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_VORBIS); + + default_acodec = 1; + break; + case FORMAT_MOV: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_QTRLE); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MPEG4); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H264); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_H265); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_MJPEG); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_PRORES); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AAC); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_AC3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP2); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_MP3); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + + default_vcodec = 2; + break; + case FORMAT_WAV: + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_PCM_S16LE); + break; + case FORMAT_WEBM: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_VP8); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_VP9); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_OPUS); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_VORBIS); + + default_vcodec = 1; + break; + case FORMAT_WMV: + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_WMV1); + add_codec_to_combobox(vcodecCombobox, AV_CODEC_ID_WMV2); + + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV1); + add_codec_to_combobox(acodecCombobox, AV_CODEC_ID_WMAV2); + + default_vcodec = 1; + default_acodec = 1; + break; + default: + qCritical() << "Invalid format selection - this is a bug, please inform the developers"; + } + + vcodecCombobox->setCurrentIndex(default_vcodec); + acodecCombobox->setCurrentIndex(default_acodec); + + bool video_enabled = vcodecCombobox->count() != 0; + bool audio_enabled = acodecCombobox->count() != 0; + videoGroupbox->setChecked(video_enabled); + audioGroupbox->setChecked(audio_enabled); + videoGroupbox->setEnabled(video_enabled); + audioGroupbox->setEnabled(audio_enabled); +} + +void ExportDialog::export_thread_finished() { + // Determine if the export succeeded + bool succeeded = (progressBar->value() == 100); + + // If it failed and we didn't cancel it, it must have errored out. Show an error message. + if (!succeeded && !export_thread_->WasInterrupted()) { + QMessageBox::critical( + this, + tr("Export Failed"), + tr("Export failed - %1").arg(export_thread_->GetError()), + QMessageBox::Ok + ); + } + + // Clear audio buffer + clear_audio_ibuffer(); + + // Re-enable/disable UI widgets based on the rendering state + 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(); + + // Update the application UI + update_ui(false); + + // Disconnect cancel button from export thread + disconnect(renderCancel, SIGNAL(clicked(bool)), export_thread_, SLOT(Interrupt())); + + // Free the export thread + export_thread_->deleteLater(); + + // If the export succeeded, close the dialog + if (succeeded) { + accept(); + } +} + +void ExportDialog::prep_ui_for_render(bool r) { + export_button->setEnabled(!r); + cancel_button->setEnabled(!r); + videoGroupbox->setEnabled(!r); + audioGroupbox->setEnabled(!r); + renderCancel->setEnabled(r); +} + +void ExportDialog::StartExport() { + if (widthSpinbox->value()%2 == 1 || heightSpinbox->value()%2 == 1) { + QMessageBox::critical( + this, + tr("Invalid dimensions"), + tr("Export width and height must both be even numbers/divisible by 2."), + QMessageBox::Ok + ); + return; + } + + QString ext; + switch (formatCombobox->currentIndex()) { + case FORMAT_3GPP: + ext = "3gp"; + break; + case FORMAT_AIFF: + ext = "aiff"; + break; + case FORMAT_APNG: + ext = "apng"; + break; + case FORMAT_AVI: + ext = "avi"; + break; + case FORMAT_DNXHD: + ext = "mxf"; + break; + case FORMAT_AC3: + ext = "ac3"; + break; + case FORMAT_FLV: + ext = "flv"; + break; + case FORMAT_GIF: + ext = "gif"; + break; + case FORMAT_IMG: + switch (vcodecCombobox->currentData().toInt()) { + case AV_CODEC_ID_BMP: + ext = "bmp"; + break; + case AV_CODEC_ID_MJPEG: + ext = "jpg"; + break; + case AV_CODEC_ID_JPEG2000: + ext = "jp2"; + break; + case AV_CODEC_ID_PSD: + ext = "psd"; + break; + case AV_CODEC_ID_PNG: + ext = "png"; + break; + case AV_CODEC_ID_TIFF: + ext = "tif"; + break; + default: + qCritical() << "Invalid codec selection for an image sequence"; + QMessageBox::critical( + this, + tr("Invalid codec"), + tr("Couldn't determine output parameters for the selected codec. This is a bug, please contact the developers."), + QMessageBox::Ok + ); + return; + } + break; + case FORMAT_MP3: + ext = "mp3"; + break; + case FORMAT_MPEG1: + if (videoGroupbox->isChecked() && !audioGroupbox->isChecked()) { + ext = "m1v"; + } else if (!videoGroupbox->isChecked() && audioGroupbox->isChecked()) { + ext = "m1a"; + } else { + ext = "mpg"; + } + break; + case FORMAT_MPEG2: + if (videoGroupbox->isChecked() && !audioGroupbox->isChecked()) { + ext = "m2v"; + } else if (!videoGroupbox->isChecked() && audioGroupbox->isChecked()) { + ext = "m2a"; + } else { + ext = "mpg"; + } + break; + case FORMAT_MPEG4: + if (videoGroupbox->isChecked() && !audioGroupbox->isChecked()) { + ext = "m4v"; + } else if (!videoGroupbox->isChecked() && audioGroupbox->isChecked()) { + ext = "m4a"; + } else { + ext = "mp4"; + } + break; + case FORMAT_MPEGTS: + ext = "ts"; + break; + case FORMAT_MKV: + if (!videoGroupbox->isChecked()) { + ext = "mka"; + } else { + ext = "mkv"; + } + break; + case FORMAT_OGG: + ext = "ogg"; + break; + case FORMAT_MOV: + ext = "mov"; + break; + case FORMAT_WAV: + ext = "wav"; + break; + case FORMAT_WEBM: + ext = "webm"; + break; + case FORMAT_WMV: + if (videoGroupbox->isChecked()) { + ext = "wmv"; + } else { + ext = "wma"; + } + break; + default: + qCritical() << "Invalid format - this is a bug, please inform the developers"; + QMessageBox::critical( + this, + tr("Invalid format"), + tr("Couldn't determine output format. This is a bug, please contact the developers."), + QMessageBox::Ok + ); + return; + } + QString filename = QFileDialog::getSaveFileName( + this, + tr("Export Media"), + "", + format_strings[formatCombobox->currentIndex()] + " (*." + ext + ")" + ); + if (!filename.isEmpty()) { + if (!filename.endsWith("." + ext, Qt::CaseInsensitive)) { + filename += "." + ext; + } + + if (formatCombobox->currentIndex() == FORMAT_IMG) { + int ext_location = filename.lastIndexOf('.'); + if (ext_location > filename.lastIndexOf('/')) { + filename.insert(ext_location, 'd'); + filename.insert(ext_location, '5'); + filename.insert(ext_location, '0'); + filename.insert(ext_location, '%'); + } + } + + // 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) { + params.video_codec = vcodecCombobox->currentData().toInt(); + params.video_width = widthSpinbox->value(); + params.video_height = heightSpinbox->value(); + params.video_frame_rate = framerateSpinbox->value(); + params.video_compression_type = compressionTypeCombobox->currentData().toInt(); + params.video_bitrate = videobitrateSpinbox->value(); + } + params.audio_enabled = audioGroupbox->isChecked(); + if (params.audio_enabled) { + params.audio_codec = acodecCombobox->currentData().toInt(); + params.audio_sampling_rate = samplingRateSpinbox->value(); + params.audio_bitrate = audiobitrateSpinbox->value(); + } + + params.start_frame = 0; + params.end_frame = sequence_->GetEndFrame(); // entire sequence + if (rangeCombobox->currentIndex() == 1) { + params.start_frame = qMax(sequence_->workarea_in, params.start_frame); + params.end_frame = qMin(sequence_->workarea_out, params.end_frame); + } + + // Create export thread + export_thread_ = new ExportThread(params, vcodec_params, this); + + // Connect export thread signals/slots + connect(export_thread_, SIGNAL(finished()), this, SLOT(export_thread_finished())); + connect(export_thread_, SIGNAL(ProgressChanged(int, qint64)), this, SLOT(update_progress_bar(int, qint64))); + connect(renderCancel, SIGNAL(clicked(bool)), export_thread_, SLOT(Interrupt())); + + // Close all effects in effect controls (prevents UI threading issues) + panel_effect_controls->Clear(); + + // Close all currently open clips + sequence_->Close(); + + olive::Global->set_export_state(true); + + olive::Global->save_autorecovery_file(); + + prep_ui_for_render(true); + + total_export_time_start = QDateTime::currentMSecsSinceEpoch(); + + export_thread_->start(); + } +} + +void ExportDialog::update_progress_bar(int value, qint64 remaining_ms) { + if (value == 100) { + // if value is 100%, show total render time rather than remaining + remaining_ms = QDateTime::currentMSecsSinceEpoch() - total_export_time_start; + } + + // convert ms to H:MM:SS + int seconds = qFloor(remaining_ms*0.001)%60; + int minutes = qFloor(remaining_ms/60000)%60; + int hours = qFloor(remaining_ms/3600000); + + if (value == 100) { + // show value as "total" + progressBar->setFormat(tr("%p% (Total: %1:%2:%3)").arg(QString::number(hours), + QString::number(minutes).rightJustified(2, '0'), + QString::number(seconds).rightJustified(2, '0'))); + } else { + // show value as "remaining" + progressBar->setFormat(tr("%p% (ETA: %1:%2:%3)").arg(QString::number(hours), + QString::number(minutes).rightJustified(2, '0'), + QString::number(seconds).rightJustified(2, '0'))); + } + + progressBar->setValue(value); +} + +void ExportDialog::vcodec_changed(int index) { + compressionTypeCombobox->clear(); + + if (vcodecCombobox->count() > 0) { + if (vcodecCombobox->itemData(index) == AV_CODEC_ID_H264 + || vcodecCombobox->itemData(index) == AV_CODEC_ID_H265) { + compressionTypeCombobox->setEnabled(true); + compressionTypeCombobox->addItem(tr("Quality-based (Constant Rate Factor)"), COMPRESSION_TYPE_CFR); + // compressionTypeCombobox->addItem("File size-based (Two-Pass)", COMPRESSION_TYPE_TARGETSIZE); + // compressionTypeCombobox->addItem("Average bitrate (Two-Pass)", COMPRESSION_TYPE_TARGETBR); + } else { + compressionTypeCombobox->addItem(tr("Constant Bitrate"), COMPRESSION_TYPE_CBR); + compressionTypeCombobox->setCurrentIndex(0); + compressionTypeCombobox->setEnabled(false); + } + + // set default pix_fmt for this codec + AVCodec* codec_info = avcodec_find_encoder(static_cast(vcodecCombobox->itemData(index).toInt())); + if (codec_info == nullptr) { + QMessageBox::critical(this, + tr("Invalid Codec"), + tr("Failed to find a suitable encoder for this codec. Export will likely fail.")); + } else { + vcodec_params.pix_fmt = codec_info->pix_fmts[0]; + if (vcodec_params.pix_fmt == -1) { + QMessageBox::critical(this, + tr("Invalid Codec"), + tr("Failed to find pixel format for this encoder. Export will likely fail.")); + } + } + } +} + +void ExportDialog::comp_type_changed(int) { + videobitrateSpinbox->setToolTip(""); + videobitrateSpinbox->setMinimum(0); + videobitrateSpinbox->setMaximum(99.99); + switch (compressionTypeCombobox->currentData().toInt()) { + case COMPRESSION_TYPE_CBR: + case COMPRESSION_TYPE_TARGETBR: + videoBitrateLabel->setText(tr("Bitrate (Mbps):")); + 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(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; + case COMPRESSION_TYPE_TARGETSIZE: + videoBitrateLabel->setText(tr("Target File Size (MB):")); + videobitrateSpinbox->setValue(100); + break; + } +} + +void ExportDialog::open_advanced_video_dialog() { + AdvancedVideoDialog avd(this, static_cast(vcodecCombobox->currentData().toInt()), vcodec_params); + avd.exec(); +} + +void ExportDialog::setup_ui() { + QVBoxLayout* verticalLayout = new QVBoxLayout(this); + + QHBoxLayout* format_layout = new QHBoxLayout(); + + format_layout->addWidget(new QLabel(tr("Format:"), this)); + + formatCombobox = new QComboBox(); + format_layout->addWidget(formatCombobox); + + verticalLayout->addLayout(format_layout); + + QHBoxLayout* range_layout = new QHBoxLayout(); + + range_layout->addWidget(new QLabel(tr("Range:"), this)); + + rangeCombobox = new QComboBox(this); + rangeCombobox->addItem(tr("Entire Sequence")); + rangeCombobox->addItem(tr("In to Out")); + + range_layout->addWidget(rangeCombobox); + + verticalLayout->addLayout(range_layout); + + videoGroupbox = new QGroupBox(this); + videoGroupbox->setTitle(tr("Video")); + videoGroupbox->setFlat(false); + videoGroupbox->setCheckable(true); + + QGridLayout* videoGridLayout = new QGridLayout(videoGroupbox); + + videoGridLayout->addWidget(new QLabel(tr("Codec:"), this), 0, 0, 1, 1); + vcodecCombobox = new QComboBox(videoGroupbox); + videoGridLayout->addWidget(vcodecCombobox, 0, 1, 1, 1); + + videoGridLayout->addWidget(new QLabel(tr("Width:"), this), 1, 0, 1, 1); + widthSpinbox = new QSpinBox(videoGroupbox); + widthSpinbox->setMaximum(16777216); + videoGridLayout->addWidget(widthSpinbox, 1, 1, 1, 1); + + videoGridLayout->addWidget(new QLabel(tr("Height:"), this), 2, 0, 1, 1); + heightSpinbox = new QSpinBox(videoGroupbox); + heightSpinbox->setMaximum(16777216); + videoGridLayout->addWidget(heightSpinbox, 2, 1, 1, 1); + + videoGridLayout->addWidget(new QLabel(tr("Frame Rate:"), this), 3, 0, 1, 1); + framerateSpinbox = new QDoubleSpinBox(videoGroupbox); + framerateSpinbox->setMaximum(60); + framerateSpinbox->setValue(0); + videoGridLayout->addWidget(framerateSpinbox, 3, 1, 1, 1); + + videoGridLayout->addWidget(new QLabel(tr("Compression Type:"), this), 4, 0, 1, 1); + compressionTypeCombobox = new QComboBox(videoGroupbox); + videoGridLayout->addWidget(compressionTypeCombobox, 4, 1, 1, 1); + + videoBitrateLabel = new QLabel(videoGroupbox); + videoGridLayout->addWidget(videoBitrateLabel, 5, 0, 1, 1); + videobitrateSpinbox = new QDoubleSpinBox(videoGroupbox); + videobitrateSpinbox->setMaximum(100); + videobitrateSpinbox->setValue(2); + videoGridLayout->addWidget(videobitrateSpinbox, 5, 1, 1, 1); + + QPushButton* advanced_video_button = new QPushButton(tr("Advanced")); + connect(advanced_video_button, SIGNAL(clicked(bool)), this, SLOT(open_advanced_video_dialog())); + videoGridLayout->addWidget(advanced_video_button, 6, 1); + + verticalLayout->addWidget(videoGroupbox); + + audioGroupbox = new QGroupBox(this); + audioGroupbox->setTitle(tr("Audio")); + audioGroupbox->setCheckable(true); + + QGridLayout* audioGridLayout = new QGridLayout(audioGroupbox); + + audioGridLayout->addWidget(new QLabel(tr("Codec:"), this), 0, 0, 1, 1); + acodecCombobox = new QComboBox(audioGroupbox); + audioGridLayout->addWidget(acodecCombobox, 0, 1, 1, 1); + + audioGridLayout->addWidget(new QLabel(tr("Sampling Rate:"), this), 1, 0, 1, 1); + samplingRateSpinbox = new QSpinBox(audioGroupbox); + samplingRateSpinbox->setMaximum(96000); + samplingRateSpinbox->setValue(0); + audioGridLayout->addWidget(samplingRateSpinbox, 1, 1, 1, 1); + + audioGridLayout->addWidget(new QLabel(tr("Bitrate (Kbps/CBR):"), this), 3, 0, 1, 1); + audiobitrateSpinbox = new QSpinBox(audioGroupbox); + audiobitrateSpinbox->setMaximum(320); + audiobitrateSpinbox->setValue(256); + audioGridLayout->addWidget(audiobitrateSpinbox, 3, 1, 1, 1); + + verticalLayout->addWidget(audioGroupbox); + + QHBoxLayout* progressLayout = new QHBoxLayout(); + progressBar = new QProgressBar(this); + progressBar->setFormat("%p% (ETA: 0:00:00)"); + progressBar->setEnabled(false); + progressBar->setValue(0); + progressLayout->addWidget(progressBar); + + renderCancel = new QPushButton(this); + renderCancel->setIcon(QIcon(":/icons/error.svg")); + renderCancel->setEnabled(false); + progressLayout->addWidget(renderCancel); + + verticalLayout->addLayout(progressLayout); + + QHBoxLayout* buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(); + + export_button = new QPushButton(this); + export_button->setText("Export"); + connect(export_button, SIGNAL(clicked(bool)), this, SLOT(StartExport())); + + buttonLayout->addWidget(export_button); + + cancel_button = new QPushButton(this); + cancel_button->setText("Cancel"); + connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); + + buttonLayout->addWidget(cancel_button); + + buttonLayout->addStretch(); + + verticalLayout->addLayout(buttonLayout); + + connect(formatCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(format_changed(int))); + connect(compressionTypeCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(comp_type_changed(int))); + connect(vcodecCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(vcodec_changed(int))); +} diff --git a/dialogs/exportdialog.h b/dialogs/exportdialog.h index d76d359f3..d24e57e18 100644 --- a/dialogs/exportdialog.h +++ b/dialogs/exportdialog.h @@ -1,277 +1,277 @@ -/*** - - 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 EXPORTDIALOG_H -#define EXPORTDIALOG_H - -#include -#include -#include -#include -#include -#include -#include - -#include "timeline/sequence.h" -#include "rendering/exportthread.h" - -/** - * @brief The ExportDialog class - * - * The dialog to initiate an export. Requires a valid Sequence to be set in olive::ActiveSequence or the result is - * defined (most likely a crash), so you should always do a `nullptr` check on olive::ActiveSequence before constructing - * this dialog. - */ -class ExportDialog : public QDialog -{ - Q_OBJECT -public: - /** - * @brief ExportDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow. - */ - explicit ExportDialog(QWidget *parent, Sequence *sequence); - -private slots: - /** - * @brief Slot for when the user changes the format - * - * Used to populate the available codecs list for this format. - * - * @param index - * - * Current format index (corresponding to enum ExportFormats) - */ - void format_changed(int index); - - /** - * @brief Slot for when the user clicks the Export button - * - * Asks the user for the file to save to. - */ - void StartExport(); - - /** - * @brief Slot for the export thread to update the progress bar's value - * - * @param value - * - * An value between 0 - 100. A percentage of the Sequence that has been exported so far. - * - * @param remaining_ms - * - * The estimated time in milliseconds that it will take to complete the rest of the Sequence. - */ - void update_progress_bar(int value, qint64 remaining_ms); - - /** - * @brief Slot for the export thread completing (both succeeding and failing) - * - * Runs whenever the thread has finished. Determines whether the thread succeeded or not (and shows an error message - * if not), cleans up the ExportThread object, sets the UI state back to normal. - * - * Connect to ExportThread::finished(). - */ - void export_thread_finished(); - - /** - * @brief Slot for when the video codec changes - * - * Some video codecs require different settings. In the case of that, this function sorts through those. - * - * @param index - * - * Current vcodecCombobox index - its item data contains the AVCodecID. - */ - void vcodec_changed(int index); - - /** - * @brief Slot for when the compression type changes - * - * Different UI objects should be displayed for different compression types. - * - * @param index - * - * Unused. - */ - void comp_type_changed(int index); - - /** - * @brief Slot to open the Advanced Video Dialog - * - * Opens a dialog for setting more advanced video settings and passes a reference to vcodec_params to it. - */ - void open_advanced_video_dialog(); - -private: - /** - * @brief Function to create UI objects. - */ - void setup_ui(); - - /** - * @brief Enables/disables certain UI objects based on the exporting state. - * - * Some UI controls don't need to be set while exporting. This function enables/disables them appropriately. - * - * @param r - * - * TRUE if we're exporting, FALSE if we finished. - */ - void prep_ui_for_render(bool r); - - /** - * @brief Retrieves the human-readable name of an AVCodecID and adds it to a QComboBox - * - * Also sets that item's data to the AVCodecID so it can be retrieved directly from the QComboBox. - * - * @param box - * - * The QComboBox to add the item to. - * - * @param codec - * - * The codec to add to the QComboBox. - */ - void add_codec_to_combobox(QComboBox* box, enum AVCodecID codec); - - /** - * @brief Internal array of human-readable names corresponding to enum ExportFormats - */ - QVector format_strings; - - /** - * @brief Pointer to an ExportThread - * - * Set when exporting starts, and deleted by export_thread_finished() when the thread is complete. - */ - ExportThread* export_thread_; - - /** - * @brief Struct for advanced video codec parameters. - * - * More advanced video encoding parameters to be sent to the ExportThread. These variables are not directly editable - * in this dialog, instead calling open_advanced_video_dialog() will open an AdvancedVideoDialog for setting these - * values directly. vcodec_changed() should also set these to the defaults for that codec where appropriate. - */ - VideoCodecParams vcodec_params; - - /** - * @brief ComboBox for selecting the time range of the Sequence to export - */ - QComboBox* rangeCombobox; - - /** - * @brief SpinBox for the exported video's width - */ - QSpinBox* widthSpinbox; - - /** - * @brief SpinBox for the exported video's bitrate - */ - QDoubleSpinBox* videobitrateSpinbox; - - /** - * @brief Label for the exported video's bitrate - changes depending on the compression type - */ - QLabel* videoBitrateLabel; - - /** - * @brief SpinBox for the exported video's frame rate - */ - QDoubleSpinBox* framerateSpinbox; - - /** - * @brief ComboBox for the exported video codec - */ - QComboBox* vcodecCombobox; - - /** - * @brief ComboBox for the exported audio's codec - */ - QComboBox* acodecCombobox; - - /** - * @brief SpinBox for the exported audio's sample rate - */ - QSpinBox* samplingRateSpinbox; - - /** - * @brief SpinBox for the exported audio's bitrate - */ - QSpinBox* audiobitrateSpinbox; - - /** - * @brief Progress bar for visually showing the export progress - */ - QProgressBar* progressBar; - - /** - * @brief ComboBox for the exported video's format - */ - QComboBox* formatCombobox; - - /** - * @brief SpinBox for the exported video's height - */ - QSpinBox* heightSpinbox; - - /** - * @brief Export button to trigger the start of an export - */ - QPushButton* export_button; - - /** - * @brief Dialog cancel button to close this dialog - */ - QPushButton* cancel_button; - - /** - * @brief Cancel button to abort the export before completion - */ - QPushButton* renderCancel; - - /** - * @brief GroupBox containing all video-related UI objects - */ - QGroupBox* videoGroupbox; - - /** - * @brief GroupBox containing all audio-related UI objects - */ - QGroupBox* audioGroupbox; - - /** - * @brief ComboBox for the exported video compression type - */ - QComboBox* compressionTypeCombobox; - - /** - * @brief Time value set when exporting begins to determine the total duration of the export - */ - qint64 total_export_time_start; - - Sequence* sequence_; -}; - -#endif // EXPORTDIALOG_H +/*** + + 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 EXPORTDIALOG_H +#define EXPORTDIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +#include "timeline/sequence.h" +#include "rendering/exportthread.h" + +/** + * @brief The ExportDialog class + * + * The dialog to initiate an export. Requires a valid Sequence to be set in olive::ActiveSequence or the result is + * defined (most likely a crash), so you should always do a `nullptr` check on olive::ActiveSequence before constructing + * this dialog. + */ +class ExportDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @brief ExportDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow. + */ + explicit ExportDialog(QWidget *parent, Sequence *sequence); + +private slots: + /** + * @brief Slot for when the user changes the format + * + * Used to populate the available codecs list for this format. + * + * @param index + * + * Current format index (corresponding to enum ExportFormats) + */ + void format_changed(int index); + + /** + * @brief Slot for when the user clicks the Export button + * + * Asks the user for the file to save to. + */ + void StartExport(); + + /** + * @brief Slot for the export thread to update the progress bar's value + * + * @param value + * + * An value between 0 - 100. A percentage of the Sequence that has been exported so far. + * + * @param remaining_ms + * + * The estimated time in milliseconds that it will take to complete the rest of the Sequence. + */ + void update_progress_bar(int value, qint64 remaining_ms); + + /** + * @brief Slot for the export thread completing (both succeeding and failing) + * + * Runs whenever the thread has finished. Determines whether the thread succeeded or not (and shows an error message + * if not), cleans up the ExportThread object, sets the UI state back to normal. + * + * Connect to ExportThread::finished(). + */ + void export_thread_finished(); + + /** + * @brief Slot for when the video codec changes + * + * Some video codecs require different settings. In the case of that, this function sorts through those. + * + * @param index + * + * Current vcodecCombobox index - its item data contains the AVCodecID. + */ + void vcodec_changed(int index); + + /** + * @brief Slot for when the compression type changes + * + * Different UI objects should be displayed for different compression types. + * + * @param index + * + * Unused. + */ + void comp_type_changed(int index); + + /** + * @brief Slot to open the Advanced Video Dialog + * + * Opens a dialog for setting more advanced video settings and passes a reference to vcodec_params to it. + */ + void open_advanced_video_dialog(); + +private: + /** + * @brief Function to create UI objects. + */ + void setup_ui(); + + /** + * @brief Enables/disables certain UI objects based on the exporting state. + * + * Some UI controls don't need to be set while exporting. This function enables/disables them appropriately. + * + * @param r + * + * TRUE if we're exporting, FALSE if we finished. + */ + void prep_ui_for_render(bool r); + + /** + * @brief Retrieves the human-readable name of an AVCodecID and adds it to a QComboBox + * + * Also sets that item's data to the AVCodecID so it can be retrieved directly from the QComboBox. + * + * @param box + * + * The QComboBox to add the item to. + * + * @param codec + * + * The codec to add to the QComboBox. + */ + void add_codec_to_combobox(QComboBox* box, enum AVCodecID codec); + + /** + * @brief Internal array of human-readable names corresponding to enum ExportFormats + */ + QVector format_strings; + + /** + * @brief Pointer to an ExportThread + * + * Set when exporting starts, and deleted by export_thread_finished() when the thread is complete. + */ + ExportThread* export_thread_; + + /** + * @brief Struct for advanced video codec parameters. + * + * More advanced video encoding parameters to be sent to the ExportThread. These variables are not directly editable + * in this dialog, instead calling open_advanced_video_dialog() will open an AdvancedVideoDialog for setting these + * values directly. vcodec_changed() should also set these to the defaults for that codec where appropriate. + */ + VideoCodecParams vcodec_params; + + /** + * @brief ComboBox for selecting the time range of the Sequence to export + */ + QComboBox* rangeCombobox; + + /** + * @brief SpinBox for the exported video's width + */ + QSpinBox* widthSpinbox; + + /** + * @brief SpinBox for the exported video's bitrate + */ + QDoubleSpinBox* videobitrateSpinbox; + + /** + * @brief Label for the exported video's bitrate - changes depending on the compression type + */ + QLabel* videoBitrateLabel; + + /** + * @brief SpinBox for the exported video's frame rate + */ + QDoubleSpinBox* framerateSpinbox; + + /** + * @brief ComboBox for the exported video codec + */ + QComboBox* vcodecCombobox; + + /** + * @brief ComboBox for the exported audio's codec + */ + QComboBox* acodecCombobox; + + /** + * @brief SpinBox for the exported audio's sample rate + */ + QSpinBox* samplingRateSpinbox; + + /** + * @brief SpinBox for the exported audio's bitrate + */ + QSpinBox* audiobitrateSpinbox; + + /** + * @brief Progress bar for visually showing the export progress + */ + QProgressBar* progressBar; + + /** + * @brief ComboBox for the exported video's format + */ + QComboBox* formatCombobox; + + /** + * @brief SpinBox for the exported video's height + */ + QSpinBox* heightSpinbox; + + /** + * @brief Export button to trigger the start of an export + */ + QPushButton* export_button; + + /** + * @brief Dialog cancel button to close this dialog + */ + QPushButton* cancel_button; + + /** + * @brief Cancel button to abort the export before completion + */ + QPushButton* renderCancel; + + /** + * @brief GroupBox containing all video-related UI objects + */ + QGroupBox* videoGroupbox; + + /** + * @brief GroupBox containing all audio-related UI objects + */ + QGroupBox* audioGroupbox; + + /** + * @brief ComboBox for the exported video compression type + */ + QComboBox* compressionTypeCombobox; + + /** + * @brief Time value set when exporting begins to determine the total duration of the export + */ + qint64 total_export_time_start; + + Sequence* sequence_; +}; + +#endif // EXPORTDIALOG_H diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 198ca3651..cee5d64e4 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -1,63 +1,63 @@ -/*** - - 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 "loaddialog.h" - -#include -#include -#include - -#include "global/global.h" - -#include "panels/panels.h" - -#include "ui/sourcetable.h" -#include "ui/mainwindow.h" - -LoadDialog::LoadDialog(QWidget *parent) : - QDialog(parent) -{ - setWindowTitle(tr("Loading...")); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - QVBoxLayout* layout = new QVBoxLayout(this); - - layout->addWidget(new QLabel(tr("Loading '%1'...").arg(olive::ActiveProjectFilename.mid(olive::ActiveProjectFilename.lastIndexOf('/')+1)), this)); - - bar = new QProgressBar(this); - bar->setValue(0); - layout->addWidget(bar); - - QPushButton* cancel_button = new QPushButton(tr("Cancel"), this); - connect(cancel_button, SIGNAL(clicked(bool)), this, SIGNAL(cancel())); - - // Wrap cancel button in a horizontal layout so it can be centered - QHBoxLayout* hboxLayout = new QHBoxLayout(); - hboxLayout->addStretch(); - hboxLayout->addWidget(cancel_button); - hboxLayout->addStretch(); - - layout->addLayout(hboxLayout); -} - -void LoadDialog::setValue(int i) -{ - bar->setValue(i); -} +/*** + + 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 "loaddialog.h" + +#include +#include +#include + +#include "global/global.h" + +#include "panels/panels.h" + +#include "ui/sourcetable.h" +#include "ui/mainwindow.h" + +LoadDialog::LoadDialog(QWidget *parent) : + QDialog(parent) +{ + setWindowTitle(tr("Loading...")); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QVBoxLayout* layout = new QVBoxLayout(this); + + layout->addWidget(new QLabel(tr("Loading '%1'...").arg(olive::ActiveProjectFilename.mid(olive::ActiveProjectFilename.lastIndexOf('/')+1)), this)); + + bar = new QProgressBar(this); + bar->setValue(0); + layout->addWidget(bar); + + QPushButton* cancel_button = new QPushButton(tr("Cancel"), this); + connect(cancel_button, SIGNAL(clicked(bool)), this, SIGNAL(cancel())); + + // Wrap cancel button in a horizontal layout so it can be centered + QHBoxLayout* hboxLayout = new QHBoxLayout(); + hboxLayout->addStretch(); + hboxLayout->addWidget(cancel_button); + hboxLayout->addStretch(); + + layout->addLayout(hboxLayout); +} + +void LoadDialog::setValue(int i) +{ + bar->setValue(i); +} diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index 24acca8af..f9b10676c 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -1,76 +1,76 @@ -/*** - - 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 LOADDIALOG_H -#define LOADDIALOG_H - -#include -#include -#include - -#include "project/projectelements.h" -#include "project/loadthread.h" - -/** - * @brief The LoadDialog class - * - * Shows a modal dialog for loading a project. Designed to be connected to a LoadThread object. This dialog should - * generally not be created directly, use OliveGlobal::LoadProject (or its variants) to correctly set up a LoadDialog - * and LoadThread and connect them to each other. - */ -class LoadDialog : public QDialog -{ - Q_OBJECT -public: - /** - * @brief LoadDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow. - */ - LoadDialog(QWidget* parent); - -public slots: - /** - * @brief Set the progress bar value - * - * Ideally, connect this to LoadThread::report_progress(). - * - * @param i - * - * Should be a value between 0-100. - */ - void setValue(int i); -signals: - /** - * @brief Signal emitted when the cancel button is clicked. - * - * Ideally, connect this to LoadThread::cancel(); - */ - void cancel(); -private: - /** - * @brief Progress bar widget - */ - QProgressBar* bar; -}; - -#endif // LOADDIALOG_H +/*** + + 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 LOADDIALOG_H +#define LOADDIALOG_H + +#include +#include +#include + +#include "project/projectelements.h" +#include "project/loadthread.h" + +/** + * @brief The LoadDialog class + * + * Shows a modal dialog for loading a project. Designed to be connected to a LoadThread object. This dialog should + * generally not be created directly, use OliveGlobal::LoadProject (or its variants) to correctly set up a LoadDialog + * and LoadThread and connect them to each other. + */ +class LoadDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @brief LoadDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow. + */ + LoadDialog(QWidget* parent); + +public slots: + /** + * @brief Set the progress bar value + * + * Ideally, connect this to LoadThread::report_progress(). + * + * @param i + * + * Should be a value between 0-100. + */ + void setValue(int i); +signals: + /** + * @brief Signal emitted when the cancel button is clicked. + * + * Ideally, connect this to LoadThread::cancel(); + */ + void cancel(); +private: + /** + * @brief Progress bar widget + */ + QProgressBar* bar; +}; + +#endif // LOADDIALOG_H diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 01154ffb6..060d9192b 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -1,238 +1,238 @@ -/*** - - 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 "mediapropertiesdialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -namespace OCIO = OCIO_NAMESPACE::v1; - -#include "project/footage.h" -#include "project/media.h" -#include "panels/project.h" -#include "undo/undo.h" -#include "undo/undostack.h" - -MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : - QDialog(parent), - item(i) -{ - setWindowTitle(tr("\"%1\" Properties").arg(i->get_name())); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - QGridLayout* grid = new QGridLayout(this); - - int row = 0; - - Footage* f = item->to_footage(); - - grid->addWidget(new QLabel(tr("Tracks:"), this), row, 0, 1, 2); - row++; - - track_list = new QListWidget(this); - for (int i=0;ivideo_tracks.size();i++) { - const FootageStream& fs = f->video_tracks.at(i); - - QListWidgetItem* item = new QListWidgetItem( - tr("Video %1: %2x%3 %4FPS").arg( - QString::number(fs.file_index), - QString::number(fs.video_width), - QString::number(fs.video_height), - QString::number(fs.video_frame_rate) - ), - track_list - ); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); - item->setData(Qt::UserRole+1, fs.file_index); - track_list->addItem(item); - } - for (int i=0;iaudio_tracks.size();i++) { - const FootageStream& fs = f->audio_tracks.at(i); - QListWidgetItem* item = new QListWidgetItem( - tr("Audio %1: %2Hz %3").arg( - QString::number(fs.file_index), - QString::number(fs.audio_frequency), - tr("%n channel(s)", "", fs.audio_channels) - ), - track_list - ); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); - item->setData(Qt::UserRole+1, fs.file_index); - track_list->addItem(item); - } - grid->addWidget(track_list, row, 0, 1, 2); - row++; - - if (f->video_tracks.size() > 0) { - // frame conforming - if (!f->video_tracks.at(0).infinite_length) { - grid->addWidget(new QLabel(tr("Conform to Frame Rate:"), this), row, 0); - conform_fr = new QDoubleSpinBox(this); - conform_fr->setMinimum(0.01); - conform_fr->setValue(f->video_tracks.at(0).video_frame_rate * f->speed); - grid->addWidget(conform_fr, row, 1); - } - - row++; - - // premultiplied alpha mode - premultiply_alpha_setting = new QCheckBox(tr("Alpha is Premultiplied"), this); - premultiply_alpha_setting->setChecked(f->alpha_is_associated); - grid->addWidget(premultiply_alpha_setting, row, 0); - - row++; - - // deinterlacing mode - interlacing_box = new QComboBox(this); - interlacing_box->addItem( - tr("Auto (%1)").arg( - Footage::get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) - ) - ); - 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) - ? 0 - : f->video_tracks.at(0).video_interlacing + 1); - - grid->addWidget(new QLabel(tr("Interlacing:"), this), row, 0); - 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); - grid->addWidget(new QLabel(tr("Name:"), this), row, 0); - grid->addWidget(name_box, row, 1); - row++; - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - buttons->setCenterButtons(true); - grid->addWidget(buttons, row, 0, 1, 2); - - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); -} - -void MediaPropertiesDialog::accept() { - Footage* f = item->to_footage(); - - ComboAction* ca = new ComboAction(); - - // set track enable - for (int i=0;icount();i++) { - QListWidgetItem* item = track_list->item(i); - const QVariant& data = item->data(Qt::UserRole+1); - if (!data.isNull()) { - int index = data.toInt(); - bool found = false; - for (int j=0;jvideo_tracks.size();j++) { - if (f->video_tracks.at(j).file_index == index) { - f->video_tracks[j].enabled = (item->checkState() == Qt::Checked); - found = true; - break; - } - } - if (!found) { - for (int j=0;jaudio_tracks.size();j++) { - if (f->audio_tracks.at(j).file_index == index) { - f->audio_tracks[j].enabled = (item->checkState() == Qt::Checked); - break; - } - } - } - } - } - - bool refresh_clips = false; - - // set interlacing - if (f->video_tracks.size() > 0) { - if (interlacing_box->currentIndex() > 0) { - ca->append(new SetInt(&f->video_tracks[0].video_interlacing, interlacing_box->currentIndex() - 1)); - } else { - ca->append(new SetInt(&f->video_tracks[0].video_interlacing, f->video_tracks.at(0).video_auto_interlacing)); - } - - // set frame rate conform - if (!f->video_tracks.at(0).infinite_length) { - if (!qFuzzyCompare(conform_fr->value(), f->video_tracks.at(0).video_frame_rate)) { - ca->append(new SetDouble(&f->speed, f->speed, conform_fr->value()/f->video_tracks.at(0).video_frame_rate)); - refresh_clips = true; - } - } - - // set premultiplied alpha - f->alpha_is_associated = premultiply_alpha_setting->isChecked(); - } - - f->SetColorspace(input_color_space->currentText()); - - // set name - MediaRename* mr = new MediaRename(item, name_box->text()); - - ca->append(mr); - ca->appendPost(new CloseAllClipsCommand()); - ca->appendPost(new UpdateFootageTooltip(item)); - if (refresh_clips) { - ca->appendPost(new RefreshClips(item)); - } - ca->appendPost(new UpdateViewer()); - - olive::undo_stack.push(ca); - - QDialog::accept(); -} +/*** + + 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 "mediapropertiesdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace OCIO = OCIO_NAMESPACE::v1; + +#include "project/footage.h" +#include "project/media.h" +#include "panels/project.h" +#include "undo/undo.h" +#include "undo/undostack.h" + +MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : + QDialog(parent), + item(i) +{ + setWindowTitle(tr("\"%1\" Properties").arg(i->get_name())); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QGridLayout* grid = new QGridLayout(this); + + int row = 0; + + Footage* f = item->to_footage(); + + grid->addWidget(new QLabel(tr("Tracks:"), this), row, 0, 1, 2); + row++; + + track_list = new QListWidget(this); + for (int i=0;ivideo_tracks.size();i++) { + const FootageStream& fs = f->video_tracks.at(i); + + QListWidgetItem* item = new QListWidgetItem( + tr("Video %1: %2x%3 %4FPS").arg( + QString::number(fs.file_index), + QString::number(fs.video_width), + QString::number(fs.video_height), + QString::number(fs.video_frame_rate) + ), + track_list + ); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole+1, fs.file_index); + track_list->addItem(item); + } + for (int i=0;iaudio_tracks.size();i++) { + const FootageStream& fs = f->audio_tracks.at(i); + QListWidgetItem* item = new QListWidgetItem( + tr("Audio %1: %2Hz %3").arg( + QString::number(fs.file_index), + QString::number(fs.audio_frequency), + tr("%n channel(s)", "", fs.audio_channels) + ), + track_list + ); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole+1, fs.file_index); + track_list->addItem(item); + } + grid->addWidget(track_list, row, 0, 1, 2); + row++; + + if (f->video_tracks.size() > 0) { + // frame conforming + if (!f->video_tracks.at(0).infinite_length) { + grid->addWidget(new QLabel(tr("Conform to Frame Rate:"), this), row, 0); + conform_fr = new QDoubleSpinBox(this); + conform_fr->setMinimum(0.01); + conform_fr->setValue(f->video_tracks.at(0).video_frame_rate * f->speed); + grid->addWidget(conform_fr, row, 1); + } + + row++; + + // premultiplied alpha mode + premultiply_alpha_setting = new QCheckBox(tr("Alpha is Premultiplied"), this); + premultiply_alpha_setting->setChecked(f->alpha_is_associated); + grid->addWidget(premultiply_alpha_setting, row, 0); + + row++; + + // deinterlacing mode + interlacing_box = new QComboBox(this); + interlacing_box->addItem( + tr("Auto (%1)").arg( + Footage::get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) + ) + ); + 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) + ? 0 + : f->video_tracks.at(0).video_interlacing + 1); + + grid->addWidget(new QLabel(tr("Interlacing:"), this), row, 0); + 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); + grid->addWidget(new QLabel(tr("Name:"), this), row, 0); + grid->addWidget(name_box, row, 1); + row++; + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttons->setCenterButtons(true); + grid->addWidget(buttons, row, 0, 1, 2); + + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); +} + +void MediaPropertiesDialog::accept() { + Footage* f = item->to_footage(); + + ComboAction* ca = new ComboAction(); + + // set track enable + for (int i=0;icount();i++) { + QListWidgetItem* item = track_list->item(i); + const QVariant& data = item->data(Qt::UserRole+1); + if (!data.isNull()) { + int index = data.toInt(); + bool found = false; + for (int j=0;jvideo_tracks.size();j++) { + if (f->video_tracks.at(j).file_index == index) { + f->video_tracks[j].enabled = (item->checkState() == Qt::Checked); + found = true; + break; + } + } + if (!found) { + for (int j=0;jaudio_tracks.size();j++) { + if (f->audio_tracks.at(j).file_index == index) { + f->audio_tracks[j].enabled = (item->checkState() == Qt::Checked); + break; + } + } + } + } + } + + bool refresh_clips = false; + + // set interlacing + if (f->video_tracks.size() > 0) { + if (interlacing_box->currentIndex() > 0) { + ca->append(new SetInt(&f->video_tracks[0].video_interlacing, interlacing_box->currentIndex() - 1)); + } else { + ca->append(new SetInt(&f->video_tracks[0].video_interlacing, f->video_tracks.at(0).video_auto_interlacing)); + } + + // set frame rate conform + if (!f->video_tracks.at(0).infinite_length) { + if (!qFuzzyCompare(conform_fr->value(), f->video_tracks.at(0).video_frame_rate)) { + ca->append(new SetDouble(&f->speed, f->speed, conform_fr->value()/f->video_tracks.at(0).video_frame_rate)); + refresh_clips = true; + } + } + + // set premultiplied alpha + f->alpha_is_associated = premultiply_alpha_setting->isChecked(); + } + + f->SetColorspace(input_color_space->currentText()); + + // set name + MediaRename* mr = new MediaRename(item, name_box->text()); + + ca->append(mr); + ca->appendPost(new CloseAllClipsCommand()); + ca->appendPost(new UpdateFootageTooltip(item)); + if (refresh_clips) { + ca->appendPost(new RefreshClips(item)); + } + ca->appendPost(new UpdateViewer()); + + olive::undo_stack.push(ca); + + QDialog::accept(); +} diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index 6e964e6ed..5e86b5832 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -1,97 +1,97 @@ -/*** - - 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 MEDIAPROPERTIESDIALOG_H -#define MEDIAPROPERTIESDIALOG_H - -#include -#include -#include -#include -#include -#include - -#include "project/footage.h" -#include "project/media.h" - -/** - * @brief The MediaPropertiesDialog class - * - * A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given - * a valid Media object. - */ -class MediaPropertiesDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief MediaPropertiesDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow or Project panel. - * - * @param i - * - * Media object to set properties for. - */ - MediaPropertiesDialog(QWidget *parent, Media* i); -private: - /** - * @brief ComboBox for interlacing setting - */ - QComboBox* interlacing_box; - - /** - * @brief Media name text field - */ - QLineEdit* name_box; - - /** - * @brief Internal pointer to Media object (set in constructor) - */ - Media* item; - - /** - * @brief A list widget for listing the tracks in Media - */ - QListWidget* track_list; - - /** - * @brief Frame rate to conform to - */ - QDoubleSpinBox* conform_fr; - - /** - * @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 - */ - void accept(); -}; - -#endif // MEDIAPROPERTIESDIALOG_H +/*** + + 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 MEDIAPROPERTIESDIALOG_H +#define MEDIAPROPERTIESDIALOG_H + +#include +#include +#include +#include +#include +#include + +#include "project/footage.h" +#include "project/media.h" + +/** + * @brief The MediaPropertiesDialog class + * + * A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given + * a valid Media object. + */ +class MediaPropertiesDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief MediaPropertiesDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow or Project panel. + * + * @param i + * + * Media object to set properties for. + */ + MediaPropertiesDialog(QWidget *parent, Media* i); +private: + /** + * @brief ComboBox for interlacing setting + */ + QComboBox* interlacing_box; + + /** + * @brief Media name text field + */ + QLineEdit* name_box; + + /** + * @brief Internal pointer to Media object (set in constructor) + */ + Media* item; + + /** + * @brief A list widget for listing the tracks in Media + */ + QListWidget* track_list; + + /** + * @brief Frame rate to conform to + */ + QDoubleSpinBox* conform_fr; + + /** + * @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 + */ + void accept(); +}; + +#endif // MEDIAPROPERTIESDIALOG_H diff --git a/dialogs/newsequencedialog.cpp b/dialogs/newsequencedialog.cpp index 0c3884ea4..8d9150862 100644 --- a/dialogs/newsequencedialog.cpp +++ b/dialogs/newsequencedialog.cpp @@ -1,323 +1,323 @@ -/*** - - 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 "newsequencedialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "panels/panels.h" -#include "panels/project.h" -#include "timeline/sequence.h" -#include "undo/undostack.h" -#include "undo/undo.h" -#include "timeline/clip.h" -#include "panels/timeline.h" -#include "project/media.h" -#include "rendering/audio.h" -#include "global/config.h" - -// FIXME: TEST CODE -#include "nodes/nodes/nodemedia.h" -// END TEST CODE - -extern "C" { -#include -} - -NewSequenceDialog::NewSequenceDialog(QWidget *parent, Media *existing, Sequence* iexisting_sequence) : - QDialog(parent), - existing_item(existing), - existing_sequence(iexisting_sequence) -{ - Q_ASSERT(!(existing != nullptr && iexisting_sequence != nullptr)); - - setup_ui(); - - if (existing != nullptr) { - existing_sequence = existing->to_sequence().get(); - } - - if (existing_sequence != nullptr) { - setWindowTitle(tr("Editing \"%1\"").arg(existing_sequence->name())); - - width_numeric->setValue(existing_sequence->width()); - height_numeric->setValue(existing_sequence->height()); - int comp_rate = qRound(existing_sequence->frame_rate()*100); - for (int i=0;icount();i++) { - if (qRound(frame_rate_combobox->itemData(i).toDouble()*100) == comp_rate) { - frame_rate_combobox->setCurrentIndex(i); - break; - } - } - sequence_name_edit->setText(existing_sequence->name()); - for (int i=0;icount();i++) { - if (audio_frequency_combobox->itemData(i) == existing_sequence->audio_frequency()) { - audio_frequency_combobox->setCurrentIndex(i); - break; - } - } - } else { - existing_sequence = nullptr; - setWindowTitle(tr("New Sequence")); - } -} - -void NewSequenceDialog::set_sequence_name(const QString& s) { - sequence_name_edit->setText(s); -} - -void NewSequenceDialog::SetNameEditable(bool enabled) -{ - sequence_name_edit->setVisible(enabled); - sequence_name_label->setVisible(enabled); -} - -void NewSequenceDialog::accept() { - if (existing_sequence == nullptr) { - - // The dialog wasn't given an existing Sequence object, so we'll make a new one - - SequencePtr s = std::make_shared(); - - s->set_name(sequence_name_edit->text()); - s->set_width(width_numeric->value()); - s->set_height(height_numeric->value()); - s->set_frame_rate(frame_rate_combobox->currentData().toDouble()); - s->set_audio_frequency(audio_frequency_combobox->currentData().toInt()); - s->set_audio_layout(AV_CH_LAYOUT_STEREO); - - ComboAction* ca = new ComboAction(); - olive::project_model.CreateSequence(ca, s, true, nullptr); - olive::undo_stack.push(ca); - - } else if (existing_item != nullptr) { - - // The dialog was given an existing Sequence object, so we'll apply the changes to it - - ComboAction* ca = new ComboAction(); - - double multiplier = frame_rate_combobox->currentData().toDouble() / existing_sequence->frame_rate(); - - EditSequenceCommand* esc = new EditSequenceCommand(existing_item, existing_item->to_sequence()); - esc->name = sequence_name_edit->text(); - esc->width = width_numeric->value(); - esc->height = height_numeric->value(); - esc->frame_rate = frame_rate_combobox->currentData().toDouble(); - esc->audio_frequency = audio_frequency_combobox->currentData().toInt(); - esc->audio_layout = AV_CH_LAYOUT_STEREO; - ca->append(esc); - - QVector existing_sequence_clips = existing_sequence->GetAllClips(); - for (int i=0;irefactor_frame_rate(ca, multiplier, true); - } - - olive::undo_stack.push(ca); - - } else if (existing_sequence != nullptr) { - - // This dialog was given an existing Sequence without a Media wrapper - therefore just directly apply the settings - - existing_sequence->set_name(sequence_name_edit->text()); - existing_sequence->set_width(width_numeric->value()); - existing_sequence->set_height(height_numeric->value()); - existing_sequence->set_frame_rate(frame_rate_combobox->currentData().toDouble()); - existing_sequence->set_audio_frequency(audio_frequency_combobox->currentData().toInt()); - existing_sequence->set_audio_layout(AV_CH_LAYOUT_STEREO); - - } - - QDialog::accept(); -} - -void NewSequenceDialog::preset_changed(int index) { - switch (index) { - case 0: // FILM 4K - width_numeric->setValue(4096); - height_numeric->setValue(2160); - break; - case 1: // TV 4K - width_numeric->setValue(3840); - height_numeric->setValue(2160); - break; - case 2: // 1080p - width_numeric->setValue(1920); - height_numeric->setValue(1080); - break; - case 3: // 720p - width_numeric->setValue(1280); - height_numeric->setValue(720); - break; - case 4: // 480p - width_numeric->setValue(640); - height_numeric->setValue(480); - break; - case 5: // 360p - width_numeric->setValue(640); - height_numeric->setValue(360); - break; - case 6: // 240p - width_numeric->setValue(320); - height_numeric->setValue(240); - break; - case 7: // 144p - width_numeric->setValue(192); - height_numeric->setValue(144); - break; - case 8: // NTSC (480i) - width_numeric->setValue(720); - height_numeric->setValue(480); - break; - case 9: // PAL (576i) - width_numeric->setValue(720); - height_numeric->setValue(576); - break; - } -} - -void NewSequenceDialog::setup_ui() { - QVBoxLayout* verticalLayout = new QVBoxLayout(this); - - QWidget* widget = new QWidget(this); - - QHBoxLayout* preset_layout = new QHBoxLayout(widget); - preset_layout->setContentsMargins(0, 0, 0, 0); - - preset_layout->addWidget(new QLabel(tr("Preset:"), this)); - - preset_combobox = new QComboBox(widget); - - preset_combobox->addItem(tr("Film 4K")); - preset_combobox->addItem(tr("TV 4K (Ultra HD/2160p)")); - preset_combobox->addItem(tr("1080p")); - preset_combobox->addItem(tr("720p")); - preset_combobox->addItem(tr("480p")); - preset_combobox->addItem(tr("360p")); - preset_combobox->addItem(tr("240p")); - preset_combobox->addItem(tr("144p")); - preset_combobox->addItem(tr("NTSC (480i)")); - preset_combobox->addItem(tr("PAL (576i)")); - preset_combobox->addItem(tr("Custom")); - preset_combobox->setCurrentIndex(2); - - preset_layout->addWidget(preset_combobox); - - verticalLayout->addWidget(widget); - - QGroupBox* videoGroupBox = new QGroupBox(this); - videoGroupBox->setTitle(tr("Video")); - - QGridLayout* videoLayout = new QGridLayout(videoGroupBox); - - videoLayout->addWidget(new QLabel(tr("Width:"), this), 0, 0, 1, 1); - width_numeric = new QSpinBox(videoGroupBox); - width_numeric->setMaximum(9999); - 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::config.default_sequence_height); - videoLayout->addWidget(height_numeric, 1, 2, 1, 2); - - videoLayout->addWidget(new QLabel(tr("Frame Rate:"), this), 2, 0, 1, 1); - frame_rate_combobox = new QComboBox(videoGroupBox); - frame_rate_combobox->addItem("10 FPS", 10.0); - frame_rate_combobox->addItem("12.5 FPS", 12.5); - frame_rate_combobox->addItem("15 FPS", 15.0); - frame_rate_combobox->addItem("23.976 FPS", 23.976); - frame_rate_combobox->addItem("24 FPS", 24.0); - frame_rate_combobox->addItem("25 FPS", 25.0); - frame_rate_combobox->addItem("29.97 FPS", 29.97); - frame_rate_combobox->addItem("30 FPS", 30.0); - frame_rate_combobox->addItem("50 FPS", 50.0); - 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::config.default_sequence_framerate)) { - frame_rate_combobox->setCurrentIndex(i); - } - } - videoLayout->addWidget(frame_rate_combobox, 2, 2, 1, 2); - - videoLayout->addWidget(new QLabel(tr("Pixel Aspect Ratio:"), this), 4, 0, 1, 1); - par_combobox = new QComboBox(videoGroupBox); - par_combobox->addItem(tr("Square Pixels (1.0)")); - videoLayout->addWidget(par_combobox, 4, 2, 1, 2); - - videoLayout->addWidget(new QLabel(tr("Interlacing:"), this), 6, 0, 1, 1); - interlacing_combobox = new QComboBox(videoGroupBox); - interlacing_combobox->addItem(tr("None (Progressive)")); - videoLayout->addWidget(interlacing_combobox, 6, 2, 1, 2); - - verticalLayout->addWidget(videoGroupBox); - - QGroupBox* audioGroupBox = new QGroupBox(this); - audioGroupBox->setTitle(tr("Audio")); - - QGridLayout* audioLayout = new QGridLayout(audioGroupBox); - - audioLayout->addWidget(new QLabel(tr("Sample Rate: "), this), 0, 0, 1, 1); - - 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::config.default_sequence_audio_frequency) { - audio_frequency_combobox->setCurrentIndex(i); - } - } - - audioLayout->addWidget(audio_frequency_combobox, 0, 1, 1, 1); - - verticalLayout->addWidget(audioGroupBox); - - QWidget* nameWidget = new QWidget(this); - QHBoxLayout* nameLayout = new QHBoxLayout(nameWidget); - nameLayout->setContentsMargins(0, 0, 0, 0); - - sequence_name_label = new QLabel(tr("Name:")); - nameLayout->addWidget(sequence_name_label); - - sequence_name_edit = new QLineEdit(nameWidget); - - nameLayout->addWidget(sequence_name_edit); - - verticalLayout->addWidget(nameWidget); - - QDialogButtonBox* buttonBox = new QDialogButtonBox(this); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - buttonBox->setCenterButtons(true); - - verticalLayout->addWidget(buttonBox); - - connect(preset_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(preset_changed(int))); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); -} +/*** + + 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 "newsequencedialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "panels/panels.h" +#include "panels/project.h" +#include "timeline/sequence.h" +#include "undo/undostack.h" +#include "undo/undo.h" +#include "timeline/clip.h" +#include "panels/timeline.h" +#include "project/media.h" +#include "rendering/audio.h" +#include "global/config.h" + +// FIXME: TEST CODE +#include "nodes/nodes/nodemedia.h" +// END TEST CODE + +extern "C" { +#include +} + +NewSequenceDialog::NewSequenceDialog(QWidget *parent, Media *existing, Sequence* iexisting_sequence) : + QDialog(parent), + existing_item(existing), + existing_sequence(iexisting_sequence) +{ + Q_ASSERT(!(existing != nullptr && iexisting_sequence != nullptr)); + + setup_ui(); + + if (existing != nullptr) { + existing_sequence = existing->to_sequence().get(); + } + + if (existing_sequence != nullptr) { + setWindowTitle(tr("Editing \"%1\"").arg(existing_sequence->name())); + + width_numeric->setValue(existing_sequence->width()); + height_numeric->setValue(existing_sequence->height()); + int comp_rate = qRound(existing_sequence->frame_rate()*100); + for (int i=0;icount();i++) { + if (qRound(frame_rate_combobox->itemData(i).toDouble()*100) == comp_rate) { + frame_rate_combobox->setCurrentIndex(i); + break; + } + } + sequence_name_edit->setText(existing_sequence->name()); + for (int i=0;icount();i++) { + if (audio_frequency_combobox->itemData(i) == existing_sequence->audio_frequency()) { + audio_frequency_combobox->setCurrentIndex(i); + break; + } + } + } else { + existing_sequence = nullptr; + setWindowTitle(tr("New Sequence")); + } +} + +void NewSequenceDialog::set_sequence_name(const QString& s) { + sequence_name_edit->setText(s); +} + +void NewSequenceDialog::SetNameEditable(bool enabled) +{ + sequence_name_edit->setVisible(enabled); + sequence_name_label->setVisible(enabled); +} + +void NewSequenceDialog::accept() { + if (existing_sequence == nullptr) { + + // The dialog wasn't given an existing Sequence object, so we'll make a new one + + SequencePtr s = std::make_shared(); + + s->set_name(sequence_name_edit->text()); + s->set_width(width_numeric->value()); + s->set_height(height_numeric->value()); + s->set_frame_rate(frame_rate_combobox->currentData().toDouble()); + s->set_audio_frequency(audio_frequency_combobox->currentData().toInt()); + s->set_audio_layout(AV_CH_LAYOUT_STEREO); + + ComboAction* ca = new ComboAction(); + olive::project_model.CreateSequence(ca, s, true, nullptr); + olive::undo_stack.push(ca); + + } else if (existing_item != nullptr) { + + // The dialog was given an existing Sequence object, so we'll apply the changes to it + + ComboAction* ca = new ComboAction(); + + double multiplier = frame_rate_combobox->currentData().toDouble() / existing_sequence->frame_rate(); + + EditSequenceCommand* esc = new EditSequenceCommand(existing_item, existing_item->to_sequence()); + esc->name = sequence_name_edit->text(); + esc->width = width_numeric->value(); + esc->height = height_numeric->value(); + esc->frame_rate = frame_rate_combobox->currentData().toDouble(); + esc->audio_frequency = audio_frequency_combobox->currentData().toInt(); + esc->audio_layout = AV_CH_LAYOUT_STEREO; + ca->append(esc); + + QVector existing_sequence_clips = existing_sequence->GetAllClips(); + for (int i=0;irefactor_frame_rate(ca, multiplier, true); + } + + olive::undo_stack.push(ca); + + } else if (existing_sequence != nullptr) { + + // This dialog was given an existing Sequence without a Media wrapper - therefore just directly apply the settings + + existing_sequence->set_name(sequence_name_edit->text()); + existing_sequence->set_width(width_numeric->value()); + existing_sequence->set_height(height_numeric->value()); + existing_sequence->set_frame_rate(frame_rate_combobox->currentData().toDouble()); + existing_sequence->set_audio_frequency(audio_frequency_combobox->currentData().toInt()); + existing_sequence->set_audio_layout(AV_CH_LAYOUT_STEREO); + + } + + QDialog::accept(); +} + +void NewSequenceDialog::preset_changed(int index) { + switch (index) { + case 0: // FILM 4K + width_numeric->setValue(4096); + height_numeric->setValue(2160); + break; + case 1: // TV 4K + width_numeric->setValue(3840); + height_numeric->setValue(2160); + break; + case 2: // 1080p + width_numeric->setValue(1920); + height_numeric->setValue(1080); + break; + case 3: // 720p + width_numeric->setValue(1280); + height_numeric->setValue(720); + break; + case 4: // 480p + width_numeric->setValue(640); + height_numeric->setValue(480); + break; + case 5: // 360p + width_numeric->setValue(640); + height_numeric->setValue(360); + break; + case 6: // 240p + width_numeric->setValue(320); + height_numeric->setValue(240); + break; + case 7: // 144p + width_numeric->setValue(192); + height_numeric->setValue(144); + break; + case 8: // NTSC (480i) + width_numeric->setValue(720); + height_numeric->setValue(480); + break; + case 9: // PAL (576i) + width_numeric->setValue(720); + height_numeric->setValue(576); + break; + } +} + +void NewSequenceDialog::setup_ui() { + QVBoxLayout* verticalLayout = new QVBoxLayout(this); + + QWidget* widget = new QWidget(this); + + QHBoxLayout* preset_layout = new QHBoxLayout(widget); + preset_layout->setContentsMargins(0, 0, 0, 0); + + preset_layout->addWidget(new QLabel(tr("Preset:"), this)); + + preset_combobox = new QComboBox(widget); + + preset_combobox->addItem(tr("Film 4K")); + preset_combobox->addItem(tr("TV 4K (Ultra HD/2160p)")); + preset_combobox->addItem(tr("1080p")); + preset_combobox->addItem(tr("720p")); + preset_combobox->addItem(tr("480p")); + preset_combobox->addItem(tr("360p")); + preset_combobox->addItem(tr("240p")); + preset_combobox->addItem(tr("144p")); + preset_combobox->addItem(tr("NTSC (480i)")); + preset_combobox->addItem(tr("PAL (576i)")); + preset_combobox->addItem(tr("Custom")); + preset_combobox->setCurrentIndex(2); + + preset_layout->addWidget(preset_combobox); + + verticalLayout->addWidget(widget); + + QGroupBox* videoGroupBox = new QGroupBox(this); + videoGroupBox->setTitle(tr("Video")); + + QGridLayout* videoLayout = new QGridLayout(videoGroupBox); + + videoLayout->addWidget(new QLabel(tr("Width:"), this), 0, 0, 1, 1); + width_numeric = new QSpinBox(videoGroupBox); + width_numeric->setMaximum(9999); + 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::config.default_sequence_height); + videoLayout->addWidget(height_numeric, 1, 2, 1, 2); + + videoLayout->addWidget(new QLabel(tr("Frame Rate:"), this), 2, 0, 1, 1); + frame_rate_combobox = new QComboBox(videoGroupBox); + frame_rate_combobox->addItem("10 FPS", 10.0); + frame_rate_combobox->addItem("12.5 FPS", 12.5); + frame_rate_combobox->addItem("15 FPS", 15.0); + frame_rate_combobox->addItem("23.976 FPS", 23.976); + frame_rate_combobox->addItem("24 FPS", 24.0); + frame_rate_combobox->addItem("25 FPS", 25.0); + frame_rate_combobox->addItem("29.97 FPS", 29.97); + frame_rate_combobox->addItem("30 FPS", 30.0); + frame_rate_combobox->addItem("50 FPS", 50.0); + 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::config.default_sequence_framerate)) { + frame_rate_combobox->setCurrentIndex(i); + } + } + videoLayout->addWidget(frame_rate_combobox, 2, 2, 1, 2); + + videoLayout->addWidget(new QLabel(tr("Pixel Aspect Ratio:"), this), 4, 0, 1, 1); + par_combobox = new QComboBox(videoGroupBox); + par_combobox->addItem(tr("Square Pixels (1.0)")); + videoLayout->addWidget(par_combobox, 4, 2, 1, 2); + + videoLayout->addWidget(new QLabel(tr("Interlacing:"), this), 6, 0, 1, 1); + interlacing_combobox = new QComboBox(videoGroupBox); + interlacing_combobox->addItem(tr("None (Progressive)")); + videoLayout->addWidget(interlacing_combobox, 6, 2, 1, 2); + + verticalLayout->addWidget(videoGroupBox); + + QGroupBox* audioGroupBox = new QGroupBox(this); + audioGroupBox->setTitle(tr("Audio")); + + QGridLayout* audioLayout = new QGridLayout(audioGroupBox); + + audioLayout->addWidget(new QLabel(tr("Sample Rate: "), this), 0, 0, 1, 1); + + 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::config.default_sequence_audio_frequency) { + audio_frequency_combobox->setCurrentIndex(i); + } + } + + audioLayout->addWidget(audio_frequency_combobox, 0, 1, 1, 1); + + verticalLayout->addWidget(audioGroupBox); + + QWidget* nameWidget = new QWidget(this); + QHBoxLayout* nameLayout = new QHBoxLayout(nameWidget); + nameLayout->setContentsMargins(0, 0, 0, 0); + + sequence_name_label = new QLabel(tr("Name:")); + nameLayout->addWidget(sequence_name_label); + + sequence_name_edit = new QLineEdit(nameWidget); + + nameLayout->addWidget(sequence_name_edit); + + verticalLayout->addWidget(nameWidget); + + QDialogButtonBox* buttonBox = new QDialogButtonBox(this); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + buttonBox->setCenterButtons(true); + + verticalLayout->addWidget(buttonBox); + + connect(preset_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(preset_changed(int))); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} diff --git a/dialogs/newsequencedialog.h b/dialogs/newsequencedialog.h index 301173b32..661dabb20 100644 --- a/dialogs/newsequencedialog.h +++ b/dialogs/newsequencedialog.h @@ -1,168 +1,168 @@ -/*** - - 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 NEWSEQUENCEDIALOG_H -#define NEWSEQUENCEDIALOG_H - -#include -#include -#include -#include - -#include "panels/project.h" -#include "project/media.h" -#include "timeline/sequence.h" - -/** - * @brief The NewSequenceDialog class - * - * A dialog that creates a new (or edits an existing) Sequence object. Can be run from any part of the application. - */ -class NewSequenceDialog : public QDialog -{ - Q_OBJECT -public: - /** - * @brief NewSequenceDialog constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow. - * - * @param existing - * - * Set this to a Sequence object (wrapped in a Media object) to edit an existing Sequence, - * or leave as nullptr to create a new one. - * - * @param existing_sequence - * - * If your Sequence object is not wrapped in a Media object, use this to reference a raw Sequence pointer. You must - * not use both existing_sequence AND existing - one must be nullptr. - */ - explicit NewSequenceDialog(QWidget *parent = nullptr, Media* existing = nullptr, Sequence* iexisting_sequence = nullptr); - - /** - * @brief Set the name for the new Sequence - * - * If creating a new Sequence, use this function before calling exec() to set what the new Sequence's - * name will be. - * - * The primary use of this is to set a unique default name (i.e. one that doesn't exist - * in the Sequence already) which is done by Project panel. This is usually "Sequence" followed by a number. - * - * @param s - * - * The name to set the new Sequence. - */ - void set_sequence_name(const QString& s); - - /** - * @brief Set whether the Sequence's name can be edited - * - * This defaults to TRUE. - * - * @param enabled - * - * TRUE to allow the user to edit the Sequence's name. FALSE if not. - */ - void SetNameEditable(bool enabled); - -private slots: - /** - * @brief Override accept function to create/edit a Sequence - */ - virtual void accept() override; - - /** - * @brief Slot when the user changes the preset - * - * Sets all values according to the preset chosen. - * - * @param index - * - * Currently selected index of preset_combobox; - */ - void preset_changed(int index); - -private: - /** - * @brief Internal reference to an existing Media wrapper (if one was provided to the constructor) - */ - Media* existing_item; - - /** - * @brief Internal reference to an existing Sequence (if one was provided to the constructor) - */ - Sequence* existing_sequence; - - /** - * @brief Internal function to create the dialog's UI - */ - void setup_ui(); - - /** - * @brief ComboBox to set the preset - */ - QComboBox* preset_combobox; - - /** - * @brief SpinBox to set the Sequence height - */ - QSpinBox* height_numeric; - - /** - * @brief SpinBox to set the Sequence width - */ - QSpinBox* width_numeric; - - /** - * @brief ComboBox to set the pixel aspect ratio - */ - QComboBox* par_combobox; - - /** - * @brief ComboBox to set the interlacing mode - */ - QComboBox* interlacing_combobox; - - /** - * @brief ComboBox to set the frame rate - */ - QComboBox* frame_rate_combobox; - - /** - * @brief ComboBox to set the audio frequence - */ - QComboBox* audio_frequency_combobox; - - /** - * @brief Label marker for setting the Sequence's name - * - * Primarily a persistent class reference so it can be hidden with SetNameEditable() alongside sequence_name_edit. - */ - QLabel* sequence_name_label; - - /** - * @brief Line edit to set the Sequence's name - */ - QLineEdit* sequence_name_edit; -}; - -#endif // NEWSEQUENCEDIALOG_H +/*** + + 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 NEWSEQUENCEDIALOG_H +#define NEWSEQUENCEDIALOG_H + +#include +#include +#include +#include + +#include "panels/project.h" +#include "project/media.h" +#include "timeline/sequence.h" + +/** + * @brief The NewSequenceDialog class + * + * A dialog that creates a new (or edits an existing) Sequence object. Can be run from any part of the application. + */ +class NewSequenceDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @brief NewSequenceDialog constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow. + * + * @param existing + * + * Set this to a Sequence object (wrapped in a Media object) to edit an existing Sequence, + * or leave as nullptr to create a new one. + * + * @param existing_sequence + * + * If your Sequence object is not wrapped in a Media object, use this to reference a raw Sequence pointer. You must + * not use both existing_sequence AND existing - one must be nullptr. + */ + explicit NewSequenceDialog(QWidget *parent = nullptr, Media* existing = nullptr, Sequence* iexisting_sequence = nullptr); + + /** + * @brief Set the name for the new Sequence + * + * If creating a new Sequence, use this function before calling exec() to set what the new Sequence's + * name will be. + * + * The primary use of this is to set a unique default name (i.e. one that doesn't exist + * in the Sequence already) which is done by Project panel. This is usually "Sequence" followed by a number. + * + * @param s + * + * The name to set the new Sequence. + */ + void set_sequence_name(const QString& s); + + /** + * @brief Set whether the Sequence's name can be edited + * + * This defaults to TRUE. + * + * @param enabled + * + * TRUE to allow the user to edit the Sequence's name. FALSE if not. + */ + void SetNameEditable(bool enabled); + +private slots: + /** + * @brief Override accept function to create/edit a Sequence + */ + virtual void accept() override; + + /** + * @brief Slot when the user changes the preset + * + * Sets all values according to the preset chosen. + * + * @param index + * + * Currently selected index of preset_combobox; + */ + void preset_changed(int index); + +private: + /** + * @brief Internal reference to an existing Media wrapper (if one was provided to the constructor) + */ + Media* existing_item; + + /** + * @brief Internal reference to an existing Sequence (if one was provided to the constructor) + */ + Sequence* existing_sequence; + + /** + * @brief Internal function to create the dialog's UI + */ + void setup_ui(); + + /** + * @brief ComboBox to set the preset + */ + QComboBox* preset_combobox; + + /** + * @brief SpinBox to set the Sequence height + */ + QSpinBox* height_numeric; + + /** + * @brief SpinBox to set the Sequence width + */ + QSpinBox* width_numeric; + + /** + * @brief ComboBox to set the pixel aspect ratio + */ + QComboBox* par_combobox; + + /** + * @brief ComboBox to set the interlacing mode + */ + QComboBox* interlacing_combobox; + + /** + * @brief ComboBox to set the frame rate + */ + QComboBox* frame_rate_combobox; + + /** + * @brief ComboBox to set the audio frequence + */ + QComboBox* audio_frequency_combobox; + + /** + * @brief Label marker for setting the Sequence's name + * + * Primarily a persistent class reference so it can be hidden with SetNameEditable() alongside sequence_name_edit. + */ + QLabel* sequence_name_label; + + /** + * @brief Line edit to set the Sequence's name + */ + QLineEdit* sequence_name_edit; +}; + +#endif // NEWSEQUENCEDIALOG_H diff --git a/dialogs/proxydialog.cpp b/dialogs/proxydialog.cpp index acbd77300..21534ca9f 100644 --- a/dialogs/proxydialog.cpp +++ b/dialogs/proxydialog.cpp @@ -1,184 +1,184 @@ -/*** - - 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 "proxydialog.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "project/proxygenerator.h" -#include "project/footage.h" -#include "ui/mainwindow.h" -#include "global/global.h" - -ProxyDialog::ProxyDialog(QWidget *parent, const QVector &media) : - QDialog(parent), - selected_media(media) -{ - // set dialog title - setWindowTitle(tr("Create Proxy")); - - // set proxy folder name to "Proxy", depending on the user's language - proxy_folder_name = tr("Proxy"); - - // set up dialog's layout - QGridLayout* layout = new QGridLayout(this); - - // set the video dimensions of the proxy - layout->addWidget(new QLabel(tr("Dimensions:"), this), 0, 0); - - size_combobox = new QComboBox(this); - size_combobox->addItem(tr("Same Size as Source"), 1.0); - size_combobox->addItem(tr("Half Resolution (1/2)"), 0.5); - size_combobox->addItem(tr("Quarter Resolution (1/4)"), 0.25); - size_combobox->addItem(tr("Eighth Resolution (1/8)"), 0.125); - size_combobox->addItem(tr("Sixteenth Resolution (1/16)"), 0.0625); - layout->addWidget(size_combobox, 0, 1); - - // set the desired format of the proxy to create - layout->addWidget(new QLabel(tr("Format:"), this), 1, 0); - - format_combobox = new QComboBox(this); - format_combobox->addItem(tr("ProRes HQ")); - // format_combobox->addItem(tr("ProRes SQ")); - // format_combobox->addItem(tr("ProRes LT")); - // format_combobox->addItem(tr("DNxHD")); - // format_combobox->addItem(tr("H.264")); - layout->addWidget(format_combobox, 1, 1); - - // set the location to place the proxies - layout->addWidget(new QLabel(tr("Location:"), this), 2, 0); - - location_combobox = new QComboBox(this); - location_combobox->addItem(tr("Same as Source (in \"%1\" folder)").arg(proxy_folder_name)); - location_combobox->addItem(""); - connect(location_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(location_changed(int))); - layout->addWidget(location_combobox, 2, 1); - - // location_changed will set the default "location" items - location_changed(0); - - // set up dialog buttons - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - buttons->setCenterButtons(true); - layout->addWidget(buttons, 3, 0, 1, 2); - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); -} - -void ProxyDialog::accept() { - QVector info_list; - - // set to TRUE if any existing proxies exist and the user chooses to overwrite all of them - bool overwrite_all_existing = false; - - for (int i=0;icurrentData().toDouble(); - - Footage* footage = selected_media.at(i)->to_footage(); - - QString base_footage_fn = QFileInfo(footage->url).baseName(); - - // TEMPORARILY hardcoded proxy format - base_footage_fn.append(".mov"); - - // determine path from input - if (custom_location.isEmpty()) { - // use same as source (proxy subfolder) - - // generate full path from footage path and proxy_folder_name's translated "Proxy" - info.path = QDir(QFileInfo(footage->url).dir().filePath(proxy_folder_name)).filePath(base_footage_fn); - } else { - // use existing location - info.path = QDir(custom_location).filePath(base_footage_fn); - } - - // if the proposed proxy file already exists & user didn't select YesToAll box - if (QFileInfo::exists(info.path) && !overwrite_all_existing){ - int rtn = QMessageBox::warning( this, - tr("Proxy file exists"), - tr("The file \"%1\" already exists. Do you wish to replace it?").arg(info.path), - QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No); - - switch (rtn){ - case QMessageBox::Yes: - // continue as normal, proxy generator will automatically overwrite this file - break; - case QMessageBox::YesToAll: - // continue as normal, also set variable so that above messagebox is not shown again - overwrite_all_existing = true; - break; - case QMessageBox::No: - // return to dialog without closing or starting any proxy generation - return; - } - } - - // send to proxy generator thread - info_list.append(info); - } - - // all proxy info checks out, queue it with the proxy generator - for (int i=0;ito_footage(); - footage->proxy = true; - footage->proxy_path.clear(); - - olive::proxy_generator.queue(info_list.at(i)); - } - - olive::Global->set_modified(true); - - QDialog::accept(); -} - -void ProxyDialog::location_changed(int i) { - // clear any custom location - either the user picked a new one or chose not a custom location - custom_location.clear(); - - if (i == 1) { - // if the user picked a custom location, ask which directory - QString s = QFileDialog::getExistingDirectory(this); - - if (s.isEmpty()) { - // if the user didn't input anything, set the combobox back to default - location_combobox->setCurrentIndex(0); - } else { - // if the user chose a custom location, set the combobox text to it and custom_location for future usage - location_combobox->setItemText(1, s); - custom_location = s; - } - } else { - // if the user doesn't picks something other than custom location, set this string back to default for clearer UX - location_combobox->setItemText(1, tr("Custom Location")); - } -} +/*** + + 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 "proxydialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "project/proxygenerator.h" +#include "project/footage.h" +#include "ui/mainwindow.h" +#include "global/global.h" + +ProxyDialog::ProxyDialog(QWidget *parent, const QVector &media) : + QDialog(parent), + selected_media(media) +{ + // set dialog title + setWindowTitle(tr("Create Proxy")); + + // set proxy folder name to "Proxy", depending on the user's language + proxy_folder_name = tr("Proxy"); + + // set up dialog's layout + QGridLayout* layout = new QGridLayout(this); + + // set the video dimensions of the proxy + layout->addWidget(new QLabel(tr("Dimensions:"), this), 0, 0); + + size_combobox = new QComboBox(this); + size_combobox->addItem(tr("Same Size as Source"), 1.0); + size_combobox->addItem(tr("Half Resolution (1/2)"), 0.5); + size_combobox->addItem(tr("Quarter Resolution (1/4)"), 0.25); + size_combobox->addItem(tr("Eighth Resolution (1/8)"), 0.125); + size_combobox->addItem(tr("Sixteenth Resolution (1/16)"), 0.0625); + layout->addWidget(size_combobox, 0, 1); + + // set the desired format of the proxy to create + layout->addWidget(new QLabel(tr("Format:"), this), 1, 0); + + format_combobox = new QComboBox(this); + format_combobox->addItem(tr("ProRes HQ")); + // format_combobox->addItem(tr("ProRes SQ")); + // format_combobox->addItem(tr("ProRes LT")); + // format_combobox->addItem(tr("DNxHD")); + // format_combobox->addItem(tr("H.264")); + layout->addWidget(format_combobox, 1, 1); + + // set the location to place the proxies + layout->addWidget(new QLabel(tr("Location:"), this), 2, 0); + + location_combobox = new QComboBox(this); + location_combobox->addItem(tr("Same as Source (in \"%1\" folder)").arg(proxy_folder_name)); + location_combobox->addItem(""); + connect(location_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(location_changed(int))); + layout->addWidget(location_combobox, 2, 1); + + // location_changed will set the default "location" items + location_changed(0); + + // set up dialog buttons + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttons->setCenterButtons(true); + layout->addWidget(buttons, 3, 0, 1, 2); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); +} + +void ProxyDialog::accept() { + QVector info_list; + + // set to TRUE if any existing proxies exist and the user chooses to overwrite all of them + bool overwrite_all_existing = false; + + for (int i=0;icurrentData().toDouble(); + + Footage* footage = selected_media.at(i)->to_footage(); + + QString base_footage_fn = QFileInfo(footage->url).baseName(); + + // TEMPORARILY hardcoded proxy format + base_footage_fn.append(".mov"); + + // determine path from input + if (custom_location.isEmpty()) { + // use same as source (proxy subfolder) + + // generate full path from footage path and proxy_folder_name's translated "Proxy" + info.path = QDir(QFileInfo(footage->url).dir().filePath(proxy_folder_name)).filePath(base_footage_fn); + } else { + // use existing location + info.path = QDir(custom_location).filePath(base_footage_fn); + } + + // if the proposed proxy file already exists & user didn't select YesToAll box + if (QFileInfo::exists(info.path) && !overwrite_all_existing){ + int rtn = QMessageBox::warning( this, + tr("Proxy file exists"), + tr("The file \"%1\" already exists. Do you wish to replace it?").arg(info.path), + QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No); + + switch (rtn){ + case QMessageBox::Yes: + // continue as normal, proxy generator will automatically overwrite this file + break; + case QMessageBox::YesToAll: + // continue as normal, also set variable so that above messagebox is not shown again + overwrite_all_existing = true; + break; + case QMessageBox::No: + // return to dialog without closing or starting any proxy generation + return; + } + } + + // send to proxy generator thread + info_list.append(info); + } + + // all proxy info checks out, queue it with the proxy generator + for (int i=0;ito_footage(); + footage->proxy = true; + footage->proxy_path.clear(); + + olive::proxy_generator.queue(info_list.at(i)); + } + + olive::Global->set_modified(true); + + QDialog::accept(); +} + +void ProxyDialog::location_changed(int i) { + // clear any custom location - either the user picked a new one or chose not a custom location + custom_location.clear(); + + if (i == 1) { + // if the user picked a custom location, ask which directory + QString s = QFileDialog::getExistingDirectory(this); + + if (s.isEmpty()) { + // if the user didn't input anything, set the combobox back to default + location_combobox->setCurrentIndex(0); + } else { + // if the user chose a custom location, set the combobox text to it and custom_location for future usage + location_combobox->setItemText(1, s); + custom_location = s; + } + } else { + // if the user doesn't picks something other than custom location, set this string back to default for clearer UX + location_combobox->setItemText(1, tr("Custom Location")); + } +} diff --git a/dialogs/proxydialog.h b/dialogs/proxydialog.h index a883b2085..45933e485 100644 --- a/dialogs/proxydialog.h +++ b/dialogs/proxydialog.h @@ -1,107 +1,107 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef PROXYDIALOG_H -#define PROXYDIALOG_H - -#include -#include -#include - -#include "project/media.h" - -/** - * @brief The ProxyDialog class - * - * Dialog to set up proxy generation of footage. This dialog can be called from anywhere provided it's given a valid - * array of Media and will start all proxy generation. - */ -class ProxyDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief ProxyDialog Constructor - * @param parent - * - * Parent widget to become modal to. - * - * @param footage - * - * List of Footage items to process. - */ - ProxyDialog(QWidget* parent, const QVector &media); -public slots: - /** - * @brief Accept changes - * - * Called when the user clicks OK on the dialog. Verifies all proxies, asking the user whether they want to overwrite - * existing proxies if necessary, and if everything is valid, queues the footage with ProxyGenerator. - */ - virtual void accept() override; -private: - /** - * @brief User's desired dimensions - * - * Always a fraction of the original video size (e.g. 1/2, 1/4, 1/8, etc.) - */ - QComboBox* size_combobox; - - /** - * @brief User's desired proxy format - * - * e.g. ProRes, DNxHD, etc. - */ - QComboBox* format_combobox; - - /** - * @brief Allows users to set the directory to store proxies in - */ - QComboBox* location_combobox; - - /** - * @brief Stores the custom location to store proxies if the user sets a custom location - */ - QString custom_location; - - /** - * @brief Stores the default subdirectory to be made next to the source (dependent on the user's language) - * - * "Proxy" in en-US. - */ - QString proxy_folder_name; - - /** - * @brief Stored list of footage to make proxies for - */ - QVector selected_media; -private slots: - /** - * @brief Slot when the user changes the location - * - * Triggered when the user changes the index in the location combobox. - * - * @param i - * - * location_combobox's new selected index - */ - void location_changed(int i); -}; - -#endif // PROXYDIALOG_H +/*** + + 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 PROXYDIALOG_H +#define PROXYDIALOG_H + +#include +#include +#include + +#include "project/media.h" + +/** + * @brief The ProxyDialog class + * + * Dialog to set up proxy generation of footage. This dialog can be called from anywhere provided it's given a valid + * array of Media and will start all proxy generation. + */ +class ProxyDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief ProxyDialog Constructor + * @param parent + * + * Parent widget to become modal to. + * + * @param footage + * + * List of Footage items to process. + */ + ProxyDialog(QWidget* parent, const QVector &media); +public slots: + /** + * @brief Accept changes + * + * Called when the user clicks OK on the dialog. Verifies all proxies, asking the user whether they want to overwrite + * existing proxies if necessary, and if everything is valid, queues the footage with ProxyGenerator. + */ + virtual void accept() override; +private: + /** + * @brief User's desired dimensions + * + * Always a fraction of the original video size (e.g. 1/2, 1/4, 1/8, etc.) + */ + QComboBox* size_combobox; + + /** + * @brief User's desired proxy format + * + * e.g. ProRes, DNxHD, etc. + */ + QComboBox* format_combobox; + + /** + * @brief Allows users to set the directory to store proxies in + */ + QComboBox* location_combobox; + + /** + * @brief Stores the custom location to store proxies if the user sets a custom location + */ + QString custom_location; + + /** + * @brief Stores the default subdirectory to be made next to the source (dependent on the user's language) + * + * "Proxy" in en-US. + */ + QString proxy_folder_name; + + /** + * @brief Stored list of footage to make proxies for + */ + QVector selected_media; +private slots: + /** + * @brief Slot when the user changes the location + * + * Triggered when the user changes the index in the location combobox. + * + * @param i + * + * location_combobox's new selected index + */ + void location_changed(int i); +}; + +#endif // PROXYDIALOG_H diff --git a/dialogs/replaceclipmediadialog.cpp b/dialogs/replaceclipmediadialog.cpp index d90452a6e..7fc237901 100644 --- a/dialogs/replaceclipmediadialog.cpp +++ b/dialogs/replaceclipmediadialog.cpp @@ -1,130 +1,130 @@ -/*** - - 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 "replaceclipmediadialog.h" - -#include -#include -#include -#include - -#include "panels/panels.h" -#include "undo/undostack.h" -#include "rendering/cacher.h" -#include "undo/undo.h" - -ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, Media* old_media) : - QDialog(parent), - media(old_media) -{ - setWindowTitle(tr("Replace clips using \"%1\"").arg(old_media->get_name())); - - resize(300, 400); - - QVBoxLayout* layout = new QVBoxLayout(this); - - layout->addWidget(new QLabel(tr("Select which media you want to replace this media's clips with:"), this)); - - tree = new QTreeView(this); - - layout->addWidget(tree); - - use_same_media_in_points = new QCheckBox(tr("Keep the same media in-points"), this); - use_same_media_in_points->setChecked(true); - layout->addWidget(use_same_media_in_points); - - QHBoxLayout* buttons = new QHBoxLayout(); - - buttons->addStretch(); - - QPushButton* replace_button = new QPushButton(tr("Replace"), this); - connect(replace_button, SIGNAL(clicked(bool)), this, SLOT(accept())); - buttons->addWidget(replace_button); - - QPushButton* cancel_button = new QPushButton(tr("Cancel"), this); - connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); - buttons->addWidget(cancel_button); - - buttons->addStretch(); - - layout->addLayout(buttons); - - tree->setModel(&olive::project_model); -} - -void ReplaceClipMediaDialog::accept() { - QModelIndexList selected_items = tree->selectionModel()->selectedRows(); - if (selected_items.size() != 1) { - QMessageBox::critical( - this, - tr("No media selected"), - tr("Please select a media to replace with or click 'Cancel'."), - QMessageBox::Ok - ); - } else { - Media* new_item = static_cast(selected_items.at(0).internalPointer()); - if (media == new_item) { - QMessageBox::critical( - this, - tr("Same media selected"), - tr("You selected the same media that you're replacing. Please select a different one or click 'Cancel'."), - QMessageBox::Ok - ); - } else if (new_item->get_type() == MEDIA_TYPE_FOLDER) { - QMessageBox::critical( - this, - tr("Folder selected"), - tr("You cannot replace footage with a folder."), - QMessageBox::Ok - ); - } else { - - 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"), - tr("You cannot insert a sequence into itself."), - QMessageBox::Ok - ); - } else { - ReplaceClipMediaCommand* rcmc = new ReplaceClipMediaCommand( - media, - new_item, - use_same_media_in_points->isChecked() - ); - - QVector all_clips = top_sequence->GetAllClips(); - for (int i=0;imedia() == media) { - rcmc->clips.append(c); - } - } - - olive::undo_stack.push(rcmc); - - QDialog::accept(); - } - - } - } -} +/*** + + 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 "replaceclipmediadialog.h" + +#include +#include +#include +#include + +#include "panels/panels.h" +#include "undo/undostack.h" +#include "rendering/cacher.h" +#include "undo/undo.h" + +ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, Media* old_media) : + QDialog(parent), + media(old_media) +{ + setWindowTitle(tr("Replace clips using \"%1\"").arg(old_media->get_name())); + + resize(300, 400); + + QVBoxLayout* layout = new QVBoxLayout(this); + + layout->addWidget(new QLabel(tr("Select which media you want to replace this media's clips with:"), this)); + + tree = new QTreeView(this); + + layout->addWidget(tree); + + use_same_media_in_points = new QCheckBox(tr("Keep the same media in-points"), this); + use_same_media_in_points->setChecked(true); + layout->addWidget(use_same_media_in_points); + + QHBoxLayout* buttons = new QHBoxLayout(); + + buttons->addStretch(); + + QPushButton* replace_button = new QPushButton(tr("Replace"), this); + connect(replace_button, SIGNAL(clicked(bool)), this, SLOT(accept())); + buttons->addWidget(replace_button); + + QPushButton* cancel_button = new QPushButton(tr("Cancel"), this); + connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); + buttons->addWidget(cancel_button); + + buttons->addStretch(); + + layout->addLayout(buttons); + + tree->setModel(&olive::project_model); +} + +void ReplaceClipMediaDialog::accept() { + QModelIndexList selected_items = tree->selectionModel()->selectedRows(); + if (selected_items.size() != 1) { + QMessageBox::critical( + this, + tr("No media selected"), + tr("Please select a media to replace with or click 'Cancel'."), + QMessageBox::Ok + ); + } else { + Media* new_item = static_cast(selected_items.at(0).internalPointer()); + if (media == new_item) { + QMessageBox::critical( + this, + tr("Same media selected"), + tr("You selected the same media that you're replacing. Please select a different one or click 'Cancel'."), + QMessageBox::Ok + ); + } else if (new_item->get_type() == MEDIA_TYPE_FOLDER) { + QMessageBox::critical( + this, + tr("Folder selected"), + tr("You cannot replace footage with a folder."), + QMessageBox::Ok + ); + } else { + + 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"), + tr("You cannot insert a sequence into itself."), + QMessageBox::Ok + ); + } else { + ReplaceClipMediaCommand* rcmc = new ReplaceClipMediaCommand( + media, + new_item, + use_same_media_in_points->isChecked() + ); + + QVector all_clips = top_sequence->GetAllClips(); + for (int i=0;imedia() == media) { + rcmc->clips.append(c); + } + } + + olive::undo_stack.push(rcmc); + + QDialog::accept(); + } + + } + } +} diff --git a/dialogs/replaceclipmediadialog.h b/dialogs/replaceclipmediadialog.h index 34fa9b6b1..ec93f46c4 100644 --- a/dialogs/replaceclipmediadialog.h +++ b/dialogs/replaceclipmediadialog.h @@ -1,82 +1,82 @@ -/*** - - 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 REPLACECLIPMEDIADIALOG_H -#define REPLACECLIPMEDIADIALOG_H - -#include -#include -#include - -#include "ui/sourcetable.h" -#include "project/projectelements.h" - -/** - * @brief The ReplaceClipMediaDialog class - * - * A dialog to replace all Clips using a certain Media with a different Media. This dialog can be run from anywhere - * provided it's given a valid Media object. - */ -class ReplaceClipMediaDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief ReplaceClipMediaDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow or Project panel. - * - * @param old_media - * - * A valid Media object which will be used to scan the currently active Sequence for Clips using it. - */ - ReplaceClipMediaDialog(QWidget* parent, Media* old_media); -private slots: - /** - * @brief Overridden accept for when the user clicks "Replace" - * - * Checks whether the requested replace is valid using the following criteria: - * * Any Media is selected - * * The selected Media is not the same Media that the user is trying to replace - * * The Media is not a folder - * * The Media is not the currently active Sequence - */ - virtual void accept() override; -private: - /** - * @brief Internal pointer to the Media we're replacing - */ - Media* media; - - /** - * @brief Tree widget to show Project's media - */ - QTreeView* tree; - - /** - * @brief CheckBox for using the same media in points - * - * When the starting point of a Clip is trimmed (i.e. the Clip no longer starts at 0), - */ - QCheckBox* use_same_media_in_points; -}; - -#endif // REPLACECLIPMEDIADIALOG_H +/*** + + 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 REPLACECLIPMEDIADIALOG_H +#define REPLACECLIPMEDIADIALOG_H + +#include +#include +#include + +#include "ui/sourcetable.h" +#include "project/projectelements.h" + +/** + * @brief The ReplaceClipMediaDialog class + * + * A dialog to replace all Clips using a certain Media with a different Media. This dialog can be run from anywhere + * provided it's given a valid Media object. + */ +class ReplaceClipMediaDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief ReplaceClipMediaDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow or Project panel. + * + * @param old_media + * + * A valid Media object which will be used to scan the currently active Sequence for Clips using it. + */ + ReplaceClipMediaDialog(QWidget* parent, Media* old_media); +private slots: + /** + * @brief Overridden accept for when the user clicks "Replace" + * + * Checks whether the requested replace is valid using the following criteria: + * * Any Media is selected + * * The selected Media is not the same Media that the user is trying to replace + * * The Media is not a folder + * * The Media is not the currently active Sequence + */ + virtual void accept() override; +private: + /** + * @brief Internal pointer to the Media we're replacing + */ + Media* media; + + /** + * @brief Tree widget to show Project's media + */ + QTreeView* tree; + + /** + * @brief CheckBox for using the same media in points + * + * When the starting point of a Clip is trimmed (i.e. the Clip no longer starts at 0), + */ + QCheckBox* use_same_media_in_points; +}; + +#endif // REPLACECLIPMEDIADIALOG_H diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index deac900aa..302ba23e0 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -1,461 +1,461 @@ -/*** - - 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 "speeddialog.h" - -#include -#include -#include -#include -#include - -#include "timeline/sequence.h" -#include "project/footage.h" -#include "rendering/renderfunctions.h" -#include "panels/panels.h" -#include "panels/timeline.h" -#include "undo/undo.h" -#include "undo/undostack.h" -#include "nodes/oldeffectnode.h" -#include "project/media.h" - -SpeedDialog::SpeedDialog(QWidget *parent, QVector clips) : QDialog(parent) { - setWindowTitle(tr("Speed/Duration")); - - clips_ = clips; - - QVBoxLayout* main_layout = new QVBoxLayout(this); - - QGridLayout* grid = new QGridLayout(); - grid->setSpacing(6); - - grid->addWidget(new QLabel(tr("Speed:"), this), 0, 0); - percent = new LabelSlider(this); - percent->SetDecimalPlaces(2); - percent->SetDisplayType(LabelSlider::Percent); - percent->SetDefault(1); - grid->addWidget(percent, 0, 1); - - grid->addWidget(new QLabel(tr("Frame Rate:"), this), 1, 0); - frame_rate = new LabelSlider(this); - frame_rate->SetDecimalPlaces(3); - grid->addWidget(frame_rate, 1, 1); - - grid->addWidget(new QLabel(tr("Duration:"), this), 2, 0); - duration = new LabelSlider(this); - duration->SetDisplayType(LabelSlider::FrameNumber); - duration->SetFrameRate(clips_.first()->track()->sequence()->frame_rate()); - grid->addWidget(duration, 2, 1); - - main_layout->addLayout(grid); - - reverse = new QCheckBox(tr("Reverse"), this); - maintain_pitch = new QCheckBox(tr("Maintain Audio Pitch"), this); - ripple = new QCheckBox(tr("Ripple Changes"), this); - - main_layout->addWidget(reverse); - main_layout->addWidget(maintain_pitch); - main_layout->addWidget(ripple); - - QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - buttonBox->setCenterButtons(true); - main_layout->addWidget(buttonBox); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - - connect(percent, SIGNAL(valueChanged(double)), this, SLOT(percent_update())); - connect(frame_rate, SIGNAL(valueChanged(double)), this, SLOT(frame_rate_update())); - connect(duration, SIGNAL(valueChanged(double)), this, SLOT(duration_update())); -} - -int SpeedDialog::exec() { - bool enable_frame_rate = false; - bool multiple_audio = false; - maintain_pitch->setEnabled(false); - - double default_frame_rate = qSNaN(); - double current_frame_rate = qSNaN(); - double current_percent = qSNaN(); - long default_length = -1; - long current_length = -1; - - for (int i=0;ispeed().value; - 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(); - if (ms != nullptr && ms->infinite_length) { - process_video = false; - } - } - - if (process_video) { - double media_frame_rate = c->media_frame_rate(); - - // get "default" frame rate" - if (enable_frame_rate) { - // check if frame rate is equal to default - if (!qIsNaN(default_frame_rate) && !qFuzzyCompare(media_frame_rate, default_frame_rate)) { - default_frame_rate = qSNaN(); - } - if (!qIsNaN(current_frame_rate) && !qFuzzyCompare(media_frame_rate*c->speed().value, current_frame_rate)) { - current_frame_rate = qSNaN(); - } - } else { - default_frame_rate = media_frame_rate; - current_frame_rate = media_frame_rate*c->speed().value; - } - - enable_frame_rate = true; - } - } else if (c->type() == olive::kTypeAudio) { - maintain_pitch->setEnabled(true); - - if (!multiple_audio) { - maintain_pitch->setChecked(c->speed().maintain_audio_pitch); - multiple_audio = true; - } else if (!maintain_pitch->isTristate() && maintain_pitch->isChecked() != c->speed().maintain_audio_pitch) { - maintain_pitch->setCheckState(Qt::PartiallyChecked); - maintain_pitch->setTristate(true); - } - } - - if (i == 0) { - reverse->setChecked(c->reversed()); - } else if (c->reversed() != reverse->isChecked()) { - reverse->setTristate(true); - reverse->setCheckState(Qt::PartiallyChecked); - } - - // get default length - long clip_default_length = qRound(c->length() * clip_percent); - if (i == 0) { - current_length = c->length(); - default_length = clip_default_length; - current_percent = clip_percent; - } else { - if (current_length != -1 && c->length() != current_length) { - current_length = -1; - } - if (default_length != -1 && clip_default_length != default_length) { - default_length = -1; - } - if (!qIsNaN(current_percent) && !qFuzzyCompare(clip_percent, current_percent)) { - current_percent = qSNaN(); - } - } - } - - frame_rate->SetMinimum(1); - percent->SetMinimum(0.0001); - duration->SetMinimum(1); - - frame_rate->setEnabled(enable_frame_rate); - frame_rate->SetDefault(default_frame_rate); - frame_rate->SetValue(current_frame_rate); - percent->SetValue(current_percent); - duration->SetDefault(default_length); - duration->SetValue((current_length == -1) ? qSNaN() : current_length); - - return QDialog::exec(); -} - -void SpeedDialog::percent_update() { - bool got_fr = false; - double fr_val = qSNaN(); - long len_val = -1; - - for (int i=0;iisEnabled() && 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)) { - fr_val = qSNaN(); - } - } else { - fr_val = clip_fr; - got_fr = true; - } - } - - // get duration - long clip_default_length = qRound(c->length() * c->speed().value); - long new_clip_length = qRound(clip_default_length / percent->value()); - if (i == 0) { - len_val = new_clip_length; - } else if (len_val > -1 && len_val != new_clip_length) { - len_val = -1; - } - } - - frame_rate->SetValue(fr_val); - duration->SetValue((len_val == -1) ? qSNaN() : len_val); -} - -void SpeedDialog::duration_update() { - double pc_val = qSNaN(); - bool got_fr = false; - double fr_val = qSNaN(); - - for (int i=0;ilength() * c->speed().value); - double clip_pc = clip_default_length / duration->value(); - if (i == 0) { - pc_val = clip_pc; - } else if (!qIsNaN(pc_val) && !qFuzzyCompare(clip_pc, pc_val)) { - pc_val = qSNaN(); - } - - // get frame rate - 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)) { - fr_val = qSNaN(); - } - } else { - fr_val = clip_fr; - got_fr = true; - } - } - } - - frame_rate->SetValue(fr_val); - percent->SetValue(pc_val); -} - -void SpeedDialog::frame_rate_update() { - /*double fr = (frame_rate->value()); - double pc = (fr / default_frame_rate); - percent->set_value(pc, false); - duration->set_value(default_length / pc, false);*/ - - double old_pc_val = qSNaN(); - bool got_pc_val = false; - double pc_val = qSNaN(); - bool got_len_val = false; - long len_val = -1; - - // analyze video clips - for (int i=0;ispeed().value; - } else if (!qIsNaN(old_pc_val) && !qFuzzyCompare(c->speed().value, old_pc_val)) { - old_pc_val = qSNaN(); - } - - 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) { - pc_val = new_clip_speed; - got_pc_val = true; - } else if (!qIsNaN(pc_val) && !qFuzzyCompare(pc_val, new_clip_speed)) { - pc_val = qSNaN(); - } - - // what would be the new length based on this speed - long new_clip_len = (c->length() * c->speed().value) / new_clip_speed; - if (!got_len_val) { - len_val = new_clip_len; - got_len_val = true; - } else if (len_val > -1 && new_clip_len != len_val) { - len_val = -1; - } - } - } - - // analyze audio clips - for (int i=0;itype() == olive::kTypeAudio) { - - long new_clip_len = (qIsNaN(old_pc_val) || qIsNaN(pc_val)) ? - c->length() : qRound((c->length() * c->speed().value) / pc_val); - - if (len_val > -1 && new_clip_len != len_val) { - len_val = -1; - break; - } - } - } - - percent->SetValue(pc_val); - duration->SetValue((len_val == -1) ? qSNaN() : len_val); -} - -void set_speed(ComboAction* ca, Clip* c, double speed, bool ripple, long& ep, long& lr) { - 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()) { - 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) { - proposed_out = compare->timeline_in(); - } - } - } - ep = qMin(ep, c->timeline_out()); - lr = qMax(lr, proposed_out - c->timeline_out()); - 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); - - c->track()->SelectArea(c->timeline_in(), proposed_out); -} - -void SpeedDialog::accept() { - ComboAction* ca = new ComboAction(); - - // undoable action for setting "maintain audio pitch" - SetClipProperty* audio_pitch_action = new SetClipProperty(kSetClipPropertyMaintainAudioPitch); - - // undoable action for setting "reversed" - SetClipProperty* reversed_action = new SetClipProperty(kSetClipPropertyReversed); - - // undoable action for restoring clip selections - Sequence* sequence = clips_.first()->track()->sequence(); - QVector old_selections = sequence->Selections(); - - // variables used to calculate ripples - long earliest_point = LONG_MAX; - long longest_ripple = LONG_MIN; - - for (int i=0;iIsOpen()) { - c->Close(true); - } - - // set maintain audio pitch if the user made a selection - 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()); - } - - // 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->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()); - } - } - - // setting the actual speed - if (!qIsNaN(percent->value())) { - - // if we have a percentage value, use that on all the clips - for (int i=0;ivalue(), ripple->isChecked(), earliest_point, longest_ripple); - } - - } else if (!qIsNaN(frame_rate->value())) { - - // if the user changed the speed by changing the frame rate, - bool can_change_all = true; - double cached_speed = clips_.first()->speed().value; - double cached_fr = qSNaN(); - - // see if we can use the frame rate to change all the speeds - for (int i=0;i 0 && !qFuzzyCompare(cached_speed, c->speed().value)) { - can_change_all = false; - } - if (c->type() == olive::kTypeVideo) { - if (qIsNaN(cached_fr)) { - cached_fr = c->media_frame_rate(); - } else if (!qFuzzyCompare(cached_fr, c->media_frame_rate())) { - can_change_all = false; - break; - } - } - } - - // make changes - for (int i=0;itype() == 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); - } - } - } else if (!qIsNaN(duration->value())) { - // simply set duration - for (int i=0;ilength() * c->speed().value) / duration->value(), ripple->isChecked(), earliest_point, longest_ripple); - } - } - - if (ripple->isChecked()) { - sequence->Ripple(ca, earliest_point, longest_ripple); - } - - ca->append(new SetSelectionsCommand(sequence, old_selections, sequence->Selections())); - - ca->append(reversed_action); - ca->append(audio_pitch_action); - - olive::undo_stack.push(ca); - - update_ui(true); - QDialog::accept(); -} +/*** + + 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 "speeddialog.h" + +#include +#include +#include +#include +#include + +#include "timeline/sequence.h" +#include "project/footage.h" +#include "rendering/renderfunctions.h" +#include "panels/panels.h" +#include "panels/timeline.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "nodes/oldeffectnode.h" +#include "project/media.h" + +SpeedDialog::SpeedDialog(QWidget *parent, QVector clips) : QDialog(parent) { + setWindowTitle(tr("Speed/Duration")); + + clips_ = clips; + + QVBoxLayout* main_layout = new QVBoxLayout(this); + + QGridLayout* grid = new QGridLayout(); + grid->setSpacing(6); + + grid->addWidget(new QLabel(tr("Speed:"), this), 0, 0); + percent = new LabelSlider(this); + percent->SetDecimalPlaces(2); + percent->SetDisplayType(LabelSlider::Percent); + percent->SetDefault(1); + grid->addWidget(percent, 0, 1); + + grid->addWidget(new QLabel(tr("Frame Rate:"), this), 1, 0); + frame_rate = new LabelSlider(this); + frame_rate->SetDecimalPlaces(3); + grid->addWidget(frame_rate, 1, 1); + + grid->addWidget(new QLabel(tr("Duration:"), this), 2, 0); + duration = new LabelSlider(this); + duration->SetDisplayType(LabelSlider::FrameNumber); + duration->SetFrameRate(clips_.first()->track()->sequence()->frame_rate()); + grid->addWidget(duration, 2, 1); + + main_layout->addLayout(grid); + + reverse = new QCheckBox(tr("Reverse"), this); + maintain_pitch = new QCheckBox(tr("Maintain Audio Pitch"), this); + ripple = new QCheckBox(tr("Ripple Changes"), this); + + main_layout->addWidget(reverse); + main_layout->addWidget(maintain_pitch); + main_layout->addWidget(ripple); + + QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttonBox->setCenterButtons(true); + main_layout->addWidget(buttonBox); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + + connect(percent, SIGNAL(valueChanged(double)), this, SLOT(percent_update())); + connect(frame_rate, SIGNAL(valueChanged(double)), this, SLOT(frame_rate_update())); + connect(duration, SIGNAL(valueChanged(double)), this, SLOT(duration_update())); +} + +int SpeedDialog::exec() { + bool enable_frame_rate = false; + bool multiple_audio = false; + maintain_pitch->setEnabled(false); + + double default_frame_rate = qSNaN(); + double current_frame_rate = qSNaN(); + double current_percent = qSNaN(); + long default_length = -1; + long current_length = -1; + + for (int i=0;ispeed().value; + 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(); + if (ms != nullptr && ms->infinite_length) { + process_video = false; + } + } + + if (process_video) { + double media_frame_rate = c->media_frame_rate(); + + // get "default" frame rate" + if (enable_frame_rate) { + // check if frame rate is equal to default + if (!qIsNaN(default_frame_rate) && !qFuzzyCompare(media_frame_rate, default_frame_rate)) { + default_frame_rate = qSNaN(); + } + if (!qIsNaN(current_frame_rate) && !qFuzzyCompare(media_frame_rate*c->speed().value, current_frame_rate)) { + current_frame_rate = qSNaN(); + } + } else { + default_frame_rate = media_frame_rate; + current_frame_rate = media_frame_rate*c->speed().value; + } + + enable_frame_rate = true; + } + } else if (c->type() == olive::kTypeAudio) { + maintain_pitch->setEnabled(true); + + if (!multiple_audio) { + maintain_pitch->setChecked(c->speed().maintain_audio_pitch); + multiple_audio = true; + } else if (!maintain_pitch->isTristate() && maintain_pitch->isChecked() != c->speed().maintain_audio_pitch) { + maintain_pitch->setCheckState(Qt::PartiallyChecked); + maintain_pitch->setTristate(true); + } + } + + if (i == 0) { + reverse->setChecked(c->reversed()); + } else if (c->reversed() != reverse->isChecked()) { + reverse->setTristate(true); + reverse->setCheckState(Qt::PartiallyChecked); + } + + // get default length + long clip_default_length = qRound(c->length() * clip_percent); + if (i == 0) { + current_length = c->length(); + default_length = clip_default_length; + current_percent = clip_percent; + } else { + if (current_length != -1 && c->length() != current_length) { + current_length = -1; + } + if (default_length != -1 && clip_default_length != default_length) { + default_length = -1; + } + if (!qIsNaN(current_percent) && !qFuzzyCompare(clip_percent, current_percent)) { + current_percent = qSNaN(); + } + } + } + + frame_rate->SetMinimum(1); + percent->SetMinimum(0.0001); + duration->SetMinimum(1); + + frame_rate->setEnabled(enable_frame_rate); + frame_rate->SetDefault(default_frame_rate); + frame_rate->SetValue(current_frame_rate); + percent->SetValue(current_percent); + duration->SetDefault(default_length); + duration->SetValue((current_length == -1) ? qSNaN() : current_length); + + return QDialog::exec(); +} + +void SpeedDialog::percent_update() { + bool got_fr = false; + double fr_val = qSNaN(); + long len_val = -1; + + for (int i=0;iisEnabled() && 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)) { + fr_val = qSNaN(); + } + } else { + fr_val = clip_fr; + got_fr = true; + } + } + + // get duration + long clip_default_length = qRound(c->length() * c->speed().value); + long new_clip_length = qRound(clip_default_length / percent->value()); + if (i == 0) { + len_val = new_clip_length; + } else if (len_val > -1 && len_val != new_clip_length) { + len_val = -1; + } + } + + frame_rate->SetValue(fr_val); + duration->SetValue((len_val == -1) ? qSNaN() : len_val); +} + +void SpeedDialog::duration_update() { + double pc_val = qSNaN(); + bool got_fr = false; + double fr_val = qSNaN(); + + for (int i=0;ilength() * c->speed().value); + double clip_pc = clip_default_length / duration->value(); + if (i == 0) { + pc_val = clip_pc; + } else if (!qIsNaN(pc_val) && !qFuzzyCompare(clip_pc, pc_val)) { + pc_val = qSNaN(); + } + + // get frame rate + 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)) { + fr_val = qSNaN(); + } + } else { + fr_val = clip_fr; + got_fr = true; + } + } + } + + frame_rate->SetValue(fr_val); + percent->SetValue(pc_val); +} + +void SpeedDialog::frame_rate_update() { + /*double fr = (frame_rate->value()); + double pc = (fr / default_frame_rate); + percent->set_value(pc, false); + duration->set_value(default_length / pc, false);*/ + + double old_pc_val = qSNaN(); + bool got_pc_val = false; + double pc_val = qSNaN(); + bool got_len_val = false; + long len_val = -1; + + // analyze video clips + for (int i=0;ispeed().value; + } else if (!qIsNaN(old_pc_val) && !qFuzzyCompare(c->speed().value, old_pc_val)) { + old_pc_val = qSNaN(); + } + + 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) { + pc_val = new_clip_speed; + got_pc_val = true; + } else if (!qIsNaN(pc_val) && !qFuzzyCompare(pc_val, new_clip_speed)) { + pc_val = qSNaN(); + } + + // what would be the new length based on this speed + long new_clip_len = (c->length() * c->speed().value) / new_clip_speed; + if (!got_len_val) { + len_val = new_clip_len; + got_len_val = true; + } else if (len_val > -1 && new_clip_len != len_val) { + len_val = -1; + } + } + } + + // analyze audio clips + for (int i=0;itype() == olive::kTypeAudio) { + + long new_clip_len = (qIsNaN(old_pc_val) || qIsNaN(pc_val)) ? + c->length() : qRound((c->length() * c->speed().value) / pc_val); + + if (len_val > -1 && new_clip_len != len_val) { + len_val = -1; + break; + } + } + } + + percent->SetValue(pc_val); + duration->SetValue((len_val == -1) ? qSNaN() : len_val); +} + +void set_speed(ComboAction* ca, Clip* c, double speed, bool ripple, long& ep, long& lr) { + 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()) { + 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) { + proposed_out = compare->timeline_in(); + } + } + } + ep = qMin(ep, c->timeline_out()); + lr = qMax(lr, proposed_out - c->timeline_out()); + 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); + + c->track()->SelectArea(c->timeline_in(), proposed_out); +} + +void SpeedDialog::accept() { + ComboAction* ca = new ComboAction(); + + // undoable action for setting "maintain audio pitch" + SetClipProperty* audio_pitch_action = new SetClipProperty(kSetClipPropertyMaintainAudioPitch); + + // undoable action for setting "reversed" + SetClipProperty* reversed_action = new SetClipProperty(kSetClipPropertyReversed); + + // undoable action for restoring clip selections + Sequence* sequence = clips_.first()->track()->sequence(); + QVector old_selections = sequence->Selections(); + + // variables used to calculate ripples + long earliest_point = LONG_MAX; + long longest_ripple = LONG_MIN; + + for (int i=0;iIsOpen()) { + c->Close(true); + } + + // set maintain audio pitch if the user made a selection + 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()); + } + + // 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->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()); + } + } + + // setting the actual speed + if (!qIsNaN(percent->value())) { + + // if we have a percentage value, use that on all the clips + for (int i=0;ivalue(), ripple->isChecked(), earliest_point, longest_ripple); + } + + } else if (!qIsNaN(frame_rate->value())) { + + // if the user changed the speed by changing the frame rate, + bool can_change_all = true; + double cached_speed = clips_.first()->speed().value; + double cached_fr = qSNaN(); + + // see if we can use the frame rate to change all the speeds + for (int i=0;i 0 && !qFuzzyCompare(cached_speed, c->speed().value)) { + can_change_all = false; + } + if (c->type() == olive::kTypeVideo) { + if (qIsNaN(cached_fr)) { + cached_fr = c->media_frame_rate(); + } else if (!qFuzzyCompare(cached_fr, c->media_frame_rate())) { + can_change_all = false; + break; + } + } + } + + // make changes + for (int i=0;itype() == 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); + } + } + } else if (!qIsNaN(duration->value())) { + // simply set duration + for (int i=0;ilength() * c->speed().value) / duration->value(), ripple->isChecked(), earliest_point, longest_ripple); + } + } + + if (ripple->isChecked()) { + sequence->Ripple(ca, earliest_point, longest_ripple); + } + + ca->append(new SetSelectionsCommand(sequence, old_selections, sequence->Selections())); + + ca->append(reversed_action); + ca->append(audio_pitch_action); + + olive::undo_stack.push(ca); + + update_ui(true); + QDialog::accept(); +} diff --git a/dialogs/speeddialog.h b/dialogs/speeddialog.h index 5ef9144e6..27d3765bb 100644 --- a/dialogs/speeddialog.h +++ b/dialogs/speeddialog.h @@ -1,132 +1,132 @@ -/*** - - 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 SPEEDDIALOG_H -#define SPEEDDIALOG_H - -#include -#include - -#include "timeline/clip.h" -#include "ui/labelslider.h" - -/** - * @brief The SpeedDialog class - * - * A dialog for setting the speed of one or more Clips. This can be run from anywhere provided it's given a valid - * array of Clips. - * - * It's preferable to - */ -class SpeedDialog : public QDialog -{ - Q_OBJECT -public: - /** - * @brief SpeedDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow or Timeline panel. - * - * @param clips - * - * A valid array of Clips to change the speed of. - */ - SpeedDialog(QWidget* parent, QVector clips); -public slots: - /** - * @brief Override of exec() to set up current Clip speed data just before opening - * - * @return - * - * The result of QDialog::exec(), a DialogCode result. - */ - virtual int exec() override; -private slots: - /** - * @brief Override of accept() to perform the selected changes on the Clips - */ - virtual void accept() override; - - /** - * @brief Slot when the speed percentage field is changed by the user - * - * The three fields (percent, duration, and frame rate) all work in tandem to create a speed multipler for the - * Clip. Each has a slot for when one of the fields changes to update the others appropriately so they all have the - * same speed multipler. - */ - void percent_update(); - - /** - * @brief Slot when the duration field is changed by the user - * - * The three fields (percent, duration, and frame rate) all work in tandem to create a speed multipler for the - * Clip. Each has a slot for when one of the fields changes to update the others appropriately so they all have the - * same speed multipler. - */ - void duration_update(); - - /** - * @brief Slot when the frame rate field is changed by the user - * - * The three fields (percent, duration, and frame rate) all work in tandem to create a speed multipler for the - * Clip. Each has a slot for when one of the fields changes to update the others appropriately so they all have the - * same speed multipler. - */ - void frame_rate_update(); -private: - /** - * @brief Internal array of Clip objects - */ - QVector clips_; - - /** - * @brief Speed percentage field - */ - LabelSlider* percent; - - /** - * @brief Duration field - */ - LabelSlider* duration; - - /** - * @brief Frame rate field - */ - LabelSlider* frame_rate; - - /** - * @brief UI widget for setting the Clip's reverse value - */ - QCheckBox* reverse; - - /** - * @brief UI widget for setting the Clip's maintain pitch value - */ - QCheckBox* maintain_pitch; - - /** - * @brief UI widget for setting whether to ripple Clips around these changes or not - */ - QCheckBox* ripple; -}; - -#endif // SPEEDDIALOG_H +/*** + + 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 SPEEDDIALOG_H +#define SPEEDDIALOG_H + +#include +#include + +#include "timeline/clip.h" +#include "ui/labelslider.h" + +/** + * @brief The SpeedDialog class + * + * A dialog for setting the speed of one or more Clips. This can be run from anywhere provided it's given a valid + * array of Clips. + * + * It's preferable to + */ +class SpeedDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @brief SpeedDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow or Timeline panel. + * + * @param clips + * + * A valid array of Clips to change the speed of. + */ + SpeedDialog(QWidget* parent, QVector clips); +public slots: + /** + * @brief Override of exec() to set up current Clip speed data just before opening + * + * @return + * + * The result of QDialog::exec(), a DialogCode result. + */ + virtual int exec() override; +private slots: + /** + * @brief Override of accept() to perform the selected changes on the Clips + */ + virtual void accept() override; + + /** + * @brief Slot when the speed percentage field is changed by the user + * + * The three fields (percent, duration, and frame rate) all work in tandem to create a speed multipler for the + * Clip. Each has a slot for when one of the fields changes to update the others appropriately so they all have the + * same speed multipler. + */ + void percent_update(); + + /** + * @brief Slot when the duration field is changed by the user + * + * The three fields (percent, duration, and frame rate) all work in tandem to create a speed multipler for the + * Clip. Each has a slot for when one of the fields changes to update the others appropriately so they all have the + * same speed multipler. + */ + void duration_update(); + + /** + * @brief Slot when the frame rate field is changed by the user + * + * The three fields (percent, duration, and frame rate) all work in tandem to create a speed multipler for the + * Clip. Each has a slot for when one of the fields changes to update the others appropriately so they all have the + * same speed multipler. + */ + void frame_rate_update(); +private: + /** + * @brief Internal array of Clip objects + */ + QVector clips_; + + /** + * @brief Speed percentage field + */ + LabelSlider* percent; + + /** + * @brief Duration field + */ + LabelSlider* duration; + + /** + * @brief Frame rate field + */ + LabelSlider* frame_rate; + + /** + * @brief UI widget for setting the Clip's reverse value + */ + QCheckBox* reverse; + + /** + * @brief UI widget for setting the Clip's maintain pitch value + */ + QCheckBox* maintain_pitch; + + /** + * @brief UI widget for setting whether to ripple Clips around these changes or not + */ + QCheckBox* ripple; +}; + +#endif // SPEEDDIALOG_H diff --git a/dialogs/texteditdialog.cpp b/dialogs/texteditdialog.cpp index fc5638f1d..281688a32 100644 --- a/dialogs/texteditdialog.cpp +++ b/dialogs/texteditdialog.cpp @@ -1,213 +1,213 @@ -/*** - - 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 "texteditdialog.h" - -#include -#include -#include -#include -#include - -#include "ui/icons.h" - -TextEditDialog::TextEditDialog(QWidget *parent, const QString &s, bool rich_text) : - QDialog(parent), - rich_text_(rich_text) -{ - setWindowTitle(tr("Edit Text")); - - QVBoxLayout* layout = new QVBoxLayout(this); - - // Create central text editor object - textEdit = new QTextEdit(this); - textEdit->setUndoRedoEnabled(true); - - // Upper toolbar - if (rich_text) { - QHBoxLayout* toolbar = new QHBoxLayout(); - - // Italic Button - italic_button = new QPushButton(); - italic_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/italic.svg", false)); - italic_button->setCheckable(true); - connect(italic_button, SIGNAL(clicked(bool)), textEdit, SLOT(setFontItalic(bool))); - toolbar->addWidget(italic_button); - - // Underline Button - underline_button = new QPushButton(); - underline_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/underline.svg", false)); - underline_button->setCheckable(true); - connect(underline_button, SIGNAL(clicked(bool)), textEdit, SLOT(setFontUnderline(bool))); - toolbar->addWidget(underline_button); - - // Font Name - font_list = new QFontComboBox(); - connect(font_list, SIGNAL(currentIndexChanged(const QString&)), textEdit, SLOT(setFontFamily(const QString&))); - toolbar->addWidget(font_list); - - // Font Weight - font_weight = new QComboBox(); - font_weight->addItem(tr("Thin"), QFont::Thin); - font_weight->addItem(tr("Extra Light"), QFont::ExtraLight); - font_weight->addItem(tr("Light"), QFont::Light); - font_weight->addItem(tr("Normal"), QFont::Normal); - font_weight->addItem(tr("Medium"), QFont::Medium); - font_weight->addItem(tr("Demi Bold"), QFont::DemiBold); - font_weight->addItem(tr("Bold"), QFont::Bold); - font_weight->addItem(tr("Extra Bold"), QFont::ExtraBold); - font_weight->addItem(tr("Black"), QFont::Black); - connect(font_weight, SIGNAL(currentIndexChanged(int)), this, SLOT(SetFontWeight(int))); - toolbar->addWidget(font_weight); - - // Font Size - font_size = new LabelSlider(); - connect(font_size, SIGNAL(valueChanged(double)), textEdit, SLOT(setFontPointSize(qreal))); - toolbar->addWidget(font_size); - - // Font Color - font_color = new ColorButton(); - connect(font_color, SIGNAL(color_changed(const QColor&)), textEdit, SLOT(setTextColor(const QColor &))); - toolbar->addWidget(font_color); - - toolbar->addStretch(); - - // Left Align - left_align_button = new QPushButton(); - left_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/align-left.svg", false)); - left_align_button->setCheckable(true); - left_align_button->setProperty("a", Qt::AlignLeft); - connect(left_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); - toolbar->addWidget(left_align_button); - - // Center Align - center_align_button = new QPushButton(); - center_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/align-center.svg", false)); - center_align_button->setCheckable(true); - center_align_button->setProperty("a", Qt::AlignCenter); - connect(center_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); - toolbar->addWidget(center_align_button); - - // Right Align - right_align_button = new QPushButton(); - right_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/align-right.svg", false)); - right_align_button->setCheckable(true); - right_align_button->setProperty("a", Qt::AlignRight); - connect(right_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); - toolbar->addWidget(right_align_button); - - // Justify Align - justify_align_button = new QPushButton(); - justify_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/justify-center.svg", false)); - justify_align_button->setCheckable(true); - justify_align_button->setProperty("a", Qt::AlignJustify); - connect(justify_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); - toolbar->addWidget(justify_align_button); - - layout->addLayout(toolbar); - } - - layout->addWidget(textEdit); - - // Lower toolbar - /* - if (rich_text) { - QHBoxLayout* lower_toolbar = new QHBoxLayout(); - - lower_toolbar->addWidget(new QLabel(tr("Letter Spacing:"))); - - letter_spacing = new LabelSlider(); - connect(letter_spacing, SIGNAL(valueChanged(double)), this, SLOT(SetLetterSpacing(qreal))); - lower_toolbar->addWidget(letter_spacing); - - lower_toolbar->addStretch(); - - layout->addLayout(lower_toolbar); - } - */ - - // Create dialog buttons at the bottom - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - buttons->setCenterButtons(true); - layout->addWidget(buttons); - connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); - - // Connect the cursor position changing to the rich text toolbar buttons updating (so for example, when italic text - // is selected, the italic button will be pressed) - connect(textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(UpdateUIFromTextCursor())); - - // Set the widget's text based on the rich text mode - if (rich_text_) { - textEdit->setHtml(s); - } else { - textEdit->setPlainText(s); - } - - // Helps ensure the UI elements update correctly at the beginning - when the cursor is at the start, the UI elements - // show up blank. Setting it to the end is probably more expected behavior anyway. - textEdit->moveCursor(QTextCursor::End); -} - -const QString& TextEditDialog::get_string() { - return result_str; -} - -void TextEditDialog::accept() { - result_str = rich_text_ ? textEdit->toHtml() : textEdit->toPlainText(); - QDialog::accept(); -} - -void TextEditDialog::SetFontWeight(int i) -{ - textEdit->setFontWeight(font_weight->itemData(i).toInt()); -} - -void TextEditDialog::SetAlignmentFromProperty() -{ - textEdit->setAlignment(static_cast(sender()->property("a").toInt())); - UpdateUIFromTextCursor(); -} - -void TextEditDialog::UpdateUIFromTextCursor() -{ - if (rich_text_) { - italic_button->setChecked(textEdit->fontItalic()); - underline_button->setChecked(textEdit->fontUnderline()); - font_list->setCurrentText(textEdit->fontFamily()); - font_size->SetValue(textEdit->fontPointSize()); - font_color->set_color(textEdit->textColor()); - - for (int i=0;icount();i++) { - if (font_weight->itemData(i).toInt() == textEdit->fontWeight()) { - font_weight->blockSignals(true); - font_weight->setCurrentIndex(i); - font_weight->blockSignals(false); - break; - } - } - - Qt::Alignment align = textEdit->alignment(); - left_align_button->setChecked(align == Qt::AlignLeft); - center_align_button->setChecked(align == Qt::AlignCenter); - right_align_button->setChecked(align == Qt::AlignRight); - justify_align_button->setChecked(align == Qt::AlignJustify); - } -} +/*** + + 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 "texteditdialog.h" + +#include +#include +#include +#include +#include + +#include "ui/icons.h" + +TextEditDialog::TextEditDialog(QWidget *parent, const QString &s, bool rich_text) : + QDialog(parent), + rich_text_(rich_text) +{ + setWindowTitle(tr("Edit Text")); + + QVBoxLayout* layout = new QVBoxLayout(this); + + // Create central text editor object + textEdit = new QTextEdit(this); + textEdit->setUndoRedoEnabled(true); + + // Upper toolbar + if (rich_text) { + QHBoxLayout* toolbar = new QHBoxLayout(); + + // Italic Button + italic_button = new QPushButton(); + italic_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/italic.svg", false)); + italic_button->setCheckable(true); + connect(italic_button, SIGNAL(clicked(bool)), textEdit, SLOT(setFontItalic(bool))); + toolbar->addWidget(italic_button); + + // Underline Button + underline_button = new QPushButton(); + underline_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/underline.svg", false)); + underline_button->setCheckable(true); + connect(underline_button, SIGNAL(clicked(bool)), textEdit, SLOT(setFontUnderline(bool))); + toolbar->addWidget(underline_button); + + // Font Name + font_list = new QFontComboBox(); + connect(font_list, SIGNAL(currentIndexChanged(const QString&)), textEdit, SLOT(setFontFamily(const QString&))); + toolbar->addWidget(font_list); + + // Font Weight + font_weight = new QComboBox(); + font_weight->addItem(tr("Thin"), QFont::Thin); + font_weight->addItem(tr("Extra Light"), QFont::ExtraLight); + font_weight->addItem(tr("Light"), QFont::Light); + font_weight->addItem(tr("Normal"), QFont::Normal); + font_weight->addItem(tr("Medium"), QFont::Medium); + font_weight->addItem(tr("Demi Bold"), QFont::DemiBold); + font_weight->addItem(tr("Bold"), QFont::Bold); + font_weight->addItem(tr("Extra Bold"), QFont::ExtraBold); + font_weight->addItem(tr("Black"), QFont::Black); + connect(font_weight, SIGNAL(currentIndexChanged(int)), this, SLOT(SetFontWeight(int))); + toolbar->addWidget(font_weight); + + // Font Size + font_size = new LabelSlider(); + connect(font_size, SIGNAL(valueChanged(double)), textEdit, SLOT(setFontPointSize(qreal))); + toolbar->addWidget(font_size); + + // Font Color + font_color = new ColorButton(); + connect(font_color, SIGNAL(color_changed(const QColor&)), textEdit, SLOT(setTextColor(const QColor &))); + toolbar->addWidget(font_color); + + toolbar->addStretch(); + + // Left Align + left_align_button = new QPushButton(); + left_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/align-left.svg", false)); + left_align_button->setCheckable(true); + left_align_button->setProperty("a", Qt::AlignLeft); + connect(left_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); + toolbar->addWidget(left_align_button); + + // Center Align + center_align_button = new QPushButton(); + center_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/align-center.svg", false)); + center_align_button->setCheckable(true); + center_align_button->setProperty("a", Qt::AlignCenter); + connect(center_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); + toolbar->addWidget(center_align_button); + + // Right Align + right_align_button = new QPushButton(); + right_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/align-right.svg", false)); + right_align_button->setCheckable(true); + right_align_button->setProperty("a", Qt::AlignRight); + connect(right_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); + toolbar->addWidget(right_align_button); + + // Justify Align + justify_align_button = new QPushButton(); + justify_align_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/justify-center.svg", false)); + justify_align_button->setCheckable(true); + justify_align_button->setProperty("a", Qt::AlignJustify); + connect(justify_align_button, SIGNAL(clicked(bool)), this, SLOT(SetAlignmentFromProperty())); + toolbar->addWidget(justify_align_button); + + layout->addLayout(toolbar); + } + + layout->addWidget(textEdit); + + // Lower toolbar + /* + if (rich_text) { + QHBoxLayout* lower_toolbar = new QHBoxLayout(); + + lower_toolbar->addWidget(new QLabel(tr("Letter Spacing:"))); + + letter_spacing = new LabelSlider(); + connect(letter_spacing, SIGNAL(valueChanged(double)), this, SLOT(SetLetterSpacing(qreal))); + lower_toolbar->addWidget(letter_spacing); + + lower_toolbar->addStretch(); + + layout->addLayout(lower_toolbar); + } + */ + + // Create dialog buttons at the bottom + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttons->setCenterButtons(true); + layout->addWidget(buttons); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + + // Connect the cursor position changing to the rich text toolbar buttons updating (so for example, when italic text + // is selected, the italic button will be pressed) + connect(textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(UpdateUIFromTextCursor())); + + // Set the widget's text based on the rich text mode + if (rich_text_) { + textEdit->setHtml(s); + } else { + textEdit->setPlainText(s); + } + + // Helps ensure the UI elements update correctly at the beginning - when the cursor is at the start, the UI elements + // show up blank. Setting it to the end is probably more expected behavior anyway. + textEdit->moveCursor(QTextCursor::End); +} + +const QString& TextEditDialog::get_string() { + return result_str; +} + +void TextEditDialog::accept() { + result_str = rich_text_ ? textEdit->toHtml() : textEdit->toPlainText(); + QDialog::accept(); +} + +void TextEditDialog::SetFontWeight(int i) +{ + textEdit->setFontWeight(font_weight->itemData(i).toInt()); +} + +void TextEditDialog::SetAlignmentFromProperty() +{ + textEdit->setAlignment(static_cast(sender()->property("a").toInt())); + UpdateUIFromTextCursor(); +} + +void TextEditDialog::UpdateUIFromTextCursor() +{ + if (rich_text_) { + italic_button->setChecked(textEdit->fontItalic()); + underline_button->setChecked(textEdit->fontUnderline()); + font_list->setCurrentText(textEdit->fontFamily()); + font_size->SetValue(textEdit->fontPointSize()); + font_color->set_color(textEdit->textColor()); + + for (int i=0;icount();i++) { + if (font_weight->itemData(i).toInt() == textEdit->fontWeight()) { + font_weight->blockSignals(true); + font_weight->setCurrentIndex(i); + font_weight->blockSignals(false); + break; + } + } + + Qt::Alignment align = textEdit->alignment(); + left_align_button->setChecked(align == Qt::AlignLeft); + center_align_button->setChecked(align == Qt::AlignCenter); + right_align_button->setChecked(align == Qt::AlignRight); + justify_align_button->setChecked(align == Qt::AlignJustify); + } +} diff --git a/dialogs/texteditdialog.h b/dialogs/texteditdialog.h index ff3cd8776..6f6e2de07 100644 --- a/dialogs/texteditdialog.h +++ b/dialogs/texteditdialog.h @@ -1,179 +1,179 @@ -/*** - - 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 TEXTEDITDIALOG_H -#define TEXTEDITDIALOG_H - -#include -#include -#include - -#include "ui/labelslider.h" -#include "ui/colorbutton.h" - -/** - * @brief The TextEditDialog class - * - * A separate window for editing text. This window can be resized arbitrarily and also provides a toolbar for rich text - * editing (if rich text is enabled). This dialog can be run from anywhere. Once the dialog has closed (i.e. returned - * from exec() ), the text entered into it can be retrieved using get_string(). - * - * TODO: Add a live signal for updating the calling function. - */ -class TextEditDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief TextEditDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow. - * - * @param s - * - * The starting string when the dialog opens. It'll be read as rich text HTML or plain text based on the `rich_text` - * parameter (which defaults to rich text HTML). It can also be left empty to start blank. - * - * @param rich_text - * - * Set the editing mode of the editor. If TRUE, the dialog will interpret the string in `s` as rich text HTML and also - * return rich text HTML through get_string(). It'll also show a toolbar with rich text options (i.e. font, italic, - * underline, size, etc.) If FALSE, the dialog will run in plain text mode interpreting the string in `s` as plain - * text and returning plain text through get_string(). It also will not show the rich text editing toolbar. - */ - TextEditDialog(QWidget* parent = nullptr, const QString& s = nullptr, bool rich_text = true); - - /** - * @brief Retrieve the current text in the dialog - * - * This function can be called after the user has accepted the dialog (i.e. made changes and clicked OK). - * This will return either plain text or rich text (HTML) depending on the mode it's running in (rich/plain text mode - * is set in the constructor). The value this returns only gets updated when the user clicks OK so it cannot be - * used to retrieve live text updates from the dialog. - * - * @return - * - * The text entered once the user accepted this dialog. - */ - const QString& get_string(); -private slots: - /** - * @brief Override of accept() to store the entered text string so it can be retrieved by get_string(). - */ - virtual void accept() override; - - /** - * @brief Slot for the font_weight combobox to set the font weight based on its data value - * - * @param i - * - * Index of the font_weight to retrieve the desired font weight from - */ - void SetFontWeight(int i); - - /** - * @brief Slot for text alignment buttons to set alignment based on their properties - * - * Intended slot for left_align_button, center_align_button, right_align_button, and justify_align_button. Pulls - * from their property("a") value which should be a member of the Qt::Alignment enum. - */ - void SetAlignmentFromProperty(); - - /** - * @brief Slot for when the text edit widget's cursor moves so the rich text toolbar can stay up to date - * - * In rich text mode, different parts of a text document can be formatted in different ways. As the user moves - * around the text, the UI buttons should be consistent with whatever text is currently selected. This slot should - * therefore be connected to QTextEdit::cursorPositionChanged() and will change the "checked" state of the formatting - * buttons and current index of the comboboxes to match the currently selected text. - */ - void UpdateUIFromTextCursor(); -private: - - /** - * @brief Internal rich text mode value - * - * This is set in the constructor and cannot be changed during the lifetime of this dialog. - */ - bool rich_text_; - - /** - * @brief Internal storage of text entered, saved when the user clicks OK - */ - QString result_str; - - /** - * @brief Main text editing widget - */ - QTextEdit* textEdit; - - /** - * @brief Toggle button for setting the italic state of the currently selected text - */ - QPushButton* italic_button; - - /** - * @brief Toggle button for setting the underlined state of the currently selected text - */ - QPushButton* underline_button; - - /** - * @brief ComboBox for the list of font families that the selected text can be set to - */ - QFontComboBox* font_list; - - /** - * @brief ComboBox for the list of font weights that the selected text can be set to - */ - QComboBox* font_weight; - - /** - * @brief A slider to set the current font size - */ - LabelSlider* font_size; - - /** - * @brief A color selector for setting the current text color - */ - ColorButton* font_color; - - /** - * @brief Button for setting the current text row(s) to left alignment - */ - QPushButton* left_align_button; - - /** - * @brief Button for setting the current text row(s) to center alignment - */ - QPushButton* center_align_button; - - /** - * @brief Button for setting the current text row(s) to right alignment - */ - QPushButton* right_align_button; - - /** - * @brief Button for setting the current text row(s) to justified alignment - */ - QPushButton* justify_align_button; -}; - -#endif // TEXTEDITDIALOG_H +/*** + + 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 TEXTEDITDIALOG_H +#define TEXTEDITDIALOG_H + +#include +#include +#include + +#include "ui/labelslider.h" +#include "ui/colorbutton.h" + +/** + * @brief The TextEditDialog class + * + * A separate window for editing text. This window can be resized arbitrarily and also provides a toolbar for rich text + * editing (if rich text is enabled). This dialog can be run from anywhere. Once the dialog has closed (i.e. returned + * from exec() ), the text entered into it can be retrieved using get_string(). + * + * TODO: Add a live signal for updating the calling function. + */ +class TextEditDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief TextEditDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow. + * + * @param s + * + * The starting string when the dialog opens. It'll be read as rich text HTML or plain text based on the `rich_text` + * parameter (which defaults to rich text HTML). It can also be left empty to start blank. + * + * @param rich_text + * + * Set the editing mode of the editor. If TRUE, the dialog will interpret the string in `s` as rich text HTML and also + * return rich text HTML through get_string(). It'll also show a toolbar with rich text options (i.e. font, italic, + * underline, size, etc.) If FALSE, the dialog will run in plain text mode interpreting the string in `s` as plain + * text and returning plain text through get_string(). It also will not show the rich text editing toolbar. + */ + TextEditDialog(QWidget* parent = nullptr, const QString& s = nullptr, bool rich_text = true); + + /** + * @brief Retrieve the current text in the dialog + * + * This function can be called after the user has accepted the dialog (i.e. made changes and clicked OK). + * This will return either plain text or rich text (HTML) depending on the mode it's running in (rich/plain text mode + * is set in the constructor). The value this returns only gets updated when the user clicks OK so it cannot be + * used to retrieve live text updates from the dialog. + * + * @return + * + * The text entered once the user accepted this dialog. + */ + const QString& get_string(); +private slots: + /** + * @brief Override of accept() to store the entered text string so it can be retrieved by get_string(). + */ + virtual void accept() override; + + /** + * @brief Slot for the font_weight combobox to set the font weight based on its data value + * + * @param i + * + * Index of the font_weight to retrieve the desired font weight from + */ + void SetFontWeight(int i); + + /** + * @brief Slot for text alignment buttons to set alignment based on their properties + * + * Intended slot for left_align_button, center_align_button, right_align_button, and justify_align_button. Pulls + * from their property("a") value which should be a member of the Qt::Alignment enum. + */ + void SetAlignmentFromProperty(); + + /** + * @brief Slot for when the text edit widget's cursor moves so the rich text toolbar can stay up to date + * + * In rich text mode, different parts of a text document can be formatted in different ways. As the user moves + * around the text, the UI buttons should be consistent with whatever text is currently selected. This slot should + * therefore be connected to QTextEdit::cursorPositionChanged() and will change the "checked" state of the formatting + * buttons and current index of the comboboxes to match the currently selected text. + */ + void UpdateUIFromTextCursor(); +private: + + /** + * @brief Internal rich text mode value + * + * This is set in the constructor and cannot be changed during the lifetime of this dialog. + */ + bool rich_text_; + + /** + * @brief Internal storage of text entered, saved when the user clicks OK + */ + QString result_str; + + /** + * @brief Main text editing widget + */ + QTextEdit* textEdit; + + /** + * @brief Toggle button for setting the italic state of the currently selected text + */ + QPushButton* italic_button; + + /** + * @brief Toggle button for setting the underlined state of the currently selected text + */ + QPushButton* underline_button; + + /** + * @brief ComboBox for the list of font families that the selected text can be set to + */ + QFontComboBox* font_list; + + /** + * @brief ComboBox for the list of font weights that the selected text can be set to + */ + QComboBox* font_weight; + + /** + * @brief A slider to set the current font size + */ + LabelSlider* font_size; + + /** + * @brief A color selector for setting the current text color + */ + ColorButton* font_color; + + /** + * @brief Button for setting the current text row(s) to left alignment + */ + QPushButton* left_align_button; + + /** + * @brief Button for setting the current text row(s) to center alignment + */ + QPushButton* center_align_button; + + /** + * @brief Button for setting the current text row(s) to right alignment + */ + QPushButton* right_align_button; + + /** + * @brief Button for setting the current text row(s) to justified alignment + */ + QPushButton* justify_align_button; +}; + +#endif // TEXTEDITDIALOG_H diff --git a/effects/effectfield.cpp b/effects/effectfield.cpp index d824f1cf8..10db09797 100644 --- a/effects/effectfield.cpp +++ b/effects/effectfield.cpp @@ -1,344 +1,344 @@ -/*** - - 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 "effectfield.h" - -#include -#include -#include - -#include "rendering/renderfunctions.h" -#include "global/config.h" -#include "global/timing.h" -#include "nodes/nodeio.h" -#include "nodes/oldeffectnode.h" -#include "undo/undo.h" -#include "timeline/clip.h" -#include "timeline/sequence.h" -#include "global/math.h" -#include "global/debug.h" - -EffectField::EffectField(NodeIO* parent, EffectFieldType t) : - QObject(parent), - type_(t), - enabled_(true) -{ - // EffectField MUST be created with a parent. - Q_ASSERT(parent != nullptr); - - // Set a very base default value - SetValueAt(0, 0); - - // Connect this field to the effect's changed function - connect(this, SIGNAL(Changed()), parent->ParentNode(), SLOT(FieldChanged())); -} - -NodeIO *EffectField::GetParentRow() -{ - return static_cast(parent()); -} - -QVariant EffectField::ConvertStringToValue(const QString &s) -{ - return s; -} - -QString EffectField::ConvertValueToString(const QVariant &v) -{ - return v.toString(); -} - -void EffectField::UpdateWidgetValue(QWidget *, double) {} - -QVariant EffectField::GetValueAt(double timecode) -{ - if (HasKeyframes()) { - int before_keyframe; - int after_keyframe; - double progress; - GetKeyframeData(timecode, before_keyframe, after_keyframe, progress); - - const QVariant& before_data = keyframes.at(before_keyframe).data; - switch (type_) { - case EFFECT_FIELD_DOUBLE: - { - double value; - if (before_keyframe == after_keyframe) { - value = keyframes.at(before_keyframe).data.toDouble(); - } else { - const EffectKeyframe& before_key = keyframes.at(before_keyframe); - const EffectKeyframe& after_key = keyframes.at(after_keyframe); - - double before_dbl = before_key.data.toDouble(); - double after_dbl = after_key.data.toDouble(); - - if (before_key.type == EFFECT_KEYFRAME_HOLD) { - - // Hold keyframes will always return the previous keyframe with no interpolation - value = before_dbl; - - } else if (before_key.type == EFFECT_KEYFRAME_BEZIER || after_key.type == EFFECT_KEYFRAME_BEZIER) { - - // bezier interpolation - if (before_key.type == EFFECT_KEYFRAME_BEZIER && after_key.type == EFFECT_KEYFRAME_BEZIER) { - - // cubic bezier - double t = cubic_t_from_x(timecode, - before_key.time, - before_key.time+GetValidKeyframeHandlePosition(before_keyframe, true), - after_key.time+GetValidKeyframeHandlePosition(after_keyframe, false), - after_key.time); - - value = cubic_from_t(before_dbl, - before_dbl+before_key.post_handle.y(), - after_dbl+after_key.pre_handle.y(), - after_dbl, - t); - - } else if (after_key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier - - // last keyframe is the bezier one - double t = quad_t_from_x(timecode, - before_key.time, - before_key.time+GetValidKeyframeHandlePosition(before_keyframe, true), - after_key.time); - - value = quad_from_t(before_dbl, - before_dbl+before_key.post_handle.y(), - after_dbl, - t); - - } else { - // this keyframe is the bezier one - double t = quad_t_from_x(timecode, - before_key.time, - after_key.time+GetValidKeyframeHandlePosition(after_keyframe, false), - after_key.time); - - value = quad_from_t(before_dbl, - after_dbl+after_key.pre_handle.y(), - after_dbl, - t); - } - } else { - - // Linear interpolation (default) - value = double_lerp(before_dbl, after_dbl, progress); - - } - } - persistent_data_ = value; - break; - } - case EFFECT_FIELD_COLOR: - { - QColor value; - if (before_keyframe == after_keyframe) { - value = keyframes.at(before_keyframe).data.value(); - } 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)); - } - persistent_data_ = value; - break; - } - case EFFECT_FIELD_STRING: - case EFFECT_FIELD_BOOL: - case EFFECT_FIELD_COMBO: - case EFFECT_FIELD_FONT: - case EFFECT_FIELD_FILE: - persistent_data_ = before_data; - break; - default: - break; - } - } - - return persistent_data_; -} - -void EffectField::SetValueAt(double time, const QVariant &value) -{ - if (HasKeyframes()) { - - // Create keyframe here - - // Check array if a keyframe at this time already exists - int keyframe_index = -1; - for (int i=0;iParentNode()->Time(); - key.data = persistent_data_; - key.type = EFFECT_KEYFRAME_LINEAR; - - keyframes.append(key); - - ca->append(new KeyframeAdd(this, keyframes.size()-1)); - - } else { - - // Convert keyframes to one "perpetual" keyframe - - // Set first keyframe to whatever the data is now - ca->append(new SetQVariant(&persistent_data_, persistent_data_, GetValueAt(GetParentRow()->ParentNode()->Time()))); - - // Delete all keyframes - for (int i=0;iappend(new KeyframeDelete(this, 0)); - } - - } -} - -const EffectField::EffectFieldType &EffectField::type() -{ - return type_; -} - -double EffectField::GetValidKeyframeHandlePosition(int key, bool post) { - int comp_key = -1; - - // find keyframe before or after this one - for (int i=0;i keyframes.at(key).time) == post) - && (comp_key == -1 - || ((keyframes.at(i).time < keyframes.at(comp_key).time) == post))) { - // compare with next keyframe for post or previous frame for pre - comp_key = i; - } - } - - double adjusted_key = post ? keyframes.at(key).post_handle.x() : keyframes.at(key).pre_handle.x(); - - // if this is the earliest/latest keyframe, no validation is required - if (comp_key == -1) { - return adjusted_key; - } - - double comp = keyframes.at(comp_key).time - keyframes.at(key).time; - - // if comp keyframe is bezier, validate with its accompanying handle - if (keyframes.at(comp_key).type == EFFECT_KEYFRAME_BEZIER) { - double relative_comp_handle = comp + (post ? keyframes.at(comp_key).pre_handle.x() : keyframes.at(comp_key).post_handle.x()); - // return an average - if ((post && keyframes.at(key).post_handle.x() > relative_comp_handle) - || (!post && keyframes.at(key).pre_handle.x() < relative_comp_handle)) { - adjusted_key = (adjusted_key + relative_comp_handle)*0.5; - } - } - - // don't let handle go beyond the compare keyframe's time - if (post == (adjusted_key > comp)) { - return comp; - } - - if (post == (adjusted_key < 0)) { - return 0; - } - - // original value is valid - return adjusted_key; -} - -void EffectField::GetKeyframeData(double timecode, int &before, int &after, double &progress) { - int before_keyframe_index = -1; - int after_keyframe_index = -1; - double before_keyframe_time = DBL_MIN; - double after_keyframe_time = DBL_MAX; - - for (int i=0;i before_keyframe_time) { - before_keyframe_index = i; - before_keyframe_time = eval_keyframe_time; - } else if (eval_keyframe_time > timecode && eval_keyframe_time < after_keyframe_time) { - after_keyframe_index = i; - after_keyframe_time = eval_keyframe_time; - } - } - - if ((type_ == EFFECT_FIELD_DOUBLE || type_ == EFFECT_FIELD_COLOR) - && (before_keyframe_index > -1 && after_keyframe_index > -1)) { - // interpolate - before = before_keyframe_index; - after = after_keyframe_index; - progress = (timecode-before_keyframe_time)/(after_keyframe_time-before_keyframe_time); - } else if (before_keyframe_index > -1) { - before = before_keyframe_index; - after = before_keyframe_index; - } else { - before = after_keyframe_index; - after = after_keyframe_index; - } -} - -bool EffectField::HasKeyframes() { - return (GetParentRow()->IsKeyframing() && !keyframes.isEmpty()); -} - -bool EffectField::IsEnabled() { - return enabled_; -} - -void EffectField::SetEnabled(bool e) { - enabled_ = e; - emit EnabledChanged(enabled_); -} +/*** + + 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 "effectfield.h" + +#include +#include +#include + +#include "rendering/renderfunctions.h" +#include "global/config.h" +#include "global/timing.h" +#include "nodes/nodeio.h" +#include "nodes/oldeffectnode.h" +#include "undo/undo.h" +#include "timeline/clip.h" +#include "timeline/sequence.h" +#include "global/math.h" +#include "global/debug.h" + +EffectField::EffectField(NodeIO* parent, EffectFieldType t) : + QObject(parent), + type_(t), + enabled_(true) +{ + // EffectField MUST be created with a parent. + Q_ASSERT(parent != nullptr); + + // Set a very base default value + SetValueAt(0, 0); + + // Connect this field to the effect's changed function + connect(this, SIGNAL(Changed()), parent->ParentNode(), SLOT(FieldChanged())); +} + +NodeIO *EffectField::GetParentRow() +{ + return static_cast(parent()); +} + +QVariant EffectField::ConvertStringToValue(const QString &s) +{ + return s; +} + +QString EffectField::ConvertValueToString(const QVariant &v) +{ + return v.toString(); +} + +void EffectField::UpdateWidgetValue(QWidget *, double) {} + +QVariant EffectField::GetValueAt(double timecode) +{ + if (HasKeyframes()) { + int before_keyframe; + int after_keyframe; + double progress; + GetKeyframeData(timecode, before_keyframe, after_keyframe, progress); + + const QVariant& before_data = keyframes.at(before_keyframe).data; + switch (type_) { + case EFFECT_FIELD_DOUBLE: + { + double value; + if (before_keyframe == after_keyframe) { + value = keyframes.at(before_keyframe).data.toDouble(); + } else { + const EffectKeyframe& before_key = keyframes.at(before_keyframe); + const EffectKeyframe& after_key = keyframes.at(after_keyframe); + + double before_dbl = before_key.data.toDouble(); + double after_dbl = after_key.data.toDouble(); + + if (before_key.type == EFFECT_KEYFRAME_HOLD) { + + // Hold keyframes will always return the previous keyframe with no interpolation + value = before_dbl; + + } else if (before_key.type == EFFECT_KEYFRAME_BEZIER || after_key.type == EFFECT_KEYFRAME_BEZIER) { + + // bezier interpolation + if (before_key.type == EFFECT_KEYFRAME_BEZIER && after_key.type == EFFECT_KEYFRAME_BEZIER) { + + // cubic bezier + double t = cubic_t_from_x(timecode, + before_key.time, + before_key.time+GetValidKeyframeHandlePosition(before_keyframe, true), + after_key.time+GetValidKeyframeHandlePosition(after_keyframe, false), + after_key.time); + + value = cubic_from_t(before_dbl, + before_dbl+before_key.post_handle.y(), + after_dbl+after_key.pre_handle.y(), + after_dbl, + t); + + } else if (after_key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier + + // last keyframe is the bezier one + double t = quad_t_from_x(timecode, + before_key.time, + before_key.time+GetValidKeyframeHandlePosition(before_keyframe, true), + after_key.time); + + value = quad_from_t(before_dbl, + before_dbl+before_key.post_handle.y(), + after_dbl, + t); + + } else { + // this keyframe is the bezier one + double t = quad_t_from_x(timecode, + before_key.time, + after_key.time+GetValidKeyframeHandlePosition(after_keyframe, false), + after_key.time); + + value = quad_from_t(before_dbl, + after_dbl+after_key.pre_handle.y(), + after_dbl, + t); + } + } else { + + // Linear interpolation (default) + value = double_lerp(before_dbl, after_dbl, progress); + + } + } + persistent_data_ = value; + break; + } + case EFFECT_FIELD_COLOR: + { + QColor value; + if (before_keyframe == after_keyframe) { + value = keyframes.at(before_keyframe).data.value(); + } 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)); + } + persistent_data_ = value; + break; + } + case EFFECT_FIELD_STRING: + case EFFECT_FIELD_BOOL: + case EFFECT_FIELD_COMBO: + case EFFECT_FIELD_FONT: + case EFFECT_FIELD_FILE: + persistent_data_ = before_data; + break; + default: + break; + } + } + + return persistent_data_; +} + +void EffectField::SetValueAt(double time, const QVariant &value) +{ + if (HasKeyframes()) { + + // Create keyframe here + + // Check array if a keyframe at this time already exists + int keyframe_index = -1; + for (int i=0;iParentNode()->Time(); + key.data = persistent_data_; + key.type = EFFECT_KEYFRAME_LINEAR; + + keyframes.append(key); + + ca->append(new KeyframeAdd(this, keyframes.size()-1)); + + } else { + + // Convert keyframes to one "perpetual" keyframe + + // Set first keyframe to whatever the data is now + ca->append(new SetQVariant(&persistent_data_, persistent_data_, GetValueAt(GetParentRow()->ParentNode()->Time()))); + + // Delete all keyframes + for (int i=0;iappend(new KeyframeDelete(this, 0)); + } + + } +} + +const EffectField::EffectFieldType &EffectField::type() +{ + return type_; +} + +double EffectField::GetValidKeyframeHandlePosition(int key, bool post) { + int comp_key = -1; + + // find keyframe before or after this one + for (int i=0;i keyframes.at(key).time) == post) + && (comp_key == -1 + || ((keyframes.at(i).time < keyframes.at(comp_key).time) == post))) { + // compare with next keyframe for post or previous frame for pre + comp_key = i; + } + } + + double adjusted_key = post ? keyframes.at(key).post_handle.x() : keyframes.at(key).pre_handle.x(); + + // if this is the earliest/latest keyframe, no validation is required + if (comp_key == -1) { + return adjusted_key; + } + + double comp = keyframes.at(comp_key).time - keyframes.at(key).time; + + // if comp keyframe is bezier, validate with its accompanying handle + if (keyframes.at(comp_key).type == EFFECT_KEYFRAME_BEZIER) { + double relative_comp_handle = comp + (post ? keyframes.at(comp_key).pre_handle.x() : keyframes.at(comp_key).post_handle.x()); + // return an average + if ((post && keyframes.at(key).post_handle.x() > relative_comp_handle) + || (!post && keyframes.at(key).pre_handle.x() < relative_comp_handle)) { + adjusted_key = (adjusted_key + relative_comp_handle)*0.5; + } + } + + // don't let handle go beyond the compare keyframe's time + if (post == (adjusted_key > comp)) { + return comp; + } + + if (post == (adjusted_key < 0)) { + return 0; + } + + // original value is valid + return adjusted_key; +} + +void EffectField::GetKeyframeData(double timecode, int &before, int &after, double &progress) { + int before_keyframe_index = -1; + int after_keyframe_index = -1; + double before_keyframe_time = DBL_MIN; + double after_keyframe_time = DBL_MAX; + + for (int i=0;i before_keyframe_time) { + before_keyframe_index = i; + before_keyframe_time = eval_keyframe_time; + } else if (eval_keyframe_time > timecode && eval_keyframe_time < after_keyframe_time) { + after_keyframe_index = i; + after_keyframe_time = eval_keyframe_time; + } + } + + if ((type_ == EFFECT_FIELD_DOUBLE || type_ == EFFECT_FIELD_COLOR) + && (before_keyframe_index > -1 && after_keyframe_index > -1)) { + // interpolate + before = before_keyframe_index; + after = after_keyframe_index; + progress = (timecode-before_keyframe_time)/(after_keyframe_time-before_keyframe_time); + } else if (before_keyframe_index > -1) { + before = before_keyframe_index; + after = before_keyframe_index; + } else { + before = after_keyframe_index; + after = after_keyframe_index; + } +} + +bool EffectField::HasKeyframes() { + return (GetParentRow()->IsKeyframing() && !keyframes.isEmpty()); +} + +bool EffectField::IsEnabled() { + return enabled_; +} + +void EffectField::SetEnabled(bool e) { + enabled_ = e; + emit EnabledChanged(enabled_); +} diff --git a/effects/effectfield.h b/effects/effectfield.h index d2264ab74..543dafc47 100644 --- a/effects/effectfield.h +++ b/effects/effectfield.h @@ -1,420 +1,420 @@ -/*** - - 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 EFFECTFIELD_H -#define EFFECTFIELD_H - -#include -#include -#include - -#include "effects/keyframe.h" -#include "undo/undostack.h" -#include "nodes/nodedatatypes.h" - -class NodeIO; -class ComboAction; - -/** - * @brief The EffectField class - * - * Any user-interactive element of an Effect. Usually a parameter that modifies the effect output, but sometimes just - * a UI object that performs some other function (e.g. LabelField and ButtonField). - * - * EffectField provides a largely abstract interface for Effect classes to pull information from. The class itself - * handles keyframing between linear, bezier, and hold interpolation accessible through GetValueAt(). This class - * is abstract, and therefore never intended to be used on its own. Instead you should always use a derived class. - * - * EffectField objects are *not* UI objects on their own. Instead, they're largely a system of values that can change - * over time. For a widget that the user can use to edit/modify these values, use CreateWidget(). - * - * Derived classes are expected to override at least CreateWidget() to create a visual interactive widget corresponding - * to the field. If this - * field is a value used in the Effect (as most will be), UpdateWidgetValue() should also be overridden to display the - * correct value for this field as the user moves around the Timeline. - * - * If the field is intended to be saved and loaded from Olive project files (as most will be), ConvertStringToValue() - * and ConvertValueToString() may also have to be overridden depending on how the derived class's data works. - */ -class EffectField : public QObject { - Q_OBJECT -public: - - /** - * @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 DoubleField). - */ - enum EffectFieldType { - /** Values are doubles. Also corresponds to DoubleField. */ - EFFECT_FIELD_DOUBLE, - - /** Values are colors. Also corresponds to ColorField. */ - EFFECT_FIELD_COLOR, - - /** Values are strings. Also corresponds to StringField. */ - EFFECT_FIELD_STRING, - - /** Values are booleans. Also corresponds to BoolField. */ - EFFECT_FIELD_BOOL, - - /** Values are arbitrary data. Also corresponds to ComboField. */ - EFFECT_FIELD_COMBO, - - /** Values are font family names (in string). Also corresponds to FontField. */ - EFFECT_FIELD_FONT, - - /** Values are filenames (in string). Also corresponds to FileField. */ - EFFECT_FIELD_FILE, - - /** Values is a UI object with no data. Corresponds to nothing. */ - EFFECT_FIELD_UI - }; - - /** - * @brief EffectField Constructor - * - * Creates a new EffectField object. - * - * @param parent - * - * The EffectRow to add this field to. This must be a valid EffectRow. The EffectRow takes ownership of the field - * using the QObject parent/child system to automate memory management. EffectFields are never expected - * to change parent during their lifetime. - * - * @param t - * - * The type of data contained within this field. This is expected to be filled by a derived class. - */ - EffectField(NodeIO* parent, EffectFieldType t); - - /** - * @brief Get the EffectRow that this field is a member of. - * - * Equivalent to `static_cast(EffectField::parent())` - * - * @return - * - * The EffectRow that this field is a member of. - */ - NodeIO* GetParentRow(); - - /** - * @brief Get the type of data to expect from this field - * - * @return - * - * A member of the EffectFieldType enum. - */ - const EffectFieldType& type(); - - /** - * @brief Get the value of this field at a given timecode - * - * EffectFields are designed to be keyframable, meaning the user can make the values change over the course of the - * Sequence. This is the main function used through Olive to retrieve what value this field will be at a given time. - * - * A common use case for this function would be EffectField::GetValueAt(EffectField::Now()), which will automatically - * retrieve the timecode at the current playhead. - * - * If the parent EffectRow is NOT keyframing, this function will simply return persistent_data_. If it IS keyframing, - * this will use the values in `keyframes` to determine what value should be specifically at this time. - * (bezier or linear interpolating it between values if necessary). Therefore this function should almost always be - * used to retrieve data from this field as the value will always be correct for the given time. - * - * @param timecode - * - * The time to retrieve the value in clip/media seconds (e.g. 0.0 is the very start of the media, 1.0 is one second - * into the media). - * - * @return - * - * A QVariant representation of the value at the given timecode. - */ - QVariant GetValueAt(double timecode); - - /** - * @brief Set the value of this field at a given timecode - * - * EffectFields are designed to be keyframable, meaning the user can make the values change over the course of the - * Sequence. This is the main function used through Olive to set what value this field will be at a given time. - * - * If the parent EffectRow is keyframing, this function will determine whether a keyframe exists at this time already. - * If it does, it will change the value at that keyframe to `value`. Otherwise, it'll create a new keyframe at the - * specified `time` with the specified `value`. - * - * If the parent EffectRow is not keyframing, the data is simply stored in `persistent_data_`. - * - * When constructing an Effect - * - * @param time - * - * The time to retrieve the value at in clip/media seconds (e.g. 0.0 is the very start of the media, 1.0 is one - * second into the media). - * - * @param value - * - * The QVariant value to set at this time. - */ - void SetValueAt(double time, const QVariant& value); - - /** - * @brief Set up keyframing on this field - * - * This should always be called if the user is enabling/disabling keyframing on the parent row. This function will - * move data between persistent_data_ and keyframes depending on whether keyframing is being enabled or disabled. - * - * If keyframing is getting ENABLED, this function will create the first keyframe automatically at the current time - * using the current value in persistent_data_. - * - * If keyframing is getting DISABLED, persistent_data_ is set to the current value at this time (GetValueAt(Now())) - * and delete all current keyframes. - * - * @param enabled - * - * TRUE if keyframing is getting enabled. - * - * @param ca - * - * A valid ComboAction object. It's expected that this function will be part of a larger action to enable/disable - * keyframing on the parent EffectRow, so this function will add commands to this ComboAction. - */ - void PrepareDataForKeyframing(bool enabled, ComboAction* ca); - - /** - * @brief Convert a value from this field to a string - * - * When saving effect data to a project file, the data needs to be converted to a string format for saving in XML. - * The needs of this string representation may differ depending on the needs of the derived class, therefore you - * derived classes may need to override it. - * - * Default behavior is a simple QVariant <-> QString conversion, which should suffice in most cases. - * - * @param v - * - * The QVariant data (retrieved from this field) to convert to string - * - * @return - * - * A string representation of the QVariant data provided. - */ - virtual QString ConvertValueToString(const QVariant& v); - - /** - * @brief Convert a string to a value appropriate for this field - * - * This function is the inverse of ConvertValueToString(), converting a string back to field data. - * - * @param s - * - * The string to convert to data. - * - * @return - * - * QVariant data converted from the provided string. - */ - virtual QVariant ConvertStringToValue(const QString& s); - - - /** - * @brief Create a widget for the user to interact with this field - * - * EffectField objects are *not* UI objects on their own. Instead, they're largely a system of values that can change - * over time. This function creates a QWidget object that can be placed somewhere in the UI so the user can - * interact with and change the data in this field. - * - * This function must be overridden by derived classes in order to create a widget that appropriate for that field's - * data. The derived class is also responsible for - * connecting signals like EnabledChanged(), Clicked(), and any other data that needs to be transferred between the - * widget and the field (setting up the signals and slots to do so). The field does NOT retain ownership (or any - * reference for that matter) to widgets it creates, - * so keeping the widget and field up to date with each other relies solely on setting up signals and slots. - * Infinite widgets can be created from a single field and used throughout Olive this way. - * - * Ownership is passed to the caller, and therefore the caller is responsible for freeing it. - * - * @param existing - * - * Olive allows multiple effects to attach to one UI layout. Pass a QWidget to this parameter (instead of nullptr) - * to attach this field additionally to the widget's signals/slots without creating a new one. The QWidget must be a - * widget previously created from the same derived class type or the result is undefined. - * - * @return - * - * A new QWidget object for this EffectField, or the same QWidget passed to `existing` if one was specified. - */ - virtual QWidget* CreateWidget(QWidget* existing = nullptr) = 0; - - /** - * @brief Update a widget created by CreateWidget() using the value at a given time - * - * Use this function to update a QWidget (obtained from CreateWidget()) with the correct value from the field at a - * given time. - * - * Since only the derived classes know what type of QWidget it created in CreateWidget() and how to work with them, - * derived classes are also expected to override this function if the field is an active value used in the Effect - * that should visually update as the user moves around the Timeline. However if the field does NOT need to update - * live (e.g. the field is just a UI wrapper like LabelField or ButtonField), this function does not need to be - * overridden as the default behavior (to do nothing) will suffice in those cases. - * - * @param widget - * - * The QWidget to set the value of (must be a QWidget obtained from CreateWidget() or the behavior is undefined). - * - * @param timecode - * - * The time in clip/media seconds to retrieve data from. - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode); - - /** - * @brief Get the correct X position/time value of a bezier keyframe's handles - * - * Retrieves the X value (time value) of a bezier keyframe's handles. Internally, the handles' X values are allowed - * to be arbitrary values. This however can lead to inadvertently creating impossible bezier curves (ones that, for - * example, mathematically loop over each other, but obviously a field can't have two values at the same time). - * - * This function returns the keyframe handles' X values adjusted to prevent this from happening. All calculations - * are consistent (i.e. the post handle of one keyframe will be adjusted the same way as the pre handle of the - * keyframe before it). It's recommended to always use this function to retrieve keyframe handle X values. - * - * @param key - * - * Index of the keyframe (in `keyframes`) to retrieve the handle position from. - * - * @param post - * - * FALSE to retrieve the "pre" handle (handle to the left of the keyframe), TRUE to retrieve the "post" handle - * (handle to the right of the keyframe). - * - * @return - * - * The adjusted X value of that keyframe handle. - */ - double GetValidKeyframeHandlePosition(int key, bool post); - - /** - * @brief Return whether this field is enabled or not - * - * @return - * - * TRUE if this field is enabled. - */ - bool IsEnabled(); - - /** - * @brief Set the enabled state of this field - * @param e - * - * TRUE to enable this field, FALSE to disable it. - */ - void SetEnabled(bool e); - - /** - * @brief Persistent data object - * - * If the parent EffectRow is not keyframing, all field data is stored and retrieved here. If the row IS keyframing, - * this variable goes basically unused unless `keyframes` is empty. - * - * NOTE: It is NOT recommended to access this variable directly. Use GetValueAt() instead. - */ - QVariant persistent_data_; - - /** - * @brief Keyframe array - * - * Contains all data about this field's keyframes, from the keyframe times, to their data, to their type (linear, - * bezier, or hold), to the bezier handles (if using bezier). If the row is not keyframing, this array is never used - * (`persistent_data_` is used instead). If it is, this array will always be used unless the array is empty, in which - * case `persistent_data_` will be used again. - */ - QVector keyframes; - -signals: - /** - * @brief Changed signal - * - * Emitted whenever SetValueAt() is called in order to trigger a UI update and Viewer repaint. Note this is NOT - * triggered as the value changes from keyframing. Only when the user themselves triggers a change. - */ - void Changed(); - - /** - * @brief Clicked signal - * - * Emitted when the user clicks on a QWidget attached to this field. Derived classes should connect this to the - * clicked signal of any QWidget's created (or attached) in CreateWidget(). - */ - void Clicked(); - - /** - * @brief Enable change state signal - * - * Emitted when the field's enabled state is changed through SetEnabled(). Derived classes should connect this to the - * setEnabled() slot of any QWidget's created (or attached) in CreateWidget(). - */ - void EnabledChanged(bool); - -private: - /** - * @brief Internal type variable set in the constructor. Access with type(). - */ - EffectFieldType type_; - - /** - * @brief Used by GetValueAt() to determine whether to use keyframe data or persistent data - * @return - * - * TRUE if this keyframe data should be retrieved, FALSE if persistent data should be retrieved - */ - bool HasKeyframes(); - - /** - * @brief Internal function for determining where we are between the available keyframes - * - * @param timecode - * - * Timecode to get keyframe data at - * - * @param before - * - * The index (in the keyframes array) in the keyframe prior to this timecode. - * - * @param after - * - * The index (in the keyframes array) in the keyframe after this timecode. - * - * @param d - * - * The progress between the `before` keyframe and `after` keyframe from 0.0 to 1.0. - */ - void GetKeyframeData(double timecode, int& before, int& after, double& d); - - /** - * @brief Internal enabled value - */ - bool enabled_; - -}; - -#endif // EFFECTFIELD_H +/*** + + 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 EFFECTFIELD_H +#define EFFECTFIELD_H + +#include +#include +#include + +#include "effects/keyframe.h" +#include "undo/undostack.h" +#include "nodes/nodedatatypes.h" + +class NodeIO; +class ComboAction; + +/** + * @brief The EffectField class + * + * Any user-interactive element of an Effect. Usually a parameter that modifies the effect output, but sometimes just + * a UI object that performs some other function (e.g. LabelField and ButtonField). + * + * EffectField provides a largely abstract interface for Effect classes to pull information from. The class itself + * handles keyframing between linear, bezier, and hold interpolation accessible through GetValueAt(). This class + * is abstract, and therefore never intended to be used on its own. Instead you should always use a derived class. + * + * EffectField objects are *not* UI objects on their own. Instead, they're largely a system of values that can change + * over time. For a widget that the user can use to edit/modify these values, use CreateWidget(). + * + * Derived classes are expected to override at least CreateWidget() to create a visual interactive widget corresponding + * to the field. If this + * field is a value used in the Effect (as most will be), UpdateWidgetValue() should also be overridden to display the + * correct value for this field as the user moves around the Timeline. + * + * If the field is intended to be saved and loaded from Olive project files (as most will be), ConvertStringToValue() + * and ConvertValueToString() may also have to be overridden depending on how the derived class's data works. + */ +class EffectField : public QObject { + Q_OBJECT +public: + + /** + * @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 DoubleField). + */ + enum EffectFieldType { + /** Values are doubles. Also corresponds to DoubleField. */ + EFFECT_FIELD_DOUBLE, + + /** Values are colors. Also corresponds to ColorField. */ + EFFECT_FIELD_COLOR, + + /** Values are strings. Also corresponds to StringField. */ + EFFECT_FIELD_STRING, + + /** Values are booleans. Also corresponds to BoolField. */ + EFFECT_FIELD_BOOL, + + /** Values are arbitrary data. Also corresponds to ComboField. */ + EFFECT_FIELD_COMBO, + + /** Values are font family names (in string). Also corresponds to FontField. */ + EFFECT_FIELD_FONT, + + /** Values are filenames (in string). Also corresponds to FileField. */ + EFFECT_FIELD_FILE, + + /** Values is a UI object with no data. Corresponds to nothing. */ + EFFECT_FIELD_UI + }; + + /** + * @brief EffectField Constructor + * + * Creates a new EffectField object. + * + * @param parent + * + * The EffectRow to add this field to. This must be a valid EffectRow. The EffectRow takes ownership of the field + * using the QObject parent/child system to automate memory management. EffectFields are never expected + * to change parent during their lifetime. + * + * @param t + * + * The type of data contained within this field. This is expected to be filled by a derived class. + */ + EffectField(NodeIO* parent, EffectFieldType t); + + /** + * @brief Get the EffectRow that this field is a member of. + * + * Equivalent to `static_cast(EffectField::parent())` + * + * @return + * + * The EffectRow that this field is a member of. + */ + NodeIO* GetParentRow(); + + /** + * @brief Get the type of data to expect from this field + * + * @return + * + * A member of the EffectFieldType enum. + */ + const EffectFieldType& type(); + + /** + * @brief Get the value of this field at a given timecode + * + * EffectFields are designed to be keyframable, meaning the user can make the values change over the course of the + * Sequence. This is the main function used through Olive to retrieve what value this field will be at a given time. + * + * A common use case for this function would be EffectField::GetValueAt(EffectField::Now()), which will automatically + * retrieve the timecode at the current playhead. + * + * If the parent EffectRow is NOT keyframing, this function will simply return persistent_data_. If it IS keyframing, + * this will use the values in `keyframes` to determine what value should be specifically at this time. + * (bezier or linear interpolating it between values if necessary). Therefore this function should almost always be + * used to retrieve data from this field as the value will always be correct for the given time. + * + * @param timecode + * + * The time to retrieve the value in clip/media seconds (e.g. 0.0 is the very start of the media, 1.0 is one second + * into the media). + * + * @return + * + * A QVariant representation of the value at the given timecode. + */ + QVariant GetValueAt(double timecode); + + /** + * @brief Set the value of this field at a given timecode + * + * EffectFields are designed to be keyframable, meaning the user can make the values change over the course of the + * Sequence. This is the main function used through Olive to set what value this field will be at a given time. + * + * If the parent EffectRow is keyframing, this function will determine whether a keyframe exists at this time already. + * If it does, it will change the value at that keyframe to `value`. Otherwise, it'll create a new keyframe at the + * specified `time` with the specified `value`. + * + * If the parent EffectRow is not keyframing, the data is simply stored in `persistent_data_`. + * + * When constructing an Effect + * + * @param time + * + * The time to retrieve the value at in clip/media seconds (e.g. 0.0 is the very start of the media, 1.0 is one + * second into the media). + * + * @param value + * + * The QVariant value to set at this time. + */ + void SetValueAt(double time, const QVariant& value); + + /** + * @brief Set up keyframing on this field + * + * This should always be called if the user is enabling/disabling keyframing on the parent row. This function will + * move data between persistent_data_ and keyframes depending on whether keyframing is being enabled or disabled. + * + * If keyframing is getting ENABLED, this function will create the first keyframe automatically at the current time + * using the current value in persistent_data_. + * + * If keyframing is getting DISABLED, persistent_data_ is set to the current value at this time (GetValueAt(Now())) + * and delete all current keyframes. + * + * @param enabled + * + * TRUE if keyframing is getting enabled. + * + * @param ca + * + * A valid ComboAction object. It's expected that this function will be part of a larger action to enable/disable + * keyframing on the parent EffectRow, so this function will add commands to this ComboAction. + */ + void PrepareDataForKeyframing(bool enabled, ComboAction* ca); + + /** + * @brief Convert a value from this field to a string + * + * When saving effect data to a project file, the data needs to be converted to a string format for saving in XML. + * The needs of this string representation may differ depending on the needs of the derived class, therefore you + * derived classes may need to override it. + * + * Default behavior is a simple QVariant <-> QString conversion, which should suffice in most cases. + * + * @param v + * + * The QVariant data (retrieved from this field) to convert to string + * + * @return + * + * A string representation of the QVariant data provided. + */ + virtual QString ConvertValueToString(const QVariant& v); + + /** + * @brief Convert a string to a value appropriate for this field + * + * This function is the inverse of ConvertValueToString(), converting a string back to field data. + * + * @param s + * + * The string to convert to data. + * + * @return + * + * QVariant data converted from the provided string. + */ + virtual QVariant ConvertStringToValue(const QString& s); + + + /** + * @brief Create a widget for the user to interact with this field + * + * EffectField objects are *not* UI objects on their own. Instead, they're largely a system of values that can change + * over time. This function creates a QWidget object that can be placed somewhere in the UI so the user can + * interact with and change the data in this field. + * + * This function must be overridden by derived classes in order to create a widget that appropriate for that field's + * data. The derived class is also responsible for + * connecting signals like EnabledChanged(), Clicked(), and any other data that needs to be transferred between the + * widget and the field (setting up the signals and slots to do so). The field does NOT retain ownership (or any + * reference for that matter) to widgets it creates, + * so keeping the widget and field up to date with each other relies solely on setting up signals and slots. + * Infinite widgets can be created from a single field and used throughout Olive this way. + * + * Ownership is passed to the caller, and therefore the caller is responsible for freeing it. + * + * @param existing + * + * Olive allows multiple effects to attach to one UI layout. Pass a QWidget to this parameter (instead of nullptr) + * to attach this field additionally to the widget's signals/slots without creating a new one. The QWidget must be a + * widget previously created from the same derived class type or the result is undefined. + * + * @return + * + * A new QWidget object for this EffectField, or the same QWidget passed to `existing` if one was specified. + */ + virtual QWidget* CreateWidget(QWidget* existing = nullptr) = 0; + + /** + * @brief Update a widget created by CreateWidget() using the value at a given time + * + * Use this function to update a QWidget (obtained from CreateWidget()) with the correct value from the field at a + * given time. + * + * Since only the derived classes know what type of QWidget it created in CreateWidget() and how to work with them, + * derived classes are also expected to override this function if the field is an active value used in the Effect + * that should visually update as the user moves around the Timeline. However if the field does NOT need to update + * live (e.g. the field is just a UI wrapper like LabelField or ButtonField), this function does not need to be + * overridden as the default behavior (to do nothing) will suffice in those cases. + * + * @param widget + * + * The QWidget to set the value of (must be a QWidget obtained from CreateWidget() or the behavior is undefined). + * + * @param timecode + * + * The time in clip/media seconds to retrieve data from. + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode); + + /** + * @brief Get the correct X position/time value of a bezier keyframe's handles + * + * Retrieves the X value (time value) of a bezier keyframe's handles. Internally, the handles' X values are allowed + * to be arbitrary values. This however can lead to inadvertently creating impossible bezier curves (ones that, for + * example, mathematically loop over each other, but obviously a field can't have two values at the same time). + * + * This function returns the keyframe handles' X values adjusted to prevent this from happening. All calculations + * are consistent (i.e. the post handle of one keyframe will be adjusted the same way as the pre handle of the + * keyframe before it). It's recommended to always use this function to retrieve keyframe handle X values. + * + * @param key + * + * Index of the keyframe (in `keyframes`) to retrieve the handle position from. + * + * @param post + * + * FALSE to retrieve the "pre" handle (handle to the left of the keyframe), TRUE to retrieve the "post" handle + * (handle to the right of the keyframe). + * + * @return + * + * The adjusted X value of that keyframe handle. + */ + double GetValidKeyframeHandlePosition(int key, bool post); + + /** + * @brief Return whether this field is enabled or not + * + * @return + * + * TRUE if this field is enabled. + */ + bool IsEnabled(); + + /** + * @brief Set the enabled state of this field + * @param e + * + * TRUE to enable this field, FALSE to disable it. + */ + void SetEnabled(bool e); + + /** + * @brief Persistent data object + * + * If the parent EffectRow is not keyframing, all field data is stored and retrieved here. If the row IS keyframing, + * this variable goes basically unused unless `keyframes` is empty. + * + * NOTE: It is NOT recommended to access this variable directly. Use GetValueAt() instead. + */ + QVariant persistent_data_; + + /** + * @brief Keyframe array + * + * Contains all data about this field's keyframes, from the keyframe times, to their data, to their type (linear, + * bezier, or hold), to the bezier handles (if using bezier). If the row is not keyframing, this array is never used + * (`persistent_data_` is used instead). If it is, this array will always be used unless the array is empty, in which + * case `persistent_data_` will be used again. + */ + QVector keyframes; + +signals: + /** + * @brief Changed signal + * + * Emitted whenever SetValueAt() is called in order to trigger a UI update and Viewer repaint. Note this is NOT + * triggered as the value changes from keyframing. Only when the user themselves triggers a change. + */ + void Changed(); + + /** + * @brief Clicked signal + * + * Emitted when the user clicks on a QWidget attached to this field. Derived classes should connect this to the + * clicked signal of any QWidget's created (or attached) in CreateWidget(). + */ + void Clicked(); + + /** + * @brief Enable change state signal + * + * Emitted when the field's enabled state is changed through SetEnabled(). Derived classes should connect this to the + * setEnabled() slot of any QWidget's created (or attached) in CreateWidget(). + */ + void EnabledChanged(bool); + +private: + /** + * @brief Internal type variable set in the constructor. Access with type(). + */ + EffectFieldType type_; + + /** + * @brief Used by GetValueAt() to determine whether to use keyframe data or persistent data + * @return + * + * TRUE if this keyframe data should be retrieved, FALSE if persistent data should be retrieved + */ + bool HasKeyframes(); + + /** + * @brief Internal function for determining where we are between the available keyframes + * + * @param timecode + * + * Timecode to get keyframe data at + * + * @param before + * + * The index (in the keyframes array) in the keyframe prior to this timecode. + * + * @param after + * + * The index (in the keyframes array) in the keyframe after this timecode. + * + * @param d + * + * The progress between the `before` keyframe and `after` keyframe from 0.0 to 1.0. + */ + void GetKeyframeData(double timecode, int& before, int& after, double& d); + + /** + * @brief Internal enabled value + */ + bool enabled_; + +}; + +#endif // EFFECTFIELD_H diff --git a/effects/effectfields.h b/effects/effectfields.h index dacd3b7d4..82644bfd6 100644 --- a/effects/effectfields.h +++ b/effects/effectfields.h @@ -1,40 +1,40 @@ -/*** - - 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 - -/** - - A simple convenience header for including all the available EffectField derivations. - - */ - -#include "fields/boolfield.h" -#include "fields/buttonfield.h" -#include "fields/colorfield.h" -#include "fields/combofield.h" -#include "fields/doublefield.h" -#include "fields/filefield.h" -#include "fields/fontfield.h" -#include "fields/labelfield.h" -#include "fields/stringfield.h" - -#endif // EFFECTFIELDS_H +/*** + + 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 + +/** + + A simple convenience header for including all the available EffectField derivations. + + */ + +#include "fields/boolfield.h" +#include "fields/buttonfield.h" +#include "fields/colorfield.h" +#include "fields/combofield.h" +#include "fields/doublefield.h" +#include "fields/filefield.h" +#include "fields/fontfield.h" +#include "fields/labelfield.h" +#include "fields/stringfield.h" + +#endif // EFFECTFIELDS_H diff --git a/effects/effectloaders.cpp b/effects/effectloaders.cpp index 54922b276..984f90000 100644 --- a/effects/effectloaders.cpp +++ b/effects/effectloaders.cpp @@ -1,170 +1,170 @@ -/*** - - 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 "effectloaders.h" - -#include -#include -#include - -#include "nodes/oldeffectnode.h" -#include "effects/transition.h" -#include "global/path.h" -#include "panels/panels.h" -#include "panels/effectcontrols.h" -#include "global/config.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/richtexteffect.h" - -#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/nodetexturepassthru.h" -#include "nodes/nodes/nodeshader.h" - -QMutex olive::effects_loaded; - -void load_internal_effects() { - if (!olive::runtime_config.shaders_are_enabled) { - qWarning() << "Shaders are disabled, some effects may be nonfunctional"; - } - - olive::node_library.resize(kInvalidNode); - olive::node_library.fill(nullptr); - - 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({"*.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); - } - break; - } - reader.readNext(); - } - - file.close(); - } - } - } -} - -void load_shader_effects() { - QList effects_paths = get_effects_paths(); - - for (int h=0;hstart(); -} - -EffectInit::EffectInit() { - olive::effects_loaded.lock(); -} - -void EffectInit::run() { - qInfo() << "Initializing effects..."; - load_internal_effects(); - load_shader_effects(); - olive::effects_loaded.unlock(); - qInfo() << "Finished initializing effects"; -} +/*** + + 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 "effectloaders.h" + +#include +#include +#include + +#include "nodes/oldeffectnode.h" +#include "effects/transition.h" +#include "global/path.h" +#include "panels/panels.h" +#include "panels/effectcontrols.h" +#include "global/config.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/richtexteffect.h" + +#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/nodetexturepassthru.h" +#include "nodes/nodes/nodeshader.h" + +QMutex olive::effects_loaded; + +void load_internal_effects() { + if (!olive::runtime_config.shaders_are_enabled) { + qWarning() << "Shaders are disabled, some effects may be nonfunctional"; + } + + olive::node_library.resize(kInvalidNode); + olive::node_library.fill(nullptr); + + 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({"*.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); + } + break; + } + reader.readNext(); + } + + file.close(); + } + } + } +} + +void load_shader_effects() { + QList effects_paths = get_effects_paths(); + + for (int h=0;hstart(); +} + +EffectInit::EffectInit() { + olive::effects_loaded.lock(); +} + +void EffectInit::run() { + qInfo() << "Initializing effects..."; + load_internal_effects(); + load_shader_effects(); + olive::effects_loaded.unlock(); + qInfo() << "Finished initializing effects"; +} diff --git a/effects/effectloaders.h b/effects/effectloaders.h index ea7294358..dd137ac52 100644 --- a/effects/effectloaders.h +++ b/effects/effectloaders.h @@ -1,55 +1,55 @@ -/*** - - 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 EFFECTLOADERS_H -#define EFFECTLOADERS_H - -#include -#include -#include - -namespace olive { - extern QMutex effects_loaded; -} - -/** - * @brief The EffectInit class - * - * A separate thread for loading effects in the background while the rest of the program's initiation takes place. - * The program can even run before the effects have finished loading, but any point the software needs to access - * effects, it will have to wait for this thread to finish before it can. Fortunately this thread is usually very - * quick and is over before the MainWindow shows. - */ -class EffectInit : public QThread { -public: - EffectInit(); - - /** - * @brief A static convenience function to set up the EffectInit thread, start it, and free itself when complete. - */ - static void StartLoading(); -protected: - /** - * @brief Function that runs in the other thread. - */ - void run(); -}; - -#endif // EFFECTLOADERS_H +/*** + + 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 EFFECTLOADERS_H +#define EFFECTLOADERS_H + +#include +#include +#include + +namespace olive { + extern QMutex effects_loaded; +} + +/** + * @brief The EffectInit class + * + * A separate thread for loading effects in the background while the rest of the program's initiation takes place. + * The program can even run before the effects have finished loading, but any point the software needs to access + * effects, it will have to wait for this thread to finish before it can. Fortunately this thread is usually very + * quick and is over before the MainWindow shows. + */ +class EffectInit : public QThread { +public: + EffectInit(); + + /** + * @brief A static convenience function to set up the EffectInit thread, start it, and free itself when complete. + */ + static void StartLoading(); +protected: + /** + * @brief Function that runs in the other thread. + */ + void run(); +}; + +#endif // EFFECTLOADERS_H diff --git a/effects/fields/boolfield.cpp b/effects/fields/boolfield.cpp index 494c3f85c..fbd2b2ea0 100644 --- a/effects/fields/boolfield.cpp +++ b/effects/fields/boolfield.cpp @@ -1,99 +1,99 @@ -/*** - - 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 - -#include "nodes/node.h" -#include "undo/undo.h" - -BoolField::BoolField(NodeIO *parent) : - EffectField(parent, EffectField::EFFECT_FIELD_BOOL) -{} - -bool BoolField::GetBoolAt(double timecode) -{ - return GetValueAt(timecode).toBool(); -} - -QWidget *BoolField::CreateWidget(QWidget *existing) -{ - QCheckBox* cb; - - if (existing == nullptr) { - - cb = new QCheckBox(); - cb->setEnabled(IsEnabled()); - - } else { - - cb = static_cast(existing); - - } - - connect(cb, SIGNAL(toggled(bool)), this, SLOT(UpdateFromWidget(bool))); - connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool))); - connect(cb, SIGNAL(toggled(bool)), this, SIGNAL(Toggled(bool))); - - return cb; -} - -void BoolField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - QCheckBox* cb = static_cast(widget); - - // Setting the checked state on the checkbox below normally triggers a change() signal that will then trickle back - // to setting a value on this field. Therefore we block its signals while we're setting this. - - cb->blockSignals(true); - - if (qIsNaN(timecode)) { - cb->setTristate(true); - cb->setCheckState(Qt::PartiallyChecked); - } else { - cb->setTristate(false); - cb->setChecked(GetBoolAt(timecode)); - } - - cb->blockSignals(false); - - emit Toggled(cb->isChecked()); -} - -QVariant BoolField::ConvertStringToValue(const QString &s) -{ - return (s == "1"); -} - -QString BoolField::ConvertValueToString(const QVariant &v) -{ - return QString::number(v.toBool()); -} - -void BoolField::UpdateFromWidget(bool b) -{ - KeyframeDataChange* kdc = new KeyframeDataChange(this); - - SetValueAt(GetParentRow()->ParentNode()->Time(), b); - - kdc->SetNewKeyframes(); - olive::undo_stack.push(kdc); -} +/*** + + 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 + +#include "nodes/node.h" +#include "undo/undo.h" + +BoolField::BoolField(NodeIO *parent) : + EffectField(parent, EffectField::EFFECT_FIELD_BOOL) +{} + +bool BoolField::GetBoolAt(double timecode) +{ + return GetValueAt(timecode).toBool(); +} + +QWidget *BoolField::CreateWidget(QWidget *existing) +{ + QCheckBox* cb; + + if (existing == nullptr) { + + cb = new QCheckBox(); + cb->setEnabled(IsEnabled()); + + } else { + + cb = static_cast(existing); + + } + + connect(cb, SIGNAL(toggled(bool)), this, SLOT(UpdateFromWidget(bool))); + connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool))); + connect(cb, SIGNAL(toggled(bool)), this, SIGNAL(Toggled(bool))); + + return cb; +} + +void BoolField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + QCheckBox* cb = static_cast(widget); + + // Setting the checked state on the checkbox below normally triggers a change() signal that will then trickle back + // to setting a value on this field. Therefore we block its signals while we're setting this. + + cb->blockSignals(true); + + if (qIsNaN(timecode)) { + cb->setTristate(true); + cb->setCheckState(Qt::PartiallyChecked); + } else { + cb->setTristate(false); + cb->setChecked(GetBoolAt(timecode)); + } + + cb->blockSignals(false); + + emit Toggled(cb->isChecked()); +} + +QVariant BoolField::ConvertStringToValue(const QString &s) +{ + return (s == "1"); +} + +QString BoolField::ConvertValueToString(const QVariant &v) +{ + return QString::number(v.toBool()); +} + +void BoolField::UpdateFromWidget(bool b) +{ + KeyframeDataChange* kdc = new KeyframeDataChange(this); + + SetValueAt(GetParentRow()->ParentNode()->Time(), b); + + kdc->SetNewKeyframes(); + olive::undo_stack.push(kdc); +} diff --git a/effects/fields/boolfield.h b/effects/fields/boolfield.h index 89693b4b6..7595aab73 100644 --- a/effects/fields/boolfield.h +++ b/effects/fields/boolfield.h @@ -1,101 +1,101 @@ -/*** - - 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 - -#include "../effectfield.h" - -/** - * @brief The BoolField class - * - * An EffectField derivative the produces boolean values (true or false) and uses a checkbox as its visual representation. - */ -class BoolField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - BoolField(NodeIO* parent); - - /** - * @brief Get the boolean value at a given timecode - * - * A convenience function, equivalent to GetValueAt(timecode).toBool() - * - * @param timecode - * - * The timecode to retrieve the value at - * - * @return - * - * The boolean value at this timecode - */ - bool GetBoolAt(double timecode); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a QCheckBox. - */ - virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; - - /** - * @brief Reimplementation of EffectField::ConvertStringToValue() - */ - virtual QVariant ConvertStringToValue(const QString& s) override; - - /** - * @brief Reimplementation of EffectField::ConvertValueToString() - */ - virtual QString ConvertValueToString(const QVariant& v) override; -signals: - /** - * @brief Emitted whenever the UI widget's boolean value has changed - * - * For any QCheckBox created through this field's CreateWidget() function, this signal is emitted any time the - * checkbox value changes (either through user intervention or keyframing). It is mostly useful for - * enabling/disabling/changing other UI elements based on the checked - * state of this field's value (e.g. enabling other fields if this field is checked). - * - * It is NOT a reliable signal that the value has changed at all, as it is only emitted if a widget (created - * from CreateWidget() ) is currently active. - */ - void Toggled(bool); -private slots: - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current checked state of the QWidget (QCheckBox in this case). Automatically set when this slot is connected - * to the QCheckBox::toggled() signal. - */ - void UpdateFromWidget(bool b); -}; - -#endif // BOOLFIELD_H +/*** + + 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 + +#include "../effectfield.h" + +/** + * @brief The BoolField class + * + * An EffectField derivative the produces boolean values (true or false) and uses a checkbox as its visual representation. + */ +class BoolField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + BoolField(NodeIO* parent); + + /** + * @brief Get the boolean value at a given timecode + * + * A convenience function, equivalent to GetValueAt(timecode).toBool() + * + * @param timecode + * + * The timecode to retrieve the value at + * + * @return + * + * The boolean value at this timecode + */ + bool GetBoolAt(double timecode); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a QCheckBox. + */ + virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; + + /** + * @brief Reimplementation of EffectField::ConvertStringToValue() + */ + virtual QVariant ConvertStringToValue(const QString& s) override; + + /** + * @brief Reimplementation of EffectField::ConvertValueToString() + */ + virtual QString ConvertValueToString(const QVariant& v) override; +signals: + /** + * @brief Emitted whenever the UI widget's boolean value has changed + * + * For any QCheckBox created through this field's CreateWidget() function, this signal is emitted any time the + * checkbox value changes (either through user intervention or keyframing). It is mostly useful for + * enabling/disabling/changing other UI elements based on the checked + * state of this field's value (e.g. enabling other fields if this field is checked). + * + * It is NOT a reliable signal that the value has changed at all, as it is only emitted if a widget (created + * from CreateWidget() ) is currently active. + */ + void Toggled(bool); +private slots: + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current checked state of the QWidget (QCheckBox in this case). Automatically set when this slot is connected + * to the QCheckBox::toggled() signal. + */ + void UpdateFromWidget(bool b); +}; + +#endif // BOOLFIELD_H diff --git a/effects/fields/buttonfield.cpp b/effects/fields/buttonfield.cpp index bbb0a1c78..0e1e7c7f5 100644 --- a/effects/fields/buttonfield.cpp +++ b/effects/fields/buttonfield.cpp @@ -1,66 +1,66 @@ -/*** - - 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(NodeIO *parent, const QString &string) : - EffectField(parent, EffectField::EFFECT_FIELD_UI), - button_text_(string) -{} - -void ButtonField::SetCheckable(bool c) -{ - checkable_ = c; -} - -void ButtonField::SetChecked(bool c) -{ - checked_ = c; - emit CheckedChanged(c); -} - -QWidget *ButtonField::CreateWidget(QWidget *existing) -{ - QPushButton* button; - - if (existing == nullptr) { - - button = new QPushButton(); - - button->setCheckable(checkable_); - button->setEnabled(IsEnabled()); - button->setText(button_text_); - - } else { - - button = static_cast(existing); - - } - - connect(this, SIGNAL(CheckedChanged(bool)), button, SLOT(setChecked(bool))); - connect(this, SIGNAL(EnabledChanged(bool)), button, SLOT(setEnabled(bool))); - connect(button, SIGNAL(clicked(bool)), this, SIGNAL(Clicked())); - connect(button, SIGNAL(toggled(bool)), this, SLOT(SetChecked(bool))); - connect(button, SIGNAL(toggled(bool)), this, SIGNAL(Toggled(bool))); - - return button; -} +/*** + + 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(NodeIO *parent, const QString &string) : + EffectField(parent, EffectField::EFFECT_FIELD_UI), + button_text_(string) +{} + +void ButtonField::SetCheckable(bool c) +{ + checkable_ = c; +} + +void ButtonField::SetChecked(bool c) +{ + checked_ = c; + emit CheckedChanged(c); +} + +QWidget *ButtonField::CreateWidget(QWidget *existing) +{ + QPushButton* button; + + if (existing == nullptr) { + + button = new QPushButton(); + + button->setCheckable(checkable_); + button->setEnabled(IsEnabled()); + button->setText(button_text_); + + } else { + + button = static_cast(existing); + + } + + connect(this, SIGNAL(CheckedChanged(bool)), button, SLOT(setChecked(bool))); + connect(this, SIGNAL(EnabledChanged(bool)), button, SLOT(setEnabled(bool))); + connect(button, SIGNAL(clicked(bool)), this, SIGNAL(Clicked())); + connect(button, SIGNAL(toggled(bool)), this, SLOT(SetChecked(bool))); + connect(button, SIGNAL(toggled(bool)), this, SIGNAL(Toggled(bool))); + + return button; +} diff --git a/effects/fields/buttonfield.h b/effects/fields/buttonfield.h index 8a4ccfaf2..ac1ca842f 100644 --- a/effects/fields/buttonfield.h +++ b/effects/fields/buttonfield.h @@ -1,111 +1,111 @@ -/*** - - 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 - -#include "../effectfield.h" - -/** - * @brief The ButtonField class - * - * A UI-type EffectField. This field is largely an EffectField wrapper around a QPushButton and provides no data that's - * usable in the Effect. It's primarily useful for other UI functions (e.g. showing/hiding a dialog or other UI - * elements). This field is not exposed to the external shader API as it requires raw C++ code to connect it to other - * elements. - * - * As with all widgets created from EffectField::CreateWidget(), you should never interface with the resulting widget - * directly (apart from adding it to a layout and deleting it when it's unnecessary). All signals/slots should pass - * through ButtonField instead to keep consistency with every layer involved. - */ -class ButtonField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - ButtonField(NodeIO* parent, const QString& string); - - /** - * @brief Set whether this pushbutton is checkable - * - * This function is mainly a wrapper around QPushButton::setCheckable(). - * - * "Checkable" means the button can be toggled between a state of being "normal" and being "pressed". In checkable - * mode this field still cannot be used as a value in an Effect. Instead use BoolField (which uses a QCheckBox - * representation) for passing values to the Effect that can only be true or false. - * - * @param c - * - * TRUE if this button should be checkable or not. - */ - void SetCheckable(bool c); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a QPushButton. - */ - virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; - -public slots: - /** - * @brief A slot for when a widget's (created and connected from CreateWidget() ) checked state is changed - * - * @param c - * - * The current checked state (automatically filled by the QPushButton::toggled() signal) - */ - void SetChecked(bool c); - -signals: - /** - * @brief A signal emitted whenever the field's internal checked state is changed - * - * Primarily used to set any connected widget's checked state to be consistent with the field's. - */ - void CheckedChanged(bool); - - /** - * @brief A signal emitted whenever the checked state of a connected widget changes - * - * Any widgets associated with this field will emit this signal when their checked state changes. - */ - void Toggled(bool); - -private: - /** - * @brief Internal button text string passed to widgets created by CreateWidget() - */ - bool checkable_; - - /** - * @brief Internal checked value passed to and from widgets created by CreateWidget() - */ - bool checked_; - - /** - * @brief Internal button text string passed to widgets created by CreateWidget() - */ - QString button_text_; -}; - -#endif // BUTTONFIELD_H +/*** + + 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 + +#include "../effectfield.h" + +/** + * @brief The ButtonField class + * + * A UI-type EffectField. This field is largely an EffectField wrapper around a QPushButton and provides no data that's + * usable in the Effect. It's primarily useful for other UI functions (e.g. showing/hiding a dialog or other UI + * elements). This field is not exposed to the external shader API as it requires raw C++ code to connect it to other + * elements. + * + * As with all widgets created from EffectField::CreateWidget(), you should never interface with the resulting widget + * directly (apart from adding it to a layout and deleting it when it's unnecessary). All signals/slots should pass + * through ButtonField instead to keep consistency with every layer involved. + */ +class ButtonField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + ButtonField(NodeIO* parent, const QString& string); + + /** + * @brief Set whether this pushbutton is checkable + * + * This function is mainly a wrapper around QPushButton::setCheckable(). + * + * "Checkable" means the button can be toggled between a state of being "normal" and being "pressed". In checkable + * mode this field still cannot be used as a value in an Effect. Instead use BoolField (which uses a QCheckBox + * representation) for passing values to the Effect that can only be true or false. + * + * @param c + * + * TRUE if this button should be checkable or not. + */ + void SetCheckable(bool c); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a QPushButton. + */ + virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; + +public slots: + /** + * @brief A slot for when a widget's (created and connected from CreateWidget() ) checked state is changed + * + * @param c + * + * The current checked state (automatically filled by the QPushButton::toggled() signal) + */ + void SetChecked(bool c); + +signals: + /** + * @brief A signal emitted whenever the field's internal checked state is changed + * + * Primarily used to set any connected widget's checked state to be consistent with the field's. + */ + void CheckedChanged(bool); + + /** + * @brief A signal emitted whenever the checked state of a connected widget changes + * + * Any widgets associated with this field will emit this signal when their checked state changes. + */ + void Toggled(bool); + +private: + /** + * @brief Internal button text string passed to widgets created by CreateWidget() + */ + bool checkable_; + + /** + * @brief Internal checked value passed to and from widgets created by CreateWidget() + */ + bool checked_; + + /** + * @brief Internal button text string passed to widgets created by CreateWidget() + */ + QString button_text_; +}; + +#endif // BUTTONFIELD_H diff --git a/effects/fields/colorfield.cpp b/effects/fields/colorfield.cpp index de04abb71..2a52c879a 100644 --- a/effects/fields/colorfield.cpp +++ b/effects/fields/colorfield.cpp @@ -1,73 +1,73 @@ -/*** - - 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(NodeIO* parent) : - EffectField(parent, EffectField::EFFECT_FIELD_COLOR) -{} - -QColor ColorField::GetColorAt(double timecode) -{ - return GetValueAt(timecode).value(); -} - -QWidget *ColorField::CreateWidget(QWidget *existing) -{ - ColorButton* cb = (existing != nullptr) ? static_cast(existing) : new ColorButton(); - - connect(cb, SIGNAL(color_changed(const QColor &)), this, SLOT(UpdateFromWidget(const QColor &))); - connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool))); - - return cb; -} - -void ColorField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - ColorButton* cb = static_cast(widget); - - cb->set_color(GetColorAt(timecode)); -} - -QVariant ColorField::ConvertStringToValue(const QString &s) -{ - return QColor(s); -} - -QString ColorField::ConvertValueToString(const QVariant &v) -{ - return v.value().name(); -} - -void ColorField::UpdateFromWidget(const QColor& c) -{ - KeyframeDataChange* kdc = new KeyframeDataChange(this); - - SetValueAt(GetParentRow()->ParentNode()->Time(), c); - - kdc->SetNewKeyframes(); - olive::undo_stack.push(kdc); -} +/*** + + 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(NodeIO* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_COLOR) +{} + +QColor ColorField::GetColorAt(double timecode) +{ + return GetValueAt(timecode).value(); +} + +QWidget *ColorField::CreateWidget(QWidget *existing) +{ + ColorButton* cb = (existing != nullptr) ? static_cast(existing) : new ColorButton(); + + connect(cb, SIGNAL(color_changed(const QColor &)), this, SLOT(UpdateFromWidget(const QColor &))); + connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool))); + + return cb; +} + +void ColorField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + ColorButton* cb = static_cast(widget); + + cb->set_color(GetColorAt(timecode)); +} + +QVariant ColorField::ConvertStringToValue(const QString &s) +{ + return QColor(s); +} + +QString ColorField::ConvertValueToString(const QVariant &v) +{ + return v.value().name(); +} + +void ColorField::UpdateFromWidget(const QColor& c) +{ + KeyframeDataChange* kdc = new KeyframeDataChange(this); + + SetValueAt(GetParentRow()->ParentNode()->Time(), c); + + kdc->SetNewKeyframes(); + olive::undo_stack.push(kdc); +} diff --git a/effects/fields/colorfield.h b/effects/fields/colorfield.h index c0421e6ad..0744a25e6 100644 --- a/effects/fields/colorfield.h +++ b/effects/fields/colorfield.h @@ -1,88 +1,88 @@ -/*** - - 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 - -#include "../effectfield.h" - -/** - * @brief The ColorField class - * - * An EffectField derivative that produces color values and uses a ColorButton as its UI representative. - */ -class ColorField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - ColorField(NodeIO* parent); - - /** - * @brief Get the color value at a given timecode - * - * A convenience function, equivalent to GetValueAt(timecode).value(). - * - * @param timecode - * - * The timecode to retrieve the color at - * - * @return - * - * The color value at this timecode - */ - QColor GetColorAt(double timecode); - - /** - * @brief CreateWidget - * - * Creates and connects to a ColorButton. - */ - virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; - - /** - * @brief Reimplementation of EffectField::ConvertStringToValue() - */ - virtual QVariant ConvertStringToValue(const QString& s) override; - - /** - * @brief Reimplementation of EffectField::ConvertValueToString() - */ - virtual QString ConvertValueToString(const QVariant& v) override; -private slots: - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current color selected by the QWidget (ColorButton in this case). Automatically triggered when this slot is - * connected to the ColorButton::color_changed() signal. - */ - void UpdateFromWidget(const QColor &c); -}; - -#endif // COLORFIELD_H +/*** + + 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 + +#include "../effectfield.h" + +/** + * @brief The ColorField class + * + * An EffectField derivative that produces color values and uses a ColorButton as its UI representative. + */ +class ColorField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + ColorField(NodeIO* parent); + + /** + * @brief Get the color value at a given timecode + * + * A convenience function, equivalent to GetValueAt(timecode).value(). + * + * @param timecode + * + * The timecode to retrieve the color at + * + * @return + * + * The color value at this timecode + */ + QColor GetColorAt(double timecode); + + /** + * @brief CreateWidget + * + * Creates and connects to a ColorButton. + */ + virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; + + /** + * @brief Reimplementation of EffectField::ConvertStringToValue() + */ + virtual QVariant ConvertStringToValue(const QString& s) override; + + /** + * @brief Reimplementation of EffectField::ConvertValueToString() + */ + virtual QString ConvertValueToString(const QVariant& v) override; +private slots: + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current color selected by the QWidget (ColorButton in this case). Automatically triggered when this slot is + * connected to the ColorButton::color_changed() signal. + */ + void UpdateFromWidget(const QColor &c); +}; + +#endif // COLORFIELD_H diff --git a/effects/fields/combofield.cpp b/effects/fields/combofield.cpp index 0137048de..5779b00e8 100644 --- a/effects/fields/combofield.cpp +++ b/effects/fields/combofield.cpp @@ -1,93 +1,93 @@ -/*** - - 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(NodeIO* parent) : - EffectField(parent, EffectField::EFFECT_FIELD_COMBO) -{} - -void ComboField::AddItem(const QString &text, const QVariant &data) -{ - ComboFieldItem item; - - item.name = text; - item.data = data; - - items_.append(item); -} - -QWidget *ComboField::CreateWidget(QWidget *existing) -{ - ComboBoxEx* cb; - - if (existing == nullptr) { - cb = new ComboBoxEx(); - - cb->setScrollingEnabled(false); - - for (int i=0;iaddItem(items_.at(i).name); - } - } else { - cb = static_cast(existing); - } - - connect(cb, SIGNAL(activated(int)), this, SLOT(UpdateFromWidget(int))); - connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool))); - - return cb; -} - -void ComboField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - QVariant data = GetValueAt(timecode); - - ComboBoxEx* cb = static_cast(widget); - - for (int i=0;iblockSignals(true); - cb->setCurrentIndex(i); - cb->blockSignals(false); - - emit DataChanged(data); - return; - } - } - - qWarning() << "Failed to set ComboField value from data"; -} - -void ComboField::UpdateFromWidget(int index) -{ - KeyframeDataChange* kdc = new KeyframeDataChange(this); - - SetValueAt(GetParentRow()->ParentNode()->Time(), items_.at(index).data); - - kdc->SetNewKeyframes(); - olive::undo_stack.push(kdc); -} +/*** + + 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(NodeIO* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_COMBO) +{} + +void ComboField::AddItem(const QString &text, const QVariant &data) +{ + ComboFieldItem item; + + item.name = text; + item.data = data; + + items_.append(item); +} + +QWidget *ComboField::CreateWidget(QWidget *existing) +{ + ComboBoxEx* cb; + + if (existing == nullptr) { + cb = new ComboBoxEx(); + + cb->setScrollingEnabled(false); + + for (int i=0;iaddItem(items_.at(i).name); + } + } else { + cb = static_cast(existing); + } + + connect(cb, SIGNAL(activated(int)), this, SLOT(UpdateFromWidget(int))); + connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool))); + + return cb; +} + +void ComboField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + QVariant data = GetValueAt(timecode); + + ComboBoxEx* cb = static_cast(widget); + + for (int i=0;iblockSignals(true); + cb->setCurrentIndex(i); + cb->blockSignals(false); + + emit DataChanged(data); + return; + } + } + + qWarning() << "Failed to set ComboField value from data"; +} + +void ComboField::UpdateFromWidget(int index) +{ + KeyframeDataChange* kdc = new KeyframeDataChange(this); + + SetValueAt(GetParentRow()->ParentNode()->Time(), items_.at(index).data); + + kdc->SetNewKeyframes(); + olive::undo_stack.push(kdc); +} diff --git a/effects/fields/combofield.h b/effects/fields/combofield.h index bbccdea32..76c199e1e 100644 --- a/effects/fields/combofield.h +++ b/effects/fields/combofield.h @@ -1,113 +1,113 @@ -/*** - - 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 - -#include "../effectfield.h" - -/** - * @brief The ComboFieldItem struct - * - * An internal string+value pair used to represent a combobox item. name is used for the UI - * representation of the choices and the data is what can be retrieved by code. - * - * \see ComboField::AddItem. - */ -struct ComboFieldItem { - QString name; - QVariant data; -}; - -/** - * @brief The ComboField class - * - * An EffectField derivative to produce arbitrary data based on a fixed selection of items. - */ -class ComboField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - ComboField(NodeIO* parent); - - /** - * @brief Add an item to this ComboField - * - * Adds a choice that the user can choose from this ComboField. All choices need text (for the on-screen - * choice) and data, which gets read on the backend. The selected data is what gets saved and loaded from - * project files, and therefore the data should be unique to this item. In case more items get added to - * this ComboField later, old project files will still open correctly. This is also why simple selected - * indices are not available. The text is only shown on the UI so it can be safely translated during runtime. - * - * @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); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a QComboBox with the set of items added in AddItem(). - */ - virtual QWidget *CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; - -signals: - /** - * @brief Signal emitted whenever a connected widget's data gets changed - * - * Useful for UI events that need to occur with the change of this ComboField's value. - */ - void DataChanged(const QVariant&); - -private: - /** - * @brief Internal array of string+value pair items. - * - * \see ComboFieldItem - */ - QVector items_; - -private slots: - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current index of the QWidget (QComboBox in this case). Automatically set when this slot is connected - * to the QComboBox::currentIndexChanged() signal. This is the only time ComboFields deal with indices since the - * QComboBox's indices will match precisely to the items_ array. Outside of this function, QVariant data is preferred. - */ - void UpdateFromWidget(int index); -}; - -#endif // COMBOFIELD_H +/*** + + 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 + +#include "../effectfield.h" + +/** + * @brief The ComboFieldItem struct + * + * An internal string+value pair used to represent a combobox item. name is used for the UI + * representation of the choices and the data is what can be retrieved by code. + * + * \see ComboField::AddItem. + */ +struct ComboFieldItem { + QString name; + QVariant data; +}; + +/** + * @brief The ComboField class + * + * An EffectField derivative to produce arbitrary data based on a fixed selection of items. + */ +class ComboField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + ComboField(NodeIO* parent); + + /** + * @brief Add an item to this ComboField + * + * Adds a choice that the user can choose from this ComboField. All choices need text (for the on-screen + * choice) and data, which gets read on the backend. The selected data is what gets saved and loaded from + * project files, and therefore the data should be unique to this item. In case more items get added to + * this ComboField later, old project files will still open correctly. This is also why simple selected + * indices are not available. The text is only shown on the UI so it can be safely translated during runtime. + * + * @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); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a QComboBox with the set of items added in AddItem(). + */ + virtual QWidget *CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; + +signals: + /** + * @brief Signal emitted whenever a connected widget's data gets changed + * + * Useful for UI events that need to occur with the change of this ComboField's value. + */ + void DataChanged(const QVariant&); + +private: + /** + * @brief Internal array of string+value pair items. + * + * \see ComboFieldItem + */ + QVector items_; + +private slots: + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current index of the QWidget (QComboBox in this case). Automatically set when this slot is connected + * to the QComboBox::currentIndexChanged() signal. This is the only time ComboFields deal with indices since the + * QComboBox's indices will match precisely to the items_ array. Outside of this function, QVariant data is preferred. + */ + void UpdateFromWidget(int index); +}; + +#endif // COMBOFIELD_H diff --git a/effects/fields/doublefield.cpp b/effects/fields/doublefield.cpp index b60ee6341..5b8033249 100644 --- a/effects/fields/doublefield.cpp +++ b/effects/fields/doublefield.cpp @@ -1,151 +1,151 @@ -/*** - - 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 "nodes/node.h" -#include "undo/undo.h" - -DoubleField::DoubleField(NodeIO* parent) : - EffectField(parent, EffectField::EFFECT_FIELD_DOUBLE), - min_(qSNaN()), - max_(qSNaN()), - default_(0), - display_type_(LabelSlider::Normal), - frame_rate_(30), - value_set_(false), - kdc_(nullptr) -{ - connect(this, SIGNAL(Changed()), this, SLOT(ValueHasBeenSet()), Qt::DirectConnection); -} - -double DoubleField::GetDoubleAt(double timecode) -{ - return GetValueAt(timecode).toDouble(); -} - -void DoubleField::SetMinimum(double minimum) -{ - min_ = minimum; - emit MinimumChanged(min_); -} - -void DoubleField::SetMaximum(double maximum) -{ - max_ = maximum; - emit MaximumChanged(max_); -} - -void DoubleField::SetDefault(double d) -{ - default_ = d; - - if (!value_set_) { - SetValueAt(0, d); - } -} - -void DoubleField::SetDisplayType(LabelSlider::DisplayType type) -{ - display_type_ = type; -} - -void DoubleField::SetFrameRate(const double &rate) -{ - frame_rate_ = rate; -} - -QVariant DoubleField::ConvertStringToValue(const QString &s) -{ - return s.toDouble(); -} - -QString DoubleField::ConvertValueToString(const QVariant &v) -{ - return QString::number(v.toDouble()); -} - -QWidget *DoubleField::CreateWidget(QWidget *existing) -{ - LabelSlider* ls; - - if (existing == nullptr) { - - ls = new LabelSlider(); - - if (!qIsNaN(min_)) { - ls->SetMinimum(min_); - } - ls->SetDefault(default_); - if (!qIsNaN(max_)) { - ls->SetMaximum(max_); - } - ls->SetDisplayType(display_type_); - ls->SetFrameRate(frame_rate_); - - ls->setEnabled(IsEnabled()); - - } else { - - ls = static_cast(existing); - - } - - connect(ls, SIGNAL(valueChanged(double)), this, SLOT(UpdateFromWidget(double))); - connect(ls, SIGNAL(clicked()), this, SIGNAL(Clicked())); - connect(this, SIGNAL(EnabledChanged(bool)), ls, SLOT(setEnabled(bool))); - connect(this, SIGNAL(MaximumChanged(double)), ls, SLOT(SetMaximum(double))); - connect(this, SIGNAL(MinimumChanged(double)), ls, SLOT(SetMinimum(double))); - - return ls; -} - -void DoubleField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - if (qIsNaN(timecode)) { - static_cast(widget)->SetValue(qSNaN()); - } else { - static_cast(widget)->SetValue(GetDoubleAt(timecode)); - } -} - -void DoubleField::ValueHasBeenSet() -{ - value_set_ = true; -} - -void DoubleField::UpdateFromWidget(double d) -{ - LabelSlider* ls = static_cast(sender()); - - if (ls->IsDragging() && kdc_ == nullptr) { - kdc_ = new KeyframeDataChange(this); - } - - SetValueAt(GetParentRow()->ParentNode()->Time(), d); - - if (!ls->IsDragging() && kdc_ != nullptr) { - kdc_->SetNewKeyframes(); - - olive::undo_stack.push(kdc_); - - kdc_ = nullptr; - } -} +/*** + + 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 "nodes/node.h" +#include "undo/undo.h" + +DoubleField::DoubleField(NodeIO* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_DOUBLE), + min_(qSNaN()), + max_(qSNaN()), + default_(0), + display_type_(LabelSlider::Normal), + frame_rate_(30), + value_set_(false), + kdc_(nullptr) +{ + connect(this, SIGNAL(Changed()), this, SLOT(ValueHasBeenSet()), Qt::DirectConnection); +} + +double DoubleField::GetDoubleAt(double timecode) +{ + return GetValueAt(timecode).toDouble(); +} + +void DoubleField::SetMinimum(double minimum) +{ + min_ = minimum; + emit MinimumChanged(min_); +} + +void DoubleField::SetMaximum(double maximum) +{ + max_ = maximum; + emit MaximumChanged(max_); +} + +void DoubleField::SetDefault(double d) +{ + default_ = d; + + if (!value_set_) { + SetValueAt(0, d); + } +} + +void DoubleField::SetDisplayType(LabelSlider::DisplayType type) +{ + display_type_ = type; +} + +void DoubleField::SetFrameRate(const double &rate) +{ + frame_rate_ = rate; +} + +QVariant DoubleField::ConvertStringToValue(const QString &s) +{ + return s.toDouble(); +} + +QString DoubleField::ConvertValueToString(const QVariant &v) +{ + return QString::number(v.toDouble()); +} + +QWidget *DoubleField::CreateWidget(QWidget *existing) +{ + LabelSlider* ls; + + if (existing == nullptr) { + + ls = new LabelSlider(); + + if (!qIsNaN(min_)) { + ls->SetMinimum(min_); + } + ls->SetDefault(default_); + if (!qIsNaN(max_)) { + ls->SetMaximum(max_); + } + ls->SetDisplayType(display_type_); + ls->SetFrameRate(frame_rate_); + + ls->setEnabled(IsEnabled()); + + } else { + + ls = static_cast(existing); + + } + + connect(ls, SIGNAL(valueChanged(double)), this, SLOT(UpdateFromWidget(double))); + connect(ls, SIGNAL(clicked()), this, SIGNAL(Clicked())); + connect(this, SIGNAL(EnabledChanged(bool)), ls, SLOT(setEnabled(bool))); + connect(this, SIGNAL(MaximumChanged(double)), ls, SLOT(SetMaximum(double))); + connect(this, SIGNAL(MinimumChanged(double)), ls, SLOT(SetMinimum(double))); + + return ls; +} + +void DoubleField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + if (qIsNaN(timecode)) { + static_cast(widget)->SetValue(qSNaN()); + } else { + static_cast(widget)->SetValue(GetDoubleAt(timecode)); + } +} + +void DoubleField::ValueHasBeenSet() +{ + value_set_ = true; +} + +void DoubleField::UpdateFromWidget(double d) +{ + LabelSlider* ls = static_cast(sender()); + + if (ls->IsDragging() && kdc_ == nullptr) { + kdc_ = new KeyframeDataChange(this); + } + + SetValueAt(GetParentRow()->ParentNode()->Time(), d); + + if (!ls->IsDragging() && kdc_ != nullptr) { + kdc_->SetNewKeyframes(); + + olive::undo_stack.push(kdc_); + + kdc_ = nullptr; + } +} diff --git a/effects/fields/doublefield.h b/effects/fields/doublefield.h index 4f31bc138..33a91d434 100644 --- a/effects/fields/doublefield.h +++ b/effects/fields/doublefield.h @@ -1,210 +1,210 @@ -/*** - - 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 - * - * An EffectField derivative the produces number values (integer or floating-point) and uses a LabelSlider as its - * visual representation. - */ -class DoubleField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - DoubleField(NodeIO* parent); - - /** - * @brief Get double value at timecode - * - * Convenience function. Equivalent to GetValueAt().toDouble() - * - * @param timecode - * - * Timecode to retrieve value at - * - * @return - * - * Double value at the set timecode - */ - double GetDoubleAt(double timecode); - - /** - * @brief Sets the minimum allowed number for the user to set to `minimum`. - */ - void SetMinimum(double minimum); - - /** - * @brief Sets the maximum allowed number for the user to set to `maximum`. - */ - void SetMaximum(double maximum); - - /** - * @brief Sets the default number for this field to `d`. - */ - void SetDefault(double d); - - /** - * @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); - - /** - * @brief Reimplementation of EffectField::ConvertStringToValue() - */ - virtual QVariant ConvertStringToValue(const QString& s) override; - - /** - * @brief Reimplementation of EffectField::ConvertValueToString() - */ - virtual QString ConvertValueToString(const QVariant& v) override; - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a LabelSlider. - */ - virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; -signals: - /** - * @brief Signal emitted when the field's maximum value has changed - * - * This signal gets connected to any LabelSlider created from CreateWidget() so the maximum value is - * always synchronized between them. - * - * Note: A connection is not made both ways as you should never manipulate a UI object created from - * an EffectField directly. Always access data through the EffectField itself. - * - * \see SetMaximum() - * - * @param maximum - * - * The new maximum value. - */ - void MaximumChanged(double maximum); - - /** - * @brief Signal emitted when the field's minimum value has changed - * - * This signal gets connected to any LabelSlider created from CreateWidget() so the minimum value is - * always synchronized between them. - * - * Note: A connection is not made both ways as you should never manipulate a UI object created from - * an EffectField directly. Always access data through the EffectField itself. - * - * \see SetMinimum() - * - * @param minimum - * - * The new minimum value. - */ - void MinimumChanged(double minimum); -private: - /** - * @brief Internal minimum value - * - * \see SetMinimum(). - */ - double min_; - - /** - * @brief Internal maximum value - * - * \see SetMaximum(). - */ - double max_; - - /** - * @brief Internal default value - * - * \see SetDefault(). - */ - double default_; - - /** - * @brief Internal display type value - * - * \see SetDisplayType(). - */ - LabelSlider::DisplayType display_type_; - - /** - * @brief Internal frame rate value - * - * \see SetFrameRate(). - */ - double frame_rate_; - - /** - * @brief Internal value used to allow SetDefault() to set the value as well if none has been set - * - * Initialized to FALSE, then set to TRUE indefinitely whenever the value gets set on this field. - */ - bool value_set_; - - /** - * @brief An internal KeyframeDataChange undoable command - * - * This is stored to allow for the value to be changed by dragging without every single "step" being pushed to - * the undo stack. Instead an undo command can be created at the start of a drag, and then pushed at the end - * to make it one single undoable action. - */ - KeyframeDataChange* kdc_; -private slots: - /** - * @brief Connected to EffectField::Changed() to ensure value_set_ gets set to TRUE whenever a value is set on this - * field. - */ - void ValueHasBeenSet(); - - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current number value of the QWidget (LabelSlider in this case). Automatically set when this slot is connected - * to the LabelSlider::valueChanged() signal. - */ - void UpdateFromWidget(double d); -}; - -#endif // DOUBLEFIELD_H +/*** + + 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 + * + * An EffectField derivative the produces number values (integer or floating-point) and uses a LabelSlider as its + * visual representation. + */ +class DoubleField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + DoubleField(NodeIO* parent); + + /** + * @brief Get double value at timecode + * + * Convenience function. Equivalent to GetValueAt().toDouble() + * + * @param timecode + * + * Timecode to retrieve value at + * + * @return + * + * Double value at the set timecode + */ + double GetDoubleAt(double timecode); + + /** + * @brief Sets the minimum allowed number for the user to set to `minimum`. + */ + void SetMinimum(double minimum); + + /** + * @brief Sets the maximum allowed number for the user to set to `maximum`. + */ + void SetMaximum(double maximum); + + /** + * @brief Sets the default number for this field to `d`. + */ + void SetDefault(double d); + + /** + * @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); + + /** + * @brief Reimplementation of EffectField::ConvertStringToValue() + */ + virtual QVariant ConvertStringToValue(const QString& s) override; + + /** + * @brief Reimplementation of EffectField::ConvertValueToString() + */ + virtual QString ConvertValueToString(const QVariant& v) override; + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a LabelSlider. + */ + virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; +signals: + /** + * @brief Signal emitted when the field's maximum value has changed + * + * This signal gets connected to any LabelSlider created from CreateWidget() so the maximum value is + * always synchronized between them. + * + * Note: A connection is not made both ways as you should never manipulate a UI object created from + * an EffectField directly. Always access data through the EffectField itself. + * + * \see SetMaximum() + * + * @param maximum + * + * The new maximum value. + */ + void MaximumChanged(double maximum); + + /** + * @brief Signal emitted when the field's minimum value has changed + * + * This signal gets connected to any LabelSlider created from CreateWidget() so the minimum value is + * always synchronized between them. + * + * Note: A connection is not made both ways as you should never manipulate a UI object created from + * an EffectField directly. Always access data through the EffectField itself. + * + * \see SetMinimum() + * + * @param minimum + * + * The new minimum value. + */ + void MinimumChanged(double minimum); +private: + /** + * @brief Internal minimum value + * + * \see SetMinimum(). + */ + double min_; + + /** + * @brief Internal maximum value + * + * \see SetMaximum(). + */ + double max_; + + /** + * @brief Internal default value + * + * \see SetDefault(). + */ + double default_; + + /** + * @brief Internal display type value + * + * \see SetDisplayType(). + */ + LabelSlider::DisplayType display_type_; + + /** + * @brief Internal frame rate value + * + * \see SetFrameRate(). + */ + double frame_rate_; + + /** + * @brief Internal value used to allow SetDefault() to set the value as well if none has been set + * + * Initialized to FALSE, then set to TRUE indefinitely whenever the value gets set on this field. + */ + bool value_set_; + + /** + * @brief An internal KeyframeDataChange undoable command + * + * This is stored to allow for the value to be changed by dragging without every single "step" being pushed to + * the undo stack. Instead an undo command can be created at the start of a drag, and then pushed at the end + * to make it one single undoable action. + */ + KeyframeDataChange* kdc_; +private slots: + /** + * @brief Connected to EffectField::Changed() to ensure value_set_ gets set to TRUE whenever a value is set on this + * field. + */ + void ValueHasBeenSet(); + + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current number value of the QWidget (LabelSlider in this case). Automatically set when this slot is connected + * to the LabelSlider::valueChanged() signal. + */ + void UpdateFromWidget(double d); +}; + +#endif // DOUBLEFIELD_H diff --git a/effects/fields/filefield.cpp b/effects/fields/filefield.cpp index d5dec92d2..4258227ed 100644 --- a/effects/fields/filefield.cpp +++ b/effects/fields/filefield.cpp @@ -1,68 +1,68 @@ -/*** - - 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(NodeIO* parent) : - EffectField(parent, EffectField::EFFECT_FIELD_FILE) -{ - // Set default value to an empty string - SetValueAt(0, ""); -} - -QString FileField::GetFileAt(double timecode) -{ - return GetValueAt(timecode).toString(); -} - -QWidget *FileField::CreateWidget(QWidget *existing) -{ - EmbeddedFileChooser* efc = (existing != nullptr) ? static_cast(existing) : new EmbeddedFileChooser(); - - connect(efc, SIGNAL(changed(const QString&)), this, SLOT(UpdateFromWidget(const QString&))); - connect(this, SIGNAL(EnabledChanged(bool)), efc, SLOT(setEnabled(bool))); - - return efc; -} - -void FileField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - EmbeddedFileChooser* efc = static_cast(widget); - - efc->blockSignals(true); - efc->setFilename(GetFileAt(timecode)); - efc->blockSignals(false); -} - -void FileField::UpdateFromWidget(const QString &s) -{ - KeyframeDataChange* kdc = new KeyframeDataChange(this); - - SetValueAt(GetParentRow()->ParentNode()->Time(), s); - - kdc->SetNewKeyframes(); - olive::undo_stack.push(kdc); -} +/*** + + 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(NodeIO* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_FILE) +{ + // Set default value to an empty string + SetValueAt(0, ""); +} + +QString FileField::GetFileAt(double timecode) +{ + return GetValueAt(timecode).toString(); +} + +QWidget *FileField::CreateWidget(QWidget *existing) +{ + EmbeddedFileChooser* efc = (existing != nullptr) ? static_cast(existing) : new EmbeddedFileChooser(); + + connect(efc, SIGNAL(changed(const QString&)), this, SLOT(UpdateFromWidget(const QString&))); + connect(this, SIGNAL(EnabledChanged(bool)), efc, SLOT(setEnabled(bool))); + + return efc; +} + +void FileField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + EmbeddedFileChooser* efc = static_cast(widget); + + efc->blockSignals(true); + efc->setFilename(GetFileAt(timecode)); + efc->blockSignals(false); +} + +void FileField::UpdateFromWidget(const QString &s) +{ + KeyframeDataChange* kdc = new KeyframeDataChange(this); + + SetValueAt(GetParentRow()->ParentNode()->Time(), s); + + kdc->SetNewKeyframes(); + olive::undo_stack.push(kdc); +} diff --git a/effects/fields/filefield.h b/effects/fields/filefield.h index bc462c0d7..032ef93ee 100644 --- a/effects/fields/filefield.h +++ b/effects/fields/filefield.h @@ -1,79 +1,79 @@ -/*** - - 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 FileInput class - * - * An EffectField derivative that produces filenames in string and uses an EmbeddedFileChooser - * as its visual representation. - */ -class FileField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - FileField(NodeIO* parent); - - /** - * @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); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a EmbeddedFileChooser. - */ - virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget *widget, double timecode) override; -private slots: - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current string of the QWidget (TextEditEx in this case). Automatically set when this slot - * is connected to the TextEditEx::textModified() signal. - */ - void UpdateFromWidget(const QString &s); -}; - -#endif // FILEFIELD_H +/*** + + 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 FileInput class + * + * An EffectField derivative that produces filenames in string and uses an EmbeddedFileChooser + * as its visual representation. + */ +class FileField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + FileField(NodeIO* parent); + + /** + * @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); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a EmbeddedFileChooser. + */ + virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget *widget, double timecode) override; +private slots: + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current string of the QWidget (TextEditEx in this case). Automatically set when this slot + * is connected to the TextEditEx::textModified() signal. + */ + void UpdateFromWidget(const QString &s); +}; + +#endif // FILEFIELD_H diff --git a/effects/fields/fontfield.cpp b/effects/fields/fontfield.cpp index 3e8a26410..282becff5 100644 --- a/effects/fields/fontfield.cpp +++ b/effects/fields/fontfield.cpp @@ -1,95 +1,95 @@ -/*** - - 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 ComboInput, and could probably be a derived class of it - -FontField::FontField(NodeIO* parent) : - EffectField(parent, EffectField::EFFECT_FIELD_FONT) -{ - font_list = QFontDatabase().families(); - - SetValueAt(0, font_list.first()); -} - -QString FontField::GetFontAt(double timecode) -{ - return GetValueAt(timecode).toString(); -} - -QWidget *FontField::CreateWidget(QWidget *existing) -{ - ComboBoxEx* fcb = new ComboBoxEx(); - - if (existing == nullptr) { - - fcb = new ComboBoxEx(); - - fcb->setScrollingEnabled(false); - - fcb->addItems(font_list); - - } else { - - fcb = static_cast(existing); - - } - - connect(fcb, SIGNAL(currentTextChanged(const QString &)), this, SLOT(UpdateFromWidget(const QString &))); - connect(this, SIGNAL(EnabledChanged(bool)), fcb, SLOT(setEnabled(bool))); - - return fcb; -} - -void FontField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - QVariant data = GetValueAt(timecode); - - ComboBoxEx* cb = static_cast(widget); - - for (int i=0;iblockSignals(true); - cb->setCurrentIndex(i); - cb->blockSignals(false); - return; - } - } - - qWarning() << "Failed to set FontField value from data"; -} - -void FontField::UpdateFromWidget(const QString& s) -{ - KeyframeDataChange* kdc = new KeyframeDataChange(this); - - SetValueAt(GetParentRow()->ParentNode()->Time(), s); - - kdc->SetNewKeyframes(); - olive::undo_stack.push(kdc); -} +/*** + + 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 ComboInput, and could probably be a derived class of it + +FontField::FontField(NodeIO* parent) : + EffectField(parent, EffectField::EFFECT_FIELD_FONT) +{ + font_list = QFontDatabase().families(); + + SetValueAt(0, font_list.first()); +} + +QString FontField::GetFontAt(double timecode) +{ + return GetValueAt(timecode).toString(); +} + +QWidget *FontField::CreateWidget(QWidget *existing) +{ + ComboBoxEx* fcb = new ComboBoxEx(); + + if (existing == nullptr) { + + fcb = new ComboBoxEx(); + + fcb->setScrollingEnabled(false); + + fcb->addItems(font_list); + + } else { + + fcb = static_cast(existing); + + } + + connect(fcb, SIGNAL(currentTextChanged(const QString &)), this, SLOT(UpdateFromWidget(const QString &))); + connect(this, SIGNAL(EnabledChanged(bool)), fcb, SLOT(setEnabled(bool))); + + return fcb; +} + +void FontField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + QVariant data = GetValueAt(timecode); + + ComboBoxEx* cb = static_cast(widget); + + for (int i=0;iblockSignals(true); + cb->setCurrentIndex(i); + cb->blockSignals(false); + return; + } + } + + qWarning() << "Failed to set FontField value from data"; +} + +void FontField::UpdateFromWidget(const QString& s) +{ + KeyframeDataChange* kdc = new KeyframeDataChange(this); + + SetValueAt(GetParentRow()->ParentNode()->Time(), s); + + kdc->SetNewKeyframes(); + olive::undo_stack.push(kdc); +} diff --git a/effects/fields/fontfield.h b/effects/fields/fontfield.h index 9b7256a35..50ef9a509 100644 --- a/effects/fields/fontfield.h +++ b/effects/fields/fontfield.h @@ -1,88 +1,88 @@ -/*** - - 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 - -#include "combofield.h" - -/** - * @brief The FontField class - * - * An EffectField derivative the produces font family names in string and uses a QComboBox - * as its visual representation. - * - * TODO Upgrade to QFontComboBox. - */ -class FontField : public EffectField { - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - FontField(NodeIO* parent); - - /** - * @brief Get the font family name at the given timecode - * - * A convenience function, equivalent to GetValueAt(timecode).toString() - * - * @param timecode - * - * The timecode to retrieve the font family name at - * - * @return - * - * The font family name at this timecode - */ - QString GetFontAt(double timecode); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a QComboBox. - */ - virtual QWidget *CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; - -private: - /** - * @brief Internal list of fonts to add to a QComboBox when creating one in CreateWidget(). - * - * NOTE: Deprecated. Once QComboBox is replaced by QFontComboBox this will be completely unnecessary. - */ - QStringList font_list; -private slots: - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current font name specified by the QWidget (QComboBox in this case). Automatically set when this slot - * is connected to the QComboBox::currentTextChanged() signal. - */ - void UpdateFromWidget(const QString& index); -}; - -#endif // FONTFIELD_H +/*** + + 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 + +#include "combofield.h" + +/** + * @brief The FontField class + * + * An EffectField derivative the produces font family names in string and uses a QComboBox + * as its visual representation. + * + * TODO Upgrade to QFontComboBox. + */ +class FontField : public EffectField { + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + FontField(NodeIO* parent); + + /** + * @brief Get the font family name at the given timecode + * + * A convenience function, equivalent to GetValueAt(timecode).toString() + * + * @param timecode + * + * The timecode to retrieve the font family name at + * + * @return + * + * The font family name at this timecode + */ + QString GetFontAt(double timecode); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a QComboBox. + */ + virtual QWidget *CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; + +private: + /** + * @brief Internal list of fonts to add to a QComboBox when creating one in CreateWidget(). + * + * NOTE: Deprecated. Once QComboBox is replaced by QFontComboBox this will be completely unnecessary. + */ + QStringList font_list; +private slots: + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current font name specified by the QWidget (QComboBox in this case). Automatically set when this slot + * is connected to the QComboBox::currentTextChanged() signal. + */ + void UpdateFromWidget(const QString& index); +}; + +#endif // FONTFIELD_H diff --git a/effects/fields/labelfield.cpp b/effects/fields/labelfield.cpp index b9f1b4fac..ee6ecbfd5 100644 --- a/effects/fields/labelfield.cpp +++ b/effects/fields/labelfield.cpp @@ -1,49 +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 . - -***/ - -#include "labelfield.h" - -#include - -LabelField::LabelField(NodeIO *parent, const QString &string) : - EffectField(parent, EffectField::EFFECT_FIELD_UI), - label_text_(string) -{} - -QWidget *LabelField::CreateWidget(QWidget *existing) -{ - QLabel* label; - - if (existing == nullptr) { - - label = new QLabel(label_text_); - - label->setEnabled(IsEnabled()); - - } else { - - label = static_cast(existing); - - } - - connect(this, SIGNAL(EnabledChanged(bool)), label, SLOT(setEnabled(bool))); - - return label; -} +/*** + + 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(NodeIO *parent, const QString &string) : + EffectField(parent, EffectField::EFFECT_FIELD_UI), + label_text_(string) +{} + +QWidget *LabelField::CreateWidget(QWidget *existing) +{ + QLabel* label; + + if (existing == nullptr) { + + label = new QLabel(label_text_); + + label->setEnabled(IsEnabled()); + + } else { + + label = static_cast(existing); + + } + + connect(this, SIGNAL(EnabledChanged(bool)), label, SLOT(setEnabled(bool))); + + return label; +} diff --git a/effects/fields/labelfield.h b/effects/fields/labelfield.h index 82985f612..a2cb05d23 100644 --- a/effects/fields/labelfield.h +++ b/effects/fields/labelfield.h @@ -1,55 +1,55 @@ -/*** - - 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 - -#include "../effectfield.h" - -/** - * @brief The LabelField class - * - * A UI-type EffectField. This field is largely an EffectField wrapper around a QLabel and provides no data that's - * usable in the Effect. It's primarily useful for showing UI information. This field is not exposed to the external - * shader API as it requires raw C++ code to connect it to other elements. - */ -class LabelField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - */ - LabelField(NodeIO* parent, const QString& string); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a QLabel. - */ - virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; -private: - /** - * @brief Internal text string - */ - QString label_text_; -}; - -#endif // LABELFIELD_H +/*** + + 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 + +#include "../effectfield.h" + +/** + * @brief The LabelField class + * + * A UI-type EffectField. This field is largely an EffectField wrapper around a QLabel and provides no data that's + * usable in the Effect. It's primarily useful for showing UI information. This field is not exposed to the external + * shader API as it requires raw C++ code to connect it to other elements. + */ +class LabelField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + */ + LabelField(NodeIO* parent, const QString& string); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a QLabel. + */ + virtual QWidget* CreateWidget(QWidget *existing = nullptr) override; +private: + /** + * @brief Internal text string + */ + QString label_text_; +}; + +#endif // LABELFIELD_H diff --git a/effects/fields/stringfield.cpp b/effects/fields/stringfield.cpp index d6cdb09b7..4f1d1470e 100644 --- a/effects/fields/stringfield.cpp +++ b/effects/fields/stringfield.cpp @@ -1,101 +1,101 @@ -/*** - - 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(NodeIO* parent, bool rich_text) : - EffectField(parent, EffectField::EFFECT_FIELD_STRING), - rich_text_(rich_text) -{ - // Set default value to an empty string - SetValueAt(0, ""); -} - -QString StringField::GetStringAt(double timecode) -{ - return GetValueAt(timecode).toString(); -} - -QWidget *StringField::CreateWidget(QWidget *existing) -{ - TextEditEx* text_edit; - - if (existing == nullptr) { - - text_edit = new TextEditEx(nullptr, rich_text_); - - text_edit->setEnabled(IsEnabled()); - 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::config.effect_textbox_lines - + text_edit->document()->documentMargin() - + text_edit->document()->documentMargin() + 2)); - - } else { - - text_edit = static_cast(existing); - - } - - connect(text_edit, SIGNAL(textModified(const QString&)), this, SLOT(UpdateFromWidget(const QString&))); - connect(this, SIGNAL(EnabledChanged(bool)), text_edit, SLOT(setEnabled(bool))); - - return text_edit; -} - -void StringField::UpdateWidgetValue(QWidget *widget, double timecode) -{ - TextEditEx* text = static_cast(widget); - - text->blockSignals(true); - - int pos = text->textCursor().position(); - - if (rich_text_) { - text->setHtml(GetValueAt(timecode).toString()); - } else { - text->setPlainText(GetValueAt(timecode).toString()); - } - - QTextCursor new_cursor(text->document()); - new_cursor.setPosition(pos); - text->setTextCursor(new_cursor); - - text->blockSignals(false); -} - -void StringField::UpdateFromWidget(const QString &s) -{ - KeyframeDataChange* kdc = new KeyframeDataChange(this); - - SetValueAt(GetParentRow()->ParentNode()->Time(), s); - - kdc->SetNewKeyframes(); - olive::undo_stack.push(kdc); -} +/*** + + 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(NodeIO* parent, bool rich_text) : + EffectField(parent, EffectField::EFFECT_FIELD_STRING), + rich_text_(rich_text) +{ + // Set default value to an empty string + SetValueAt(0, ""); +} + +QString StringField::GetStringAt(double timecode) +{ + return GetValueAt(timecode).toString(); +} + +QWidget *StringField::CreateWidget(QWidget *existing) +{ + TextEditEx* text_edit; + + if (existing == nullptr) { + + text_edit = new TextEditEx(nullptr, rich_text_); + + text_edit->setEnabled(IsEnabled()); + 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::config.effect_textbox_lines + + text_edit->document()->documentMargin() + + text_edit->document()->documentMargin() + 2)); + + } else { + + text_edit = static_cast(existing); + + } + + connect(text_edit, SIGNAL(textModified(const QString&)), this, SLOT(UpdateFromWidget(const QString&))); + connect(this, SIGNAL(EnabledChanged(bool)), text_edit, SLOT(setEnabled(bool))); + + return text_edit; +} + +void StringField::UpdateWidgetValue(QWidget *widget, double timecode) +{ + TextEditEx* text = static_cast(widget); + + text->blockSignals(true); + + int pos = text->textCursor().position(); + + if (rich_text_) { + text->setHtml(GetValueAt(timecode).toString()); + } else { + text->setPlainText(GetValueAt(timecode).toString()); + } + + QTextCursor new_cursor(text->document()); + new_cursor.setPosition(pos); + text->setTextCursor(new_cursor); + + text->blockSignals(false); +} + +void StringField::UpdateFromWidget(const QString &s) +{ + KeyframeDataChange* kdc = new KeyframeDataChange(this); + + SetValueAt(GetParentRow()->ParentNode()->Time(), s); + + kdc->SetNewKeyframes(); + olive::undo_stack.push(kdc); +} diff --git a/effects/fields/stringfield.h b/effects/fields/stringfield.h index 4a26a1957..d18cd84ed 100644 --- a/effects/fields/stringfield.h +++ b/effects/fields/stringfield.h @@ -1,87 +1,87 @@ -/*** - - 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 - -#include "../effectfield.h" - -/** - * @brief The StringField class - * - * An EffectField derivative that produces arbitrary strings entered by the user and uses a TextEditEx as its - * visual representation. - */ -class StringField : public EffectField -{ - Q_OBJECT -public: - /** - * @brief Reimplementation of EffectField::EffectField(). - * - * 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(NodeIO* parent, bool rich_text = true); - - /** - * @brief Get the string at the given timecode - * - * A convenience function, equivalent to GetValueAt(timecode).toString() - * - * @param timecode - * - * The timecode to retrieve the string at - * - * @return - * - * The string at this timecode - */ - QString GetStringAt(double timecode); - - /** - * @brief Reimplementation of EffectField::CreateWidget() - * - * Creates and connects to a TextEditEx. - */ - virtual QWidget *CreateWidget(QWidget *existing = nullptr) override; - - /** - * @brief Reimplementation of EffectField::UpdateWidgetValue() - */ - virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; -private slots: - /** - * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input - * - * @param b - * - * The current checked state of the QWidget (EmbeddedFileChooser in this case). Automatically set when this slot - * is connected to the EmbeddedFileChooser::changed() signal. - */ - void UpdateFromWidget(const QString& b); -private: - /** - * @brief Internal value for whether this field is in rich text or plain text mode - */ - bool rich_text_; -}; - -#endif // STRINGFIELD_H +/*** + + 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 + +#include "../effectfield.h" + +/** + * @brief The StringField class + * + * An EffectField derivative that produces arbitrary strings entered by the user and uses a TextEditEx as its + * visual representation. + */ +class StringField : public EffectField +{ + Q_OBJECT +public: + /** + * @brief Reimplementation of EffectField::EffectField(). + * + * 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(NodeIO* parent, bool rich_text = true); + + /** + * @brief Get the string at the given timecode + * + * A convenience function, equivalent to GetValueAt(timecode).toString() + * + * @param timecode + * + * The timecode to retrieve the string at + * + * @return + * + * The string at this timecode + */ + QString GetStringAt(double timecode); + + /** + * @brief Reimplementation of EffectField::CreateWidget() + * + * Creates and connects to a TextEditEx. + */ + virtual QWidget *CreateWidget(QWidget *existing = nullptr) override; + + /** + * @brief Reimplementation of EffectField::UpdateWidgetValue() + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode) override; +private slots: + /** + * @brief Internal function connected to any QWidget made from CreateWidget() to update the value based on user input + * + * @param b + * + * The current checked state of the QWidget (EmbeddedFileChooser in this case). Automatically set when this slot + * is connected to the EmbeddedFileChooser::changed() signal. + */ + void UpdateFromWidget(const QString& b); +private: + /** + * @brief Internal value for whether this field is in rich text or plain text mode + */ + bool rich_text_; +}; + +#endif // STRINGFIELD_H diff --git a/effects/internal/audionoiseeffect.h b/effects/internal/audionoiseeffect.h index d04e00da0..cebbf07b4 100644 --- a/effects/internal/audionoiseeffect.h +++ b/effects/internal/audionoiseeffect.h @@ -1,49 +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 AUDIONOISEEFFECT_H -#define AUDIONOISEEFFECT_H - -#include "nodes/oldeffectnode.h" - -class AudioNoiseEffect : public OldEffectNode { - Q_OBJECT -public: - AudioNoiseEffect(Clip* c); - - virtual QString name() override; - virtual QString id() override; - virtual QString description() override; - virtual EffectType type() override; - virtual olive::TrackType subtype() override; - virtual OldEffectNodePtr 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 +/*** + + 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 AUDIONOISEEFFECT_H +#define AUDIONOISEEFFECT_H + +#include "nodes/oldeffectnode.h" + +class AudioNoiseEffect : public OldEffectNode { + Q_OBJECT +public: + AudioNoiseEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual OldEffectNodePtr 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/common.vert b/effects/internal/common.vert index 2d088fa2f..d698c628e 100644 --- a/effects/internal/common.vert +++ b/effects/internal/common.vert @@ -1,8 +1,8 @@ -#version 110 - -varying vec2 vTexCoord; - -void main() { - vTexCoord = gl_MultiTexCoord0.xy; - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +#version 110 + +varying vec2 vTexCoord; + +void main() { + vTexCoord = gl_MultiTexCoord0.xy; + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } \ No newline at end of file diff --git a/effects/internal/cornerpin.frag b/effects/internal/cornerpin.frag index 7dfbd6944..1eba35cc1 100644 --- a/effects/internal/cornerpin.frag +++ b/effects/internal/cornerpin.frag @@ -1,46 +1,46 @@ -#version 130 - -uniform sampler2D tex; -uniform bool perspective; - -varying vec2 q; -varying vec2 b1; -varying vec2 b2; -varying vec2 b3; -varying vec2 vTexCoord; - -float Wedge2D(vec2 v, vec2 w) { - return (v.x*w.y) - (v.y*w.x); -} - -void main(void) { - if (perspective) { - gl_FragColor = texture2D(tex, vTexCoord); - } else { - float A = Wedge2D(b2, b3); - float B = Wedge2D(b3, q) - Wedge2D(b1, b2); - float C = Wedge2D(b1, q); - - vec2 uv; - - // solve for v - if (abs(A) < 0.001) { - uv.y = -C/B; - } else { - float discrim = B*B - 4.0*A*C; - uv.y = 0.5 * (-B + sqrt(discrim)) / A; - } - - // solve for u - vec2 denom = b1 + uv.y * b3; - if (abs(denom.x) > abs(denom.y)) { - uv.x = (q.x - b2.x * uv.y) / denom.x; - } else { - uv.x = (q.y - b2.y * uv.y) / denom.y; - } - - uv.y = 1.0 - uv.y; - - gl_FragColor = texture2D(tex, uv); - } +#version 130 + +uniform sampler2D tex; +uniform bool perspective; + +varying vec2 q; +varying vec2 b1; +varying vec2 b2; +varying vec2 b3; +varying vec2 vTexCoord; + +float Wedge2D(vec2 v, vec2 w) { + return (v.x*w.y) - (v.y*w.x); +} + +void main(void) { + if (perspective) { + gl_FragColor = texture2D(tex, vTexCoord); + } else { + float A = Wedge2D(b2, b3); + float B = Wedge2D(b3, q) - Wedge2D(b1, b2); + float C = Wedge2D(b1, q); + + vec2 uv; + + // solve for v + if (abs(A) < 0.001) { + uv.y = -C/B; + } else { + float discrim = B*B - 4.0*A*C; + uv.y = 0.5 * (-B + sqrt(discrim)) / A; + } + + // solve for u + vec2 denom = b1 + uv.y * b3; + if (abs(denom.x) > abs(denom.y)) { + uv.x = (q.x - b2.x * uv.y) / denom.x; + } else { + uv.x = (q.y - b2.y * uv.y) / denom.y; + } + + uv.y = 1.0 - uv.y; + + gl_FragColor = texture2D(tex, uv); + } } \ No newline at end of file diff --git a/effects/internal/cornerpin.vert b/effects/internal/cornerpin.vert index 9f3783846..ee96216a0 100644 --- a/effects/internal/cornerpin.vert +++ b/effects/internal/cornerpin.vert @@ -1,66 +1,66 @@ -#version 130 - -uniform bool perspective; -uniform vec2 p0; -uniform vec2 p1; -uniform vec2 p2; -uniform vec2 p3; - -varying vec2 q; -varying vec2 b1; -varying vec2 b2; -varying vec2 b3; -varying vec2 vTexCoord; - -void main() { - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; - - if (perspective) { - float m1 = (p3.y - p0.y)/(p3.x - p0.x); - float c1 = p0.y - m1 * p0.x; - float m2 = (p1.y - p2.y)/(p1.x - p2.x); - float c2 = p2.y - m2 * p2.x; - float mid_x = (c2 - c1) / (m1 - m2); - float mid_y = m1 * mid_x + c1; - - float d0 = length(vec2(mid_x - p0.x, mid_y - p0.y)); - float d1 = length(vec2(p1.x - mid_x, mid_y - p1.y)); - float d2 = length(vec2(p3.x - mid_x, p3.y - mid_y)); - float d3 = length(vec2(mid_x - p2.x, p2.y - mid_y)); - - float q; - - if (gl_VertexID == 0) { - q = (d1+d3)/d3; - } else if (gl_VertexID == 1) { - q = (d0+d2)/d2; - } else if (gl_VertexID == 2) { - q = (d3+d1)/d1; - } else { - q = (d2+d0)/d0; - } - - gl_Position[0] *= q; - gl_Position[1] *= q; - gl_Position[3] = q; - - vTexCoord = gl_MultiTexCoord0.xy; - } else { - vec2 pos; - - if (gl_VertexID == 0) { // top left - pos = p2; - } else if (gl_VertexID == 1) { // top right - pos = p3; - } else if (gl_VertexID == 2) { // bottom right - pos = p1; - } else if (gl_VertexID == 3) { // bottom left - pos = p0; - } - - q = pos - p0; - b1 = p1 - p0; - b2 = p2 - p0; - b3 = p0 - p1 - p2 + p3; - } +#version 130 + +uniform bool perspective; +uniform vec2 p0; +uniform vec2 p1; +uniform vec2 p2; +uniform vec2 p3; + +varying vec2 q; +varying vec2 b1; +varying vec2 b2; +varying vec2 b3; +varying vec2 vTexCoord; + +void main() { + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + if (perspective) { + float m1 = (p3.y - p0.y)/(p3.x - p0.x); + float c1 = p0.y - m1 * p0.x; + float m2 = (p1.y - p2.y)/(p1.x - p2.x); + float c2 = p2.y - m2 * p2.x; + float mid_x = (c2 - c1) / (m1 - m2); + float mid_y = m1 * mid_x + c1; + + float d0 = length(vec2(mid_x - p0.x, mid_y - p0.y)); + float d1 = length(vec2(p1.x - mid_x, mid_y - p1.y)); + float d2 = length(vec2(p3.x - mid_x, p3.y - mid_y)); + float d3 = length(vec2(mid_x - p2.x, p2.y - mid_y)); + + float q; + + if (gl_VertexID == 0) { + q = (d1+d3)/d3; + } else if (gl_VertexID == 1) { + q = (d0+d2)/d2; + } else if (gl_VertexID == 2) { + q = (d3+d1)/d1; + } else { + q = (d2+d0)/d0; + } + + gl_Position[0] *= q; + gl_Position[1] *= q; + gl_Position[3] = q; + + vTexCoord = gl_MultiTexCoord0.xy; + } else { + vec2 pos; + + if (gl_VertexID == 0) { // top left + pos = p2; + } else if (gl_VertexID == 1) { // top right + pos = p3; + } else if (gl_VertexID == 2) { // bottom right + pos = p1; + } else if (gl_VertexID == 3) { // bottom left + pos = p0; + } + + q = pos - p0; + b1 = p1 - p0; + b2 = p2 - p0; + b3 = p0 - p1 - p2 + p3; + } } \ No newline at end of file diff --git a/effects/internal/cornerpineffect.cpp b/effects/internal/cornerpineffect.cpp index 059945a2f..7b7c4b9a6 100644 --- a/effects/internal/cornerpineffect.cpp +++ b/effects/internal/cornerpineffect.cpp @@ -1,116 +1,116 @@ -/*** - - 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 "cornerpineffect.h" - -#include "global/path.h" -#include "timeline/clip.h" -#include "global/debug.h" - -CornerPinEffect::CornerPinEffect(Clip* c) : OldEffectNode(c) { - SetFlags(OldEffectNode::CoordsFlag | OldEffectNode::ShaderFlag); - - top_left = new Vec2Input(this, "topleft", tr("Top Left")); - - top_right = new Vec2Input(this, "topright", tr("Top Right")); - - bottom_left = new Vec2Input(this, "bottomleft", tr("Bottom Left")); - - bottom_right = new Vec2Input(this, "bottomright", tr("Bottom Right")); - - perspective = new BoolInput(this, "perspective", tr("Perspective")); - perspective->SetValueAt(0, true); - - top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - 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 = 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 = 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 = static_cast(bottom_right->Field(0)); - bottom_right_gizmo->y_field1 = static_cast(bottom_right->Field(1)); - - 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; -} - -OldEffectNodePtr CornerPinEffect::Create(Clip *c) -{ - return std::make_shared(c); -} - -void CornerPinEffect::process_coords(double timecode, GLTextureCoords &coords, int) { - 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) { - 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] = 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; -} +/*** + + 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 "cornerpineffect.h" + +#include "global/path.h" +#include "timeline/clip.h" +#include "global/debug.h" + +CornerPinEffect::CornerPinEffect(Clip* c) : OldEffectNode(c) { + SetFlags(OldEffectNode::CoordsFlag | OldEffectNode::ShaderFlag); + + top_left = new Vec2Input(this, "topleft", tr("Top Left")); + + top_right = new Vec2Input(this, "topright", tr("Top Right")); + + bottom_left = new Vec2Input(this, "bottomleft", tr("Bottom Left")); + + bottom_right = new Vec2Input(this, "bottomright", tr("Bottom Right")); + + perspective = new BoolInput(this, "perspective", tr("Perspective")); + perspective->SetValueAt(0, true); + + top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + 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 = 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 = 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 = static_cast(bottom_right->Field(0)); + bottom_right_gizmo->y_field1 = static_cast(bottom_right->Field(1)); + + 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; +} + +OldEffectNodePtr CornerPinEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + +void CornerPinEffect::process_coords(double timecode, GLTextureCoords &coords, int) { + 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) { + 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] = 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 06d7a61f7..a9ecbb2ad 100644 --- a/effects/internal/cornerpineffect.h +++ b/effects/internal/cornerpineffect.h @@ -1,56 +1,56 @@ -/*** - - 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 CORNERPINEFFECT_H -#define CORNERPINEFFECT_H - -#include "nodes/oldeffectnode.h" - -class CornerPinEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr 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: - Vec2Input* top_left; - Vec2Input* top_right; - Vec2Input* bottom_left; - Vec2Input* bottom_right; - - BoolInput* perspective; - - EffectGizmo* top_left_gizmo; - EffectGizmo* top_right_gizmo; - EffectGizmo* bottom_left_gizmo; - EffectGizmo* bottom_right_gizmo; -}; - -#endif // CORNERPINEFFECT_H +/*** + + 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 CORNERPINEFFECT_H +#define CORNERPINEFFECT_H + +#include "nodes/oldeffectnode.h" + +class CornerPinEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr 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: + Vec2Input* top_left; + Vec2Input* top_right; + Vec2Input* bottom_left; + Vec2Input* bottom_right; + + BoolInput* perspective; + + EffectGizmo* top_left_gizmo; + EffectGizmo* top_right_gizmo; + EffectGizmo* bottom_left_gizmo; + EffectGizmo* bottom_right_gizmo; +}; + +#endif // CORNERPINEFFECT_H diff --git a/effects/internal/crossdissolvetransition.h b/effects/internal/crossdissolvetransition.h index b95588716..13d33f5d5 100644 --- a/effects/internal/crossdissolvetransition.h +++ b/effects/internal/crossdissolvetransition.h @@ -1,41 +1,41 @@ -/*** - - 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 CROSSDISSOLVETRANSITION_H -#define CROSSDISSOLVETRANSITION_H - -#include "effects/transition.h" - -class CrossDissolveTransition : public Transition { -public: - 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 OldEffectNodePtr Create(Clip *c) override; - - virtual void process_coords(double timecode, GLTextureCoords &, int data) override; -}; - -#endif // CROSSDISSOLVETRANSITION_H +/*** + + 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 CROSSDISSOLVETRANSITION_H +#define CROSSDISSOLVETRANSITION_H + +#include "effects/transition.h" + +class CrossDissolveTransition : public Transition { +public: + 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 OldEffectNodePtr 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 b1dbe0607..f6c5910d3 100644 --- a/effects/internal/cubetransition.h +++ b/effects/internal/cubetransition.h @@ -1,32 +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 . - -***/ - -#ifndef CUBETRANSITION_H -#define CUBETRANSITION_H - -#include "effects/transition.h" - -class CubeTransition : public Transition { -public: - CubeTransition(Clip* c, Clip* s); - void process_coords(double timecode, GLTextureCoords &, int data); -}; - -#endif // CUBETRANSITION_H +/*** + + 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 CUBETRANSITION_H +#define CUBETRANSITION_H + +#include "effects/transition.h" + +class CubeTransition : public Transition { +public: + 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 934a76b04..3d2a178ef 100644 --- a/effects/internal/dropshadoweffect.cpp +++ b/effects/internal/dropshadoweffect.cpp @@ -1,25 +1,25 @@ -/*** - - 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() { - -} +/*** + + 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 5492f09a3..e6bcdbc81 100644 --- a/effects/internal/dropshadoweffect.h +++ b/effects/internal/dropshadoweffect.h @@ -1,31 +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 DROPSHADOWEFFECT_H -#define DROPSHADOWEFFECT_H - - -class DropShadowEffect -{ -public: - DropShadowEffect(); -}; - -#endif // DROPSHADOWEFFECT_H +/*** + + 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 + + +class DropShadowEffect +{ +public: + DropShadowEffect(); +}; + +#endif // DROPSHADOWEFFECT_H diff --git a/effects/internal/exponentialfadetransition.cpp b/effects/internal/exponentialfadetransition.cpp index 791dd5a03..095e149f5 100644 --- a/effects/internal/exponentialfadetransition.cpp +++ b/effects/internal/exponentialfadetransition.cpp @@ -1,83 +1,83 @@ -/*** - - 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 "exponentialfadetransition.h" - -#include - -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; -} - -OldEffectNodePtr 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. + +***/ + +#include "exponentialfadetransition.h" + +#include + +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; +} + +OldEffectNodePtr 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. - -***/ - -#ifndef EXPONENTIALFADETRANSITION_H -#define EXPONENTIALFADETRANSITION_H - -#include "effects/transition.h" - -class ExponentialFadeTransition : public Transition { -public: - 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 OldEffectNodePtr 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 +/*** + + 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 EXPONENTIALFADETRANSITION_H +#define EXPONENTIALFADETRANSITION_H + +#include "effects/transition.h" + +class ExponentialFadeTransition : public Transition { +public: + 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 OldEffectNodePtr 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 ed5d9a594..64d15fe03 100644 --- a/effects/internal/fillleftrighteffect.cpp +++ b/effects/internal/fillleftrighteffect.cpp @@ -1,84 +1,84 @@ -/*** - - 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 "fillleftrighteffect.h" - -enum FillType { - FILL_TYPE_LEFT, - FILL_TYPE_RIGHT -}; - -FillLeftRightEffect::FillLeftRightEffect(Clip* c) : OldEffectNode(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); -} - -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; -} - -OldEffectNodePtr 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]; - } - } - } -} +/*** + + 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 "fillleftrighteffect.h" + +enum FillType { + FILL_TYPE_LEFT, + FILL_TYPE_RIGHT +}; + +FillLeftRightEffect::FillLeftRightEffect(Clip* c) : OldEffectNode(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); +} + +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; +} + +OldEffectNodePtr 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 11de0a3e6..58e0e3006 100644 --- a/effects/internal/fillleftrighteffect.h +++ b/effects/internal/fillleftrighteffect.h @@ -1,48 +1,48 @@ -/*** - - 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 FILLLEFTRIGHTEFFECT_H -#define FILLLEFTRIGHTEFFECT_H - -#include "nodes/oldeffectnode.h" - -class FillLeftRightEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr 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: - ComboInput* fill_type; -}; - -#endif // FILLLEFTRIGHTEFFECT_H +/*** + + 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 FILLLEFTRIGHTEFFECT_H +#define FILLLEFTRIGHTEFFECT_H + +#include "nodes/oldeffectnode.h" + +class FillLeftRightEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr 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: + ComboInput* fill_type; +}; + +#endif // FILLLEFTRIGHTEFFECT_H diff --git a/effects/internal/frei0reffect.cpp b/effects/internal/frei0reffect.cpp index c01ee0835..9508dd243 100644 --- a/effects/internal/frei0reffect.cpp +++ b/effects/internal/frei0reffect.cpp @@ -1,196 +1,196 @@ -/*** - - 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 "frei0reffect.h" - -#ifndef NOFREI0R - -#include -#include - -#include "timeline/clip.h" - -typedef f0r_instance_t (*f0rConstructFunc)(unsigned int width, unsigned int height); -typedef int (*f0rInitFunc) (); -typedef void (*f0rDeinitFunc) (); -typedef void (*f0rUpdateFunc) (f0r_instance_t instance, - double time, const uint32_t* inframe, uint32_t* outframe); -typedef void (*f0rDestructFunc)(f0r_instance_t instance); -typedef void (*f0rGetPluginInfo)(f0r_plugin_info_t* info); -typedef void (*f0rSetParamValue) (f0r_instance_t instance, - f0r_param_t param, int param_index); - -Frei0rEffect::Frei0rEffect(Clip* c, const EffectMeta *em) : - Effect(c, em), - open(false) -{ - SetFlags(ImageFlag); - - // Windows DLL loading routine - QString dll_fn = QDir(em->path).filePath(em->filename); - - handle.setFileName(dll_fn); - - - if (!handle.load()) { - QString dll_error = handle.errorString(); - QMessageBox::critical(nullptr, tr("Error loading Frei0r plugin"), - tr("Failed to load Frei0r plugin \"%1\": %2").arg(dll_fn, dll_error)); - - return; - } - - f0rInitFunc init = reinterpret_cast(handle.resolve("f0r_init")); - init(); - - construct_module(); - - f0r_plugin_info_t info; - f0rGetPluginInfo info_func = reinterpret_cast(handle.resolve("f0r_get_plugin_info")); - info_func(&info); - - param_count = info.num_params; - - get_param_info = reinterpret_cast(handle.resolve("f0r_get_param_info")); - for (int i=0;i= 0 && param_info.type <= F0R_PARAM_STRING) { - EffectRow* row = new EffectRow(this, param_info.name); - switch (param_info.type) { - case F0R_PARAM_BOOL: - new BoolField(row, QString::number(i)); - break; - case F0R_PARAM_DOUBLE: - { - DoubleField* f = new DoubleField(row, QString::number(i)); - f->SetMinimum(0); - f->SetMaximum(100); - } - break; - case F0R_PARAM_COLOR: - new ColorField(row, QString::number(i)); - break; - case F0R_PARAM_POSITION: - { - DoubleField* fx = new DoubleField(row, QString("%1X").arg(QString::number(i))); - fx->SetMinimum(0); - fx->SetMaximum(100); - DoubleField* fy = new DoubleField(row, QString("%1Y").arg(QString::number(i))); - fy->SetMinimum(0); - fy->SetMaximum(100); - } - break; - case F0R_PARAM_STRING: - new StringField(row, QString::number(i), false); - break; - } - } - } -} - -Frei0rEffect::~Frei0rEffect() { - if (handle.isLoaded()) { - f0rDeinitFunc deinit = reinterpret_cast(handle.resolve("f0r_deinit")); - deinit(); - - handle.unload(); - } -} - -void Frei0rEffect::process_image(double timecode, uint8_t *input, uint8_t *output, int) { - f0rUpdateFunc update_func = reinterpret_cast(handle.resolve("f0r_update")); - - for (int i=0;i(handle.resolve("f0r_set_param_value")); - switch (param_info.type) { - case F0R_PARAM_BOOL: - { - double b = param_row->Field(0)->GetValueAt(timecode).toBool(); - set_param(instance, &b, i); - } - break; - case F0R_PARAM_DOUBLE: - { - double d = param_row->Field(0)->GetValueAt(timecode).toDouble()*0.01; - set_param(instance, &d, i); - } - break; - case F0R_PARAM_COLOR: - { - QColor qcolor = param_row->Field(0)->GetValueAt(timecode).value(); - - f0r_param_color fcolor; - fcolor.r = float(qcolor.redF()); - fcolor.g = float(qcolor.greenF()); - fcolor.b = float(qcolor.blueF()); - - set_param(instance, &fcolor, i); - } - break; - case F0R_PARAM_POSITION: - { - f0r_param_position pos; - pos.x = param_row->Field(0)->GetValueAt(timecode).toDouble(); - pos.y = param_row->Field(1)->GetValueAt(timecode).toDouble(); - set_param(instance, &pos, i); - } - break; - case F0R_PARAM_STRING: - { - QByteArray bytes = param_row->Field(0)->GetValueAt(timecode).toString().toUtf8(); - char* byte_data = bytes.data(); - set_param(instance, &byte_data, i); - } - break; - } - } - - update_func(instance, timecode, reinterpret_cast(input), reinterpret_cast(output)); -} - -void Frei0rEffect::refresh() { - destruct_module(); - construct_module(); -} - -void Frei0rEffect::destruct_module() { - if (open) { - f0rDestructFunc destruct = reinterpret_cast(handle.resolve("f0r_destruct")); - destruct(instance); - - open = false; - } -} - -void Frei0rEffect::construct_module() { - f0rConstructFunc construct = reinterpret_cast(handle.resolve("f0r_construct")); - instance = construct(parent_clip->media_width(), parent_clip->media_height()); - - open = true; -} - -#endif +/*** + + 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 "frei0reffect.h" + +#ifndef NOFREI0R + +#include +#include + +#include "timeline/clip.h" + +typedef f0r_instance_t (*f0rConstructFunc)(unsigned int width, unsigned int height); +typedef int (*f0rInitFunc) (); +typedef void (*f0rDeinitFunc) (); +typedef void (*f0rUpdateFunc) (f0r_instance_t instance, + double time, const uint32_t* inframe, uint32_t* outframe); +typedef void (*f0rDestructFunc)(f0r_instance_t instance); +typedef void (*f0rGetPluginInfo)(f0r_plugin_info_t* info); +typedef void (*f0rSetParamValue) (f0r_instance_t instance, + f0r_param_t param, int param_index); + +Frei0rEffect::Frei0rEffect(Clip* c, const EffectMeta *em) : + Effect(c, em), + open(false) +{ + SetFlags(ImageFlag); + + // Windows DLL loading routine + QString dll_fn = QDir(em->path).filePath(em->filename); + + handle.setFileName(dll_fn); + + + if (!handle.load()) { + QString dll_error = handle.errorString(); + QMessageBox::critical(nullptr, tr("Error loading Frei0r plugin"), + tr("Failed to load Frei0r plugin \"%1\": %2").arg(dll_fn, dll_error)); + + return; + } + + f0rInitFunc init = reinterpret_cast(handle.resolve("f0r_init")); + init(); + + construct_module(); + + f0r_plugin_info_t info; + f0rGetPluginInfo info_func = reinterpret_cast(handle.resolve("f0r_get_plugin_info")); + info_func(&info); + + param_count = info.num_params; + + get_param_info = reinterpret_cast(handle.resolve("f0r_get_param_info")); + for (int i=0;i= 0 && param_info.type <= F0R_PARAM_STRING) { + EffectRow* row = new EffectRow(this, param_info.name); + switch (param_info.type) { + case F0R_PARAM_BOOL: + new BoolField(row, QString::number(i)); + break; + case F0R_PARAM_DOUBLE: + { + DoubleField* f = new DoubleField(row, QString::number(i)); + f->SetMinimum(0); + f->SetMaximum(100); + } + break; + case F0R_PARAM_COLOR: + new ColorField(row, QString::number(i)); + break; + case F0R_PARAM_POSITION: + { + DoubleField* fx = new DoubleField(row, QString("%1X").arg(QString::number(i))); + fx->SetMinimum(0); + fx->SetMaximum(100); + DoubleField* fy = new DoubleField(row, QString("%1Y").arg(QString::number(i))); + fy->SetMinimum(0); + fy->SetMaximum(100); + } + break; + case F0R_PARAM_STRING: + new StringField(row, QString::number(i), false); + break; + } + } + } +} + +Frei0rEffect::~Frei0rEffect() { + if (handle.isLoaded()) { + f0rDeinitFunc deinit = reinterpret_cast(handle.resolve("f0r_deinit")); + deinit(); + + handle.unload(); + } +} + +void Frei0rEffect::process_image(double timecode, uint8_t *input, uint8_t *output, int) { + f0rUpdateFunc update_func = reinterpret_cast(handle.resolve("f0r_update")); + + for (int i=0;i(handle.resolve("f0r_set_param_value")); + switch (param_info.type) { + case F0R_PARAM_BOOL: + { + double b = param_row->Field(0)->GetValueAt(timecode).toBool(); + set_param(instance, &b, i); + } + break; + case F0R_PARAM_DOUBLE: + { + double d = param_row->Field(0)->GetValueAt(timecode).toDouble()*0.01; + set_param(instance, &d, i); + } + break; + case F0R_PARAM_COLOR: + { + QColor qcolor = param_row->Field(0)->GetValueAt(timecode).value(); + + f0r_param_color fcolor; + fcolor.r = float(qcolor.redF()); + fcolor.g = float(qcolor.greenF()); + fcolor.b = float(qcolor.blueF()); + + set_param(instance, &fcolor, i); + } + break; + case F0R_PARAM_POSITION: + { + f0r_param_position pos; + pos.x = param_row->Field(0)->GetValueAt(timecode).toDouble(); + pos.y = param_row->Field(1)->GetValueAt(timecode).toDouble(); + set_param(instance, &pos, i); + } + break; + case F0R_PARAM_STRING: + { + QByteArray bytes = param_row->Field(0)->GetValueAt(timecode).toString().toUtf8(); + char* byte_data = bytes.data(); + set_param(instance, &byte_data, i); + } + break; + } + } + + update_func(instance, timecode, reinterpret_cast(input), reinterpret_cast(output)); +} + +void Frei0rEffect::refresh() { + destruct_module(); + construct_module(); +} + +void Frei0rEffect::destruct_module() { + if (open) { + f0rDestructFunc destruct = reinterpret_cast(handle.resolve("f0r_destruct")); + destruct(instance); + + open = false; + } +} + +void Frei0rEffect::construct_module() { + f0rConstructFunc construct = reinterpret_cast(handle.resolve("f0r_construct")); + instance = construct(parent_clip->media_width(), parent_clip->media_height()); + + open = true; +} + +#endif diff --git a/effects/internal/frei0reffect.h b/effects/internal/frei0reffect.h index 2078289b0..693c9df5c 100644 --- a/effects/internal/frei0reffect.h +++ b/effects/internal/frei0reffect.h @@ -1,55 +1,55 @@ -/*** - - 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 FREI0REFFECT_H -#define FREI0REFFECT_H - -#ifndef NOFREI0R - -#include -#include - -#include "effects/effect.h" - -typedef void (*f0rGetParamInfo)(f0r_param_info_t * info, - int param_index ); - -class Frei0rEffect : public Effect { - Q_OBJECT -public: - Frei0rEffect(Clip* c, const EffectMeta* em); - ~Frei0rEffect(); - - virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size); - - virtual void refresh(); -private: - QLibrary handle; - f0r_instance_t instance; - int param_count; - f0rGetParamInfo get_param_info; - void destruct_module(); - void construct_module(); - bool open; -}; - -#endif - -#endif // FREI0REFFECT_H +/*** + + 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 FREI0REFFECT_H +#define FREI0REFFECT_H + +#ifndef NOFREI0R + +#include +#include + +#include "effects/effect.h" + +typedef void (*f0rGetParamInfo)(f0r_param_info_t * info, + int param_index ); + +class Frei0rEffect : public Effect { + Q_OBJECT +public: + Frei0rEffect(Clip* c, const EffectMeta* em); + ~Frei0rEffect(); + + virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size); + + virtual void refresh(); +private: + QLibrary handle; + f0r_instance_t instance; + int param_count; + f0rGetParamInfo get_param_info; + void destruct_module(); + void construct_module(); + bool open; +}; + +#endif + +#endif // FREI0REFFECT_H diff --git a/effects/internal/internalshaders.qrc b/effects/internal/internalshaders.qrc index ae51e0b27..a6239c9e1 100644 --- a/effects/internal/internalshaders.qrc +++ b/effects/internal/internalshaders.qrc @@ -1,9 +1,9 @@ - - - common.vert - cornerpin.frag - cornerpin.vert - premultiply.frag - dropshadow.frag - - + + + common.vert + cornerpin.frag + cornerpin.vert + premultiply.frag + dropshadow.frag + + diff --git a/effects/internal/linearfadetransition.cpp b/effects/internal/linearfadetransition.cpp index 270716a5b..f25eff752 100644 --- a/effects/internal/linearfadetransition.cpp +++ b/effects/internal/linearfadetransition.cpp @@ -1,79 +1,79 @@ -/*** - - 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 "linearfadetransition.h" - -LinearFadeTransition::LinearFadeTransition(Clip* c) : Transition(c) {} - -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; -} - -OldEffectNodePtr 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. + +***/ + +#include "linearfadetransition.h" + +LinearFadeTransition::LinearFadeTransition(Clip* c) : Transition(c) {} + +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; +} + +OldEffectNodePtr 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. - -***/ - -#ifndef LINEARFADETRANSITION_H -#define LINEARFADETRANSITION_H - -#include "effects/transition.h" - -class LinearFadeTransition : public Transition { -public: - 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 OldEffectNodePtr 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 +/*** + + 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 LINEARFADETRANSITION_H +#define LINEARFADETRANSITION_H + +#include "effects/transition.h" + +class LinearFadeTransition : public Transition { +public: + 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 OldEffectNodePtr 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 05b278f5b..6208a1e4e 100644 --- a/effects/internal/logarithmicfadetransition.cpp +++ b/effects/internal/logarithmicfadetransition.cpp @@ -1,83 +1,83 @@ -/*** - - 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 "logarithmicfadetransition.h" - -#include - -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; -} - -OldEffectNodePtr 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. + +***/ + +#include "logarithmicfadetransition.h" + +#include + +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; +} + +OldEffectNodePtr 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. - -***/ - -#ifndef LOGARITHMICFADETRANSITION_H -#define LOGARITHMICFADETRANSITION_H - -#include "effects/transition.h" - -class LogarithmicFadeTransition : public Transition { -public: - 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 OldEffectNodePtr 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 +/*** + + 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 LOGARITHMICFADETRANSITION_H +#define LOGARITHMICFADETRANSITION_H + +#include "effects/transition.h" + +class LogarithmicFadeTransition : public Transition { +public: + 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 OldEffectNodePtr 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/ocio.frag b/effects/internal/ocio.frag index 1d732b00d..479c63a67 100644 --- a/effects/internal/ocio.frag +++ b/effects/internal/ocio.frag @@ -1,10 +1,10 @@ -#version 110 - -uniform sampler2D tex1; -uniform sampler3D tex2; - -void main() -{ - vec4 col = texture2D(tex1, gl_TexCoord[0].st); - gl_FragColor = OCIODisplay(col, tex2); +#version 110 + +uniform sampler2D tex1; +uniform sampler3D tex2; + +void main() +{ + vec4 col = texture2D(tex1, gl_TexCoord[0].st); + gl_FragColor = OCIODisplay(col, tex2); } \ No newline at end of file diff --git a/effects/internal/paneffect.cpp b/effects/internal/paneffect.cpp index cea8936db..2d713f884 100644 --- a/effects/internal/paneffect.cpp +++ b/effects/internal/paneffect.cpp @@ -1,96 +1,96 @@ -/*** - - 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 "paneffect.h" - -#include -#include -#include -#include - -#include "ui/labelslider.h" -#include "ui/collapsiblewidget.h" - -PanEffect::PanEffect(Clip* c) : OldEffectNode(c) { - pan_val = new DoubleInput(this, "pan", tr("Pan")); - pan_val->SetMinimum(-100); - pan_val->SetDefault(0); - pan_val->SetMaximum(100); -} - -QString PanEffect::name() -{ - return tr("Pan"); -} - -QString PanEffect::id() -{ - return "org.olivevideoeditor.Olive.pan"; -} - -QString PanEffect::description() -{ - return tr("Modifying the panning on a stereo audio clip."); -} - -EffectType PanEffect::type() -{ - return EFFECT_TYPE_EFFECT; -} - -olive::TrackType PanEffect::subtype() -{ - return olive::kTypeAudio; -} - -OldEffectNodePtr PanEffect::Create(Clip *c) -{ - return std::make_shared(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); - - if (pan_field_val < 0) { - // affect right channel - samples[1][i] *= (1.0-pval); - } else { - // affect left channel - samples[0][i] *= (1.0-pval); - } - } -} +/*** + + 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 "paneffect.h" + +#include +#include +#include +#include + +#include "ui/labelslider.h" +#include "ui/collapsiblewidget.h" + +PanEffect::PanEffect(Clip* c) : OldEffectNode(c) { + pan_val = new DoubleInput(this, "pan", tr("Pan")); + pan_val->SetMinimum(-100); + pan_val->SetDefault(0); + pan_val->SetMaximum(100); +} + +QString PanEffect::name() +{ + return tr("Pan"); +} + +QString PanEffect::id() +{ + return "org.olivevideoeditor.Olive.pan"; +} + +QString PanEffect::description() +{ + return tr("Modifying the panning on a stereo audio clip."); +} + +EffectType PanEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType PanEffect::subtype() +{ + return olive::kTypeAudio; +} + +OldEffectNodePtr PanEffect::Create(Clip *c) +{ + return std::make_shared(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); + + if (pan_field_val < 0) { + // affect right channel + samples[1][i] *= (1.0-pval); + } else { + // affect left channel + samples[0][i] *= (1.0-pval); + } + } +} diff --git a/effects/internal/paneffect.h b/effects/internal/paneffect.h index 9a682b76a..130d8fe50 100644 --- a/effects/internal/paneffect.h +++ b/effects/internal/paneffect.h @@ -1,48 +1,48 @@ -/*** - - 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 PANEFFECT_H -#define PANEFFECT_H - -#include "nodes/oldeffectnode.h" - -class PanEffect : public OldEffectNode { - Q_OBJECT -public: - PanEffect(Clip* c); - - virtual QString name() override; - virtual QString id() override; - virtual QString description() override; - virtual EffectType type() override; - virtual olive::TrackType subtype() override; - virtual OldEffectNodePtr 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 +/*** + + 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 PANEFFECT_H +#define PANEFFECT_H + +#include "nodes/oldeffectnode.h" + +class PanEffect : public OldEffectNode { + Q_OBJECT +public: + PanEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual OldEffectNodePtr 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/premultiply.frag b/effects/internal/premultiply.frag index bc16e5cb4..64381fb27 100644 --- a/effects/internal/premultiply.frag +++ b/effects/internal/premultiply.frag @@ -1,10 +1,10 @@ -#version 110 - -uniform sampler2D tex; -varying vec2 vTexCoord; - -void main(void) { - vec4 c = texture2D(tex, vTexCoord); - c.rgb *= c.a; - gl_FragColor = c; +#version 110 + +uniform sampler2D tex; +varying vec2 vTexCoord; + +void main(void) { + vec4 c = texture2D(tex, vTexCoord); + c.rgb *= c.a; + gl_FragColor = c; } \ No newline at end of file diff --git a/effects/internal/richtexteffect.cpp b/effects/internal/richtexteffect.cpp index b8b9a42af..09e5c7efd 100644 --- a/effects/internal/richtexteffect.cpp +++ b/effects/internal/richtexteffect.cpp @@ -1,225 +1,225 @@ -/*** - - 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 -#include - -#include "timeline/clip.h" -#include "ui/blur.h" - -enum AutoscrollDirection { - SCROLL_OFF, - SCROLL_UP, - SCROLL_DOWN, - SCROLL_LEFT, - SCROLL_RIGHT, -}; - -RichTextEffect::RichTextEffect(Clip *c) : - OldEffectNode(c) -{ - SetFlags(OldEffectNode::SuperimposeFlag); - - text_val = new StringInput(this, "text", tr("Text")); - - padding_field = new DoubleInput(this, "padding", tr("Padding")); - - position = new Vec2Input(this, "pos", tr("Position")); - - 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); - - 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); - - shadow_bool = new BoolInput(this, "shadow", tr("Shadow")); - - shadow_color = new ColorInput(this, "shadowcolor", tr("Shadow Color")); - - shadow_angle = new DoubleInput(this, "shadowangle", tr("Shadow Angle")); - - shadow_distance = new DoubleInput(this, "shadowdistance", tr("Shadow Distance")); - shadow_distance->SetMinimum(0); - - shadow_softness = new DoubleInput(this, "shadowsoftness", tr("Shadow Softness")); - shadow_softness->SetMinimum(0); - - shadow_opacity = new DoubleInput(this, "shadowopacity", tr("Shadow Opacity")); - shadow_opacity->SetMinimum(0); - shadow_opacity->SetMaximum(100); - - // Create default text - text_val->SetValueAt(0, "" - "" - "
Sample Text
" - "" - ""); -} - -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; -} - -OldEffectNodePtr RichTextEffect::Create(Clip *c) -{ - return std::make_shared(c); -} - -void RichTextEffect::redraw(double timecode) -{ - QPainter p(&img); - p.setRenderHint(QPainter::Antialiasing); - int width = img.width(); - int height = img.height(); - - int padding = qRound(padding_field->GetDoubleAt(timecode)); - - width -= 2 * padding; - height -= 2 * padding; - - QTextDocument td; - td.setHtml(text_val->GetStringAt(timecode)); - td.setTextWidth(width); - - QPoint translation = position->GetVector2DAt(timecode).toPoint(); - translation += {padding, padding}; - - int doc_height = qRound(td.size().height()); - - AutoscrollDirection auto_scroll_dir = static_cast(autoscroll->GetValueAt(timecode).toInt()); - - double scroll_progress = 0; - - if (auto_scroll_dir != SCROLL_OFF) { - double clip_length_secs = double(parent_clip->length()) / parent_clip->media_frame_rate(); - scroll_progress = (timecode - double(parent_clip->clip_in()) / parent_clip->media_frame_rate()) / clip_length_secs; - } - - if (auto_scroll_dir == SCROLL_OFF || auto_scroll_dir == SCROLL_LEFT || auto_scroll_dir == SCROLL_RIGHT) { - - // If we're not auto-scrolling the vertical direction, respect the vertical alignment - if (vertical_align->GetValueAt(timecode).toInt() == Qt::AlignCenter) { - translation.setY(translation.y() + height / 2 - doc_height / 2); - } else if (vertical_align->GetValueAt(timecode).toInt() == Qt::AlignBottom) { - translation.setY(translation.y() + height - doc_height); - } - - // Check if we are autoscrolling - if (auto_scroll_dir != SCROLL_OFF) { - - if (auto_scroll_dir == SCROLL_LEFT) { - scroll_progress = 1.0 - scroll_progress; - } - - int doc_width = qRound(td.size().width()); - 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) { - - // Auto-scroll bottom to top or top to bottom - - if (auto_scroll_dir == SCROLL_UP) { - scroll_progress = 1.0 - scroll_progress; - } - - translation.setY(translation.y() + qRound(-doc_height + (img.height() + doc_height)*scroll_progress)); - - } - - QRect clip_rect = img.rect(); - clip_rect.translate(-translation); - p.translate(translation); - - img.fill(Qt::transparent); - - // draw software shadow - if (shadow_bool->GetBoolAt(timecode)) { - - // calculate offset using distance and angle - double angle = shadow_angle->GetDoubleAt(timecode) * M_PI / 180.0; - double distance = qFloor(shadow_distance->GetDoubleAt(timecode)); - int shadow_x_offset = qRound(qCos(angle) * distance); - int shadow_y_offset = qRound(qSin(angle) * distance); - - p.translate(shadow_x_offset, shadow_y_offset); - clip_rect.translate(-shadow_x_offset, -shadow_y_offset); - - td.drawContents(&p, clip_rect); - - int blurSoftness = qFloor(shadow_softness->GetDoubleAt(timecode)); - if (blurSoftness > 0) { - olive::ui::blur(img, img.rect(), blurSoftness, true); - } - - p.setCompositionMode(QPainter::CompositionMode_SourceIn); - - p.fillRect(clip_rect, shadow_color->GetColorAt(timecode)); - - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - p.translate(-shadow_x_offset, -shadow_y_offset); - clip_rect.translate(shadow_x_offset, shadow_y_offset); - } - - td.drawContents(&p, clip_rect); - - p.end(); -} - -bool RichTextEffect::AlwaysUpdate() -{ - return autoscroll->GetValueAt(Now()).toInt() != SCROLL_OFF; -} +/*** + + 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 +#include + +#include "timeline/clip.h" +#include "ui/blur.h" + +enum AutoscrollDirection { + SCROLL_OFF, + SCROLL_UP, + SCROLL_DOWN, + SCROLL_LEFT, + SCROLL_RIGHT, +}; + +RichTextEffect::RichTextEffect(Clip *c) : + OldEffectNode(c) +{ + SetFlags(OldEffectNode::SuperimposeFlag); + + text_val = new StringInput(this, "text", tr("Text")); + + padding_field = new DoubleInput(this, "padding", tr("Padding")); + + position = new Vec2Input(this, "pos", tr("Position")); + + 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); + + 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); + + shadow_bool = new BoolInput(this, "shadow", tr("Shadow")); + + shadow_color = new ColorInput(this, "shadowcolor", tr("Shadow Color")); + + shadow_angle = new DoubleInput(this, "shadowangle", tr("Shadow Angle")); + + shadow_distance = new DoubleInput(this, "shadowdistance", tr("Shadow Distance")); + shadow_distance->SetMinimum(0); + + shadow_softness = new DoubleInput(this, "shadowsoftness", tr("Shadow Softness")); + shadow_softness->SetMinimum(0); + + shadow_opacity = new DoubleInput(this, "shadowopacity", tr("Shadow Opacity")); + shadow_opacity->SetMinimum(0); + shadow_opacity->SetMaximum(100); + + // Create default text + text_val->SetValueAt(0, "" + "" + "
Sample Text
" + "" + ""); +} + +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; +} + +OldEffectNodePtr RichTextEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + +void RichTextEffect::redraw(double timecode) +{ + QPainter p(&img); + p.setRenderHint(QPainter::Antialiasing); + int width = img.width(); + int height = img.height(); + + int padding = qRound(padding_field->GetDoubleAt(timecode)); + + width -= 2 * padding; + height -= 2 * padding; + + QTextDocument td; + td.setHtml(text_val->GetStringAt(timecode)); + td.setTextWidth(width); + + QPoint translation = position->GetVector2DAt(timecode).toPoint(); + translation += {padding, padding}; + + int doc_height = qRound(td.size().height()); + + AutoscrollDirection auto_scroll_dir = static_cast(autoscroll->GetValueAt(timecode).toInt()); + + double scroll_progress = 0; + + if (auto_scroll_dir != SCROLL_OFF) { + double clip_length_secs = double(parent_clip->length()) / parent_clip->media_frame_rate(); + scroll_progress = (timecode - double(parent_clip->clip_in()) / parent_clip->media_frame_rate()) / clip_length_secs; + } + + if (auto_scroll_dir == SCROLL_OFF || auto_scroll_dir == SCROLL_LEFT || auto_scroll_dir == SCROLL_RIGHT) { + + // If we're not auto-scrolling the vertical direction, respect the vertical alignment + if (vertical_align->GetValueAt(timecode).toInt() == Qt::AlignCenter) { + translation.setY(translation.y() + height / 2 - doc_height / 2); + } else if (vertical_align->GetValueAt(timecode).toInt() == Qt::AlignBottom) { + translation.setY(translation.y() + height - doc_height); + } + + // Check if we are autoscrolling + if (auto_scroll_dir != SCROLL_OFF) { + + if (auto_scroll_dir == SCROLL_LEFT) { + scroll_progress = 1.0 - scroll_progress; + } + + int doc_width = qRound(td.size().width()); + 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) { + + // Auto-scroll bottom to top or top to bottom + + if (auto_scroll_dir == SCROLL_UP) { + scroll_progress = 1.0 - scroll_progress; + } + + translation.setY(translation.y() + qRound(-doc_height + (img.height() + doc_height)*scroll_progress)); + + } + + QRect clip_rect = img.rect(); + clip_rect.translate(-translation); + p.translate(translation); + + img.fill(Qt::transparent); + + // draw software shadow + if (shadow_bool->GetBoolAt(timecode)) { + + // calculate offset using distance and angle + double angle = shadow_angle->GetDoubleAt(timecode) * M_PI / 180.0; + double distance = qFloor(shadow_distance->GetDoubleAt(timecode)); + int shadow_x_offset = qRound(qCos(angle) * distance); + int shadow_y_offset = qRound(qSin(angle) * distance); + + p.translate(shadow_x_offset, shadow_y_offset); + clip_rect.translate(-shadow_x_offset, -shadow_y_offset); + + td.drawContents(&p, clip_rect); + + int blurSoftness = qFloor(shadow_softness->GetDoubleAt(timecode)); + if (blurSoftness > 0) { + olive::ui::blur(img, img.rect(), blurSoftness, true); + } + + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + + p.fillRect(clip_rect, shadow_color->GetColorAt(timecode)); + + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.translate(-shadow_x_offset, -shadow_y_offset); + clip_rect.translate(shadow_x_offset, shadow_y_offset); + } + + td.drawContents(&p, clip_rect); + + p.end(); +} + +bool RichTextEffect::AlwaysUpdate() +{ + return autoscroll->GetValueAt(Now()).toInt() != SCROLL_OFF; +} diff --git a/effects/internal/richtexteffect.h b/effects/internal/richtexteffect.h index e6c7970e6..28a477c01 100644 --- a/effects/internal/richtexteffect.h +++ b/effects/internal/richtexteffect.h @@ -1,58 +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 "nodes/oldeffectnode.h" - -class RichTextEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr Create(Clip *c) override; - - virtual void redraw(double timecode) override; - -protected: - virtual bool AlwaysUpdate() override; -private: - StringInput* text_val; - DoubleInput* padding_field; - Vec2Input* position; - ComboInput* vertical_align; - ComboInput* autoscroll; - - BoolInput* shadow_bool; - DoubleInput* shadow_angle; - DoubleInput* shadow_distance; - ColorInput* shadow_color; - DoubleInput* shadow_softness; - DoubleInput* shadow_opacity; -}; - -#endif // RICHTEXTEFFECT_H +/*** + + 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 "nodes/oldeffectnode.h" + +class RichTextEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; + +protected: + virtual bool AlwaysUpdate() override; +private: + StringInput* text_val; + DoubleInput* padding_field; + Vec2Input* position; + ComboInput* vertical_align; + ComboInput* autoscroll; + + 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.h b/effects/internal/shakeeffect.h index bcf4163c1..13b818188 100644 --- a/effects/internal/shakeeffect.h +++ b/effects/internal/shakeeffect.h @@ -1,49 +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 SHAKEEFFECT_H -#define SHAKEEFFECT_H - -#include "nodes/oldeffectnode.h" - -#define RANDOM_VAL_SIZE 30 - -class ShakeEffect : public OldEffectNode { - Q_OBJECT -public: - ShakeEffect(Clip* c); - virtual void process_coords(double timecode, GLTextureCoords& coords, int data) override; - - 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 OldEffectNodePtr Create(Clip *c) override; - - DoubleInput* intensity_val; - DoubleInput* rotation_val; - DoubleInput* frequency_val; -private: - double random_vals[RANDOM_VAL_SIZE]; -}; - -#endif // SHAKEEFFECT_H +/*** + + 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 SHAKEEFFECT_H +#define SHAKEEFFECT_H + +#include "nodes/oldeffectnode.h" + +#define RANDOM_VAL_SIZE 30 + +class ShakeEffect : public OldEffectNode { + Q_OBJECT +public: + ShakeEffect(Clip* c); + virtual void process_coords(double timecode, GLTextureCoords& coords, int data) override; + + 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 OldEffectNodePtr Create(Clip *c) override; + + DoubleInput* intensity_val; + DoubleInput* rotation_val; + DoubleInput* frequency_val; +private: + double random_vals[RANDOM_VAL_SIZE]; +}; + +#endif // SHAKEEFFECT_H diff --git a/effects/internal/solideffect.h b/effects/internal/solideffect.h index 26d3211f2..0d3e60410 100644 --- a/effects/internal/solideffect.h +++ b/effects/internal/solideffect.h @@ -1,59 +1,59 @@ -/*** - - 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 SOLIDEFFECT_H -#define SOLIDEFFECT_H - -#include "nodes/oldeffectnode.h" - -#include - -class SolidEffect : public OldEffectNode { - Q_OBJECT -public: - enum SolidType { - SOLID_TYPE_COLOR, - SOLID_TYPE_BARS, - SOLID_TYPE_CHECKERBOARD - }; - - 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 OldEffectNodePtr Create(Clip *c) override; - - virtual void redraw(double timecode) override; - - void SetType(SolidType type); -private slots: - void ui_update(const QVariant &d); -private: - ComboInput* solid_type; - ColorInput* solid_color_field; - DoubleInput* opacity_field; - DoubleInput* checkerboard_size_field; -}; - -#endif // SOLIDEFFECT_H +/*** + + 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 SOLIDEFFECT_H +#define SOLIDEFFECT_H + +#include "nodes/oldeffectnode.h" + +#include + +class SolidEffect : public OldEffectNode { + Q_OBJECT +public: + enum SolidType { + SOLID_TYPE_COLOR, + SOLID_TYPE_BARS, + SOLID_TYPE_CHECKERBOARD + }; + + 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 OldEffectNodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; + + void SetType(SolidType type); +private slots: + void ui_update(const QVariant &d); +private: + ComboInput* solid_type; + ColorInput* solid_color_field; + DoubleInput* opacity_field; + DoubleInput* checkerboard_size_field; +}; + +#endif // SOLIDEFFECT_H diff --git a/effects/internal/texteffect.h b/effects/internal/texteffect.h index 99ca57eb2..c054caaf5 100644 --- a/effects/internal/texteffect.h +++ b/effects/internal/texteffect.h @@ -1,72 +1,72 @@ -/*** - - 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 TEXTEFFECT_H -#define TEXTEFFECT_H - -#include "nodes/oldeffectnode.h" - -#include -#include - -class TextEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr Create(Clip *c) override; - - virtual void redraw(double timecode) override; -private slots: - void outline_enable(bool); - void shadow_enable(bool); -private: - QFont font; - - 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; - - BoolInput* outline_bool; - DoubleInput* outline_width; - ColorInput* outline_color; - - BoolInput* shadow_bool; - DoubleInput* shadow_angle; - DoubleInput* shadow_distance; - ColorInput* shadow_color; - DoubleInput* shadow_softness; - DoubleInput* shadow_opacity; - -}; - -#endif // TEXTEFFECT_H +/*** + + 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 TEXTEFFECT_H +#define TEXTEFFECT_H + +#include "nodes/oldeffectnode.h" + +#include +#include + +class TextEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr Create(Clip *c) override; + + virtual void redraw(double timecode) override; +private slots: + void outline_enable(bool); + void shadow_enable(bool); +private: + QFont font; + + 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; + + BoolInput* outline_bool; + DoubleInput* outline_width; + ColorInput* outline_color; + + BoolInput* shadow_bool; + DoubleInput* shadow_angle; + DoubleInput* shadow_distance; + ColorInput* shadow_color; + DoubleInput* shadow_softness; + DoubleInput* shadow_opacity; + +}; + +#endif // TEXTEFFECT_H diff --git a/effects/internal/toneeffect.cpp b/effects/internal/toneeffect.cpp index 4a2c040fa..4ed9122e6 100644 --- a/effects/internal/toneeffect.cpp +++ b/effects/internal/toneeffect.cpp @@ -1,114 +1,114 @@ -/*** - - 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 "toneeffect.h" - -#include - -#define TONE_TYPE_SINE 0 - -#include "timeline/clip.h" -#include "timeline/sequence.h" - -ToneEffect::ToneEffect(Clip* c) : OldEffectNode(c), sinX(INT_MIN) { - type_val = new ComboInput(this, "type", tr("Type")); - type_val->AddItem(tr("Sine"), TONE_TYPE_SINE); - - freq_val = new DoubleInput(this, "frequency", tr("Frequency")); - freq_val->SetMinimum(20); - freq_val->SetMaximum(20000); - freq_val->SetDefault(1000); - - amount_val = new DoubleInput(this, "amount", tr("Amount")); - amount_val->SetMinimum(0); - amount_val->SetMaximum(100); - amount_val->SetDefault(25); - - mix_val = new BoolInput(this, "mix", tr("Mix")); - mix_val->SetValueAt(0, true); -} - -QString ToneEffect::name() -{ - return tr("Tone"); -} - -QString ToneEffect::id() -{ - return "org.olivevideoeditor.Olive.tone"; -} - -QString ToneEffect::description() -{ - return tr("Generate a sine wave tone to mix into this clip's audio."); -} - -EffectType ToneEffect::type() -{ - return EFFECT_TYPE_EFFECT; -} - -olive::TrackType ToneEffect::subtype() -{ - return olive::kTypeAudio; -} - -OldEffectNodePtr ToneEffect::Create(Clip *c) -{ - return std::make_shared(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); - - for (int j=0;jGetBoolAt(timecode)) { - - // mix with source audio - samples[j][i] += tone_sample; - - } else { - - // replace source audio - samples[j][i] = tone_sample; - - } - - } - - sinX++; - } -} +/*** + + 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 "toneeffect.h" + +#include + +#define TONE_TYPE_SINE 0 + +#include "timeline/clip.h" +#include "timeline/sequence.h" + +ToneEffect::ToneEffect(Clip* c) : OldEffectNode(c), sinX(INT_MIN) { + type_val = new ComboInput(this, "type", tr("Type")); + type_val->AddItem(tr("Sine"), TONE_TYPE_SINE); + + freq_val = new DoubleInput(this, "frequency", tr("Frequency")); + freq_val->SetMinimum(20); + freq_val->SetMaximum(20000); + freq_val->SetDefault(1000); + + amount_val = new DoubleInput(this, "amount", tr("Amount")); + amount_val->SetMinimum(0); + amount_val->SetMaximum(100); + amount_val->SetDefault(25); + + mix_val = new BoolInput(this, "mix", tr("Mix")); + mix_val->SetValueAt(0, true); +} + +QString ToneEffect::name() +{ + return tr("Tone"); +} + +QString ToneEffect::id() +{ + return "org.olivevideoeditor.Olive.tone"; +} + +QString ToneEffect::description() +{ + return tr("Generate a sine wave tone to mix into this clip's audio."); +} + +EffectType ToneEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType ToneEffect::subtype() +{ + return olive::kTypeAudio; +} + +OldEffectNodePtr ToneEffect::Create(Clip *c) +{ + return std::make_shared(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); + + for (int j=0;jGetBoolAt(timecode)) { + + // mix with source audio + samples[j][i] += tone_sample; + + } else { + + // replace source audio + samples[j][i] = tone_sample; + + } + + } + + sinX++; + } +} diff --git a/effects/internal/toneeffect.h b/effects/internal/toneeffect.h index 60734b5c2..8309a8f84 100644 --- a/effects/internal/toneeffect.h +++ b/effects/internal/toneeffect.h @@ -1,54 +1,54 @@ -/*** - - 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 TONEEFFECT_H -#define TONEEFFECT_H - -#include "nodes/oldeffectnode.h" - -class ToneEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr 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: - ComboInput* type_val; - DoubleInput* freq_val; - DoubleInput* amount_val; - BoolInput* mix_val; - - int sinX; -}; - -#endif // TONEEFFECT_H +/*** + + 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 TONEEFFECT_H +#define TONEEFFECT_H + +#include "nodes/oldeffectnode.h" + +class ToneEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr 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: + ComboInput* type_val; + DoubleInput* freq_val; + DoubleInput* amount_val; + BoolInput* mix_val; + + int sinX; +}; + +#endif // TONEEFFECT_H diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index 8a2c68b75..c2a05c705 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -1,256 +1,256 @@ -/*** - - 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 "transformeffect.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ui/collapsiblewidget.h" -#include "timeline/clip.h" -#include "timeline/sequence.h" -#include "project/footage.h" -#include "global/math.h" -#include "ui/labelslider.h" -#include "ui/comboboxex.h" -#include "panels/project.h" -#include "global/debug.h" - -#include "panels/panels.h" -#include "panels/viewer.h" -#include "ui/viewerwidget.h" - -TransformEffect::TransformEffect(Clip* c) : OldEffectNode(c) { - SetFlags(OldEffectNode::CoordsFlag); - - position = new Vec2Input(this, "pos", tr("Position")); - - scale = new Vec2Input(this, "scale", tr("Scale")); - scale->SetMinimum(0); - scale->SetDefault(100); - - 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); - - rotation = new DoubleInput(this, "rotation", tr("Rotation")); - - anchor_point = new Vec2Input(this, "anchor", tr("Anchor Point")); - anchor_point->SetDefault(0); - - // opacity - opacity = new DoubleInput(this, "opacity", tr("Opacity")); - opacity->SetMinimum(0); - opacity->SetMaximum(100); - opacity->SetDefault(100); - - // TEMP - Create matrix output - NodeIO* matrix_output = new NodeIO(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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = static_cast(scale->Field(0)); - - anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); - anchor_gizmo->set_cursor(Qt::SizeAllCursor); - 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 = static_cast(rotation->Field(0)); - - rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); - 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))); - - 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; -} - -OldEffectNodePtr TransformEffect::Create(Clip *c) -{ - return std::make_shared(c); -} - -void TransformEffect::refresh() { - if (parent_clip != nullptr && parent_clip->track()->sequence() != nullptr) { - - position->SetDefault({parent_clip->track()->sequence()->width()*0.5, - parent_clip->track()->sequence()->height()*0.5}); - - 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; - top_center_gizmo->y_field_multi1 = -y_percent_multipler; - top_right_gizmo->x_field_multi1 = x_percent_multipler; - top_right_gizmo->y_field_multi1 = -y_percent_multipler; - bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; - bottom_left_gizmo->y_field_multi1 = y_percent_multipler; - bottom_center_gizmo->y_field_multi1 = y_percent_multipler; - bottom_right_gizmo->x_field_multi1 = x_percent_multipler; - bottom_right_gizmo->y_field_multi1 = y_percent_multipler; - left_center_gizmo->x_field_multi1 = -x_percent_multipler; - right_center_gizmo->x_field_multi1 = x_percent_multipler; - rotate_gizmo->x_field_multi1 = x_percent_multipler; - - } -} - -void TransformEffect::toggle_uniform_scale(bool 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; - top_left_gizmo->y_field1 = enabled ? nullptr : scale_y; - top_right_gizmo->y_field1 = enabled ? nullptr : scale_y; - bottom_left_gizmo->y_field1 = enabled ? nullptr : scale_y; - bottom_right_gizmo->y_field1 = enabled ? nullptr : scale_y; -} - -void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int) { - // position - coords.matrix.translate(position->GetVector2DAt(timecode) - - QVector2D(parent_clip->track()->sequence()->width()*0.5f, - parent_clip->track()->sequence()->height()*0.5f)); - - // anchor point - 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 - coords.matrix.rotate(QQuaternion::fromEulerAngles(0, 0, float(rotation->GetDoubleAt(timecode)))); - - // scale - 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] = 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] = 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] = 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; -} +/*** + + 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 "transformeffect.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui/collapsiblewidget.h" +#include "timeline/clip.h" +#include "timeline/sequence.h" +#include "project/footage.h" +#include "global/math.h" +#include "ui/labelslider.h" +#include "ui/comboboxex.h" +#include "panels/project.h" +#include "global/debug.h" + +#include "panels/panels.h" +#include "panels/viewer.h" +#include "ui/viewerwidget.h" + +TransformEffect::TransformEffect(Clip* c) : OldEffectNode(c) { + SetFlags(OldEffectNode::CoordsFlag); + + position = new Vec2Input(this, "pos", tr("Position")); + + scale = new Vec2Input(this, "scale", tr("Scale")); + scale->SetMinimum(0); + scale->SetDefault(100); + + 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); + + rotation = new DoubleInput(this, "rotation", tr("Rotation")); + + anchor_point = new Vec2Input(this, "anchor", tr("Anchor Point")); + anchor_point->SetDefault(0); + + // opacity + opacity = new DoubleInput(this, "opacity", tr("Opacity")); + opacity->SetMinimum(0); + opacity->SetMaximum(100); + opacity->SetDefault(100); + + // TEMP - Create matrix output + NodeIO* matrix_output = new NodeIO(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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = static_cast(scale->Field(0)); + + anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); + anchor_gizmo->set_cursor(Qt::SizeAllCursor); + 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 = static_cast(rotation->Field(0)); + + rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); + 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))); + + 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; +} + +OldEffectNodePtr TransformEffect::Create(Clip *c) +{ + return std::make_shared(c); +} + +void TransformEffect::refresh() { + if (parent_clip != nullptr && parent_clip->track()->sequence() != nullptr) { + + position->SetDefault({parent_clip->track()->sequence()->width()*0.5, + parent_clip->track()->sequence()->height()*0.5}); + + 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; + top_center_gizmo->y_field_multi1 = -y_percent_multipler; + top_right_gizmo->x_field_multi1 = x_percent_multipler; + top_right_gizmo->y_field_multi1 = -y_percent_multipler; + bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; + bottom_left_gizmo->y_field_multi1 = y_percent_multipler; + bottom_center_gizmo->y_field_multi1 = y_percent_multipler; + bottom_right_gizmo->x_field_multi1 = x_percent_multipler; + bottom_right_gizmo->y_field_multi1 = y_percent_multipler; + left_center_gizmo->x_field_multi1 = -x_percent_multipler; + right_center_gizmo->x_field_multi1 = x_percent_multipler; + rotate_gizmo->x_field_multi1 = x_percent_multipler; + + } +} + +void TransformEffect::toggle_uniform_scale(bool 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; + top_left_gizmo->y_field1 = enabled ? nullptr : scale_y; + top_right_gizmo->y_field1 = enabled ? nullptr : scale_y; + bottom_left_gizmo->y_field1 = enabled ? nullptr : scale_y; + bottom_right_gizmo->y_field1 = enabled ? nullptr : scale_y; +} + +void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int) { + // position + coords.matrix.translate(position->GetVector2DAt(timecode) + - QVector2D(parent_clip->track()->sequence()->width()*0.5f, + parent_clip->track()->sequence()->height()*0.5f)); + + // anchor point + 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 + coords.matrix.rotate(QQuaternion::fromEulerAngles(0, 0, float(rotation->GetDoubleAt(timecode)))); + + // scale + 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] = 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] = 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] = 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 8b61d2267..132c6d164 100644 --- a/effects/internal/transformeffect.h +++ b/effects/internal/transformeffect.h @@ -1,68 +1,68 @@ -/*** - - 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 TRANSFORMEFFECT_H -#define TRANSFORMEFFECT_H - -#include "nodes/oldeffectnode.h" - -class TransformEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr 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; - -public slots: - void toggle_uniform_scale(bool enabled); - -private: - Vec2Input* position; - Vec2Input* scale; - BoolInput* uniform_scale_field; - DoubleInput* rotation; - Vec2Input* anchor_point; - DoubleInput* opacity; - - EffectGizmo* top_left_gizmo; - EffectGizmo* top_center_gizmo; - EffectGizmo* top_right_gizmo; - EffectGizmo* bottom_left_gizmo; - EffectGizmo* bottom_center_gizmo; - EffectGizmo* bottom_right_gizmo; - EffectGizmo* left_center_gizmo; - EffectGizmo* right_center_gizmo; - EffectGizmo* anchor_gizmo; - EffectGizmo* rotate_gizmo; - EffectGizmo* rect_gizmo; -}; - -#endif // TRANSFORMEFFECT_H +/*** + + 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 TRANSFORMEFFECT_H +#define TRANSFORMEFFECT_H + +#include "nodes/oldeffectnode.h" + +class TransformEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr 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; + +public slots: + void toggle_uniform_scale(bool enabled); + +private: + Vec2Input* position; + Vec2Input* scale; + BoolInput* uniform_scale_field; + DoubleInput* rotation; + Vec2Input* anchor_point; + DoubleInput* opacity; + + EffectGizmo* top_left_gizmo; + EffectGizmo* top_center_gizmo; + EffectGizmo* top_right_gizmo; + EffectGizmo* bottom_left_gizmo; + EffectGizmo* bottom_center_gizmo; + EffectGizmo* bottom_right_gizmo; + EffectGizmo* left_center_gizmo; + EffectGizmo* right_center_gizmo; + EffectGizmo* anchor_gizmo; + EffectGizmo* rotate_gizmo; + EffectGizmo* rect_gizmo; +}; + +#endif // TRANSFORMEFFECT_H diff --git a/effects/internal/voideffect.cpp b/effects/internal/voideffect.cpp index 477324e09..fd81cd400 100644 --- a/effects/internal/voideffect.cpp +++ b/effects/internal/voideffect.cpp @@ -1,123 +1,123 @@ -/*** - - 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 "voideffect.h" - -#include -#include -#include - -#include "ui/collapsiblewidget.h" -#include "global/debug.h" - -VoidEffect::VoidEffect(Clip* c, const QString& n, const QString& id) : - OldEffectNode(c), - display_name_(n), - id_(id) -{ - if (display_name_.isEmpty()) { - display_name_ = tr("(unknown)"); - } - - new LabelWidget(this, tr("Missing Effect"), display_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; -} - -OldEffectNodePtr VoidEffect::Create(Clip *) -{ - return nullptr; -} - -OldEffectNodePtr VoidEffect::copy(Clip* c) { - OldEffectNodePtr copy = std::make_shared(c, display_name_, id_); - copy->SetEnabled(IsEnabled()); - copy_field_keyframes(copy); - return copy; -} - -void VoidEffect::load(QXmlStreamReader &stream) { - QString tag = stream.name().toString(); - - QXmlStreamWriter writer(&bytes_); - - // copy XML from reader to writer - while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { - stream.readNext(); - - if (stream.isStartElement()) { - writer.writeStartElement(stream.name().toString()); - } - if (stream.isEndElement()) { - writer.writeEndElement(); - } - if (stream.isCharacters()) { - writer.writeCharacters(stream.text().toString()); - } - for (int i=0;i tag, ignored when loading - stream.writeStartElement("void"); - stream.writeEndElement(); - - if (!bytes_.isEmpty()) { - // write stored data - QIODevice* device = stream.device(); - device->write(bytes_); - } - } -} +/*** + + 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 "voideffect.h" + +#include +#include +#include + +#include "ui/collapsiblewidget.h" +#include "global/debug.h" + +VoidEffect::VoidEffect(Clip* c, const QString& n, const QString& id) : + OldEffectNode(c), + display_name_(n), + id_(id) +{ + if (display_name_.isEmpty()) { + display_name_ = tr("(unknown)"); + } + + new LabelWidget(this, tr("Missing Effect"), display_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; +} + +OldEffectNodePtr VoidEffect::Create(Clip *) +{ + return nullptr; +} + +OldEffectNodePtr VoidEffect::copy(Clip* c) { + OldEffectNodePtr copy = std::make_shared(c, display_name_, id_); + copy->SetEnabled(IsEnabled()); + copy_field_keyframes(copy); + return copy; +} + +void VoidEffect::load(QXmlStreamReader &stream) { + QString tag = stream.name().toString(); + + QXmlStreamWriter writer(&bytes_); + + // copy XML from reader to writer + while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { + stream.readNext(); + + if (stream.isStartElement()) { + writer.writeStartElement(stream.name().toString()); + } + if (stream.isEndElement()) { + writer.writeEndElement(); + } + if (stream.isCharacters()) { + writer.writeCharacters(stream.text().toString()); + } + for (int i=0;i tag, ignored when loading + stream.writeStartElement("void"); + stream.writeEndElement(); + + if (!bytes_.isEmpty()) { + // write stored data + QIODevice* device = stream.device(); + device->write(bytes_); + } + } +} diff --git a/effects/internal/voideffect.h b/effects/internal/voideffect.h index f1c56a463..323e04c05 100644 --- a/effects/internal/voideffect.h +++ b/effects/internal/voideffect.h @@ -1,54 +1,54 @@ -/*** - - 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 VOIDEFFECT_H -#define VOIDEFFECT_H - -/* VoidEffect is a placeholder used when Olive is unable to find an effect - * requested by a loaded project. It displays a missing effect so the user knows - * an effect is missing, and stores the XML project data verbatim so that it - * isn't lost if the user saves over the project. - */ - -#include "nodes/oldeffectnode.h" - -class VoidEffect : public OldEffectNode { - Q_OBJECT -public: - 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 OldEffectNodePtr Create(Clip *c) override; - virtual OldEffectNodePtr copy(Clip* c) override; - - virtual void load(QXmlStreamReader &stream) override; - virtual void save(QXmlStreamWriter &stream) override; -private: - QByteArray bytes_; - QString display_name_; - QString id_; -}; - -#endif // VOIDEFFECT_H +/*** + + 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 VOIDEFFECT_H +#define VOIDEFFECT_H + +/* VoidEffect is a placeholder used when Olive is unable to find an effect + * requested by a loaded project. It displays a missing effect so the user knows + * an effect is missing, and stores the XML project data verbatim so that it + * isn't lost if the user saves over the project. + */ + +#include "nodes/oldeffectnode.h" + +class VoidEffect : public OldEffectNode { + Q_OBJECT +public: + 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 OldEffectNodePtr Create(Clip *c) override; + virtual OldEffectNodePtr copy(Clip* c) override; + + virtual void load(QXmlStreamReader &stream) override; + virtual void save(QXmlStreamWriter &stream) override; +private: + QByteArray bytes_; + QString display_name_; + QString id_; +}; + +#endif // VOIDEFFECT_H diff --git a/effects/internal/volumeeffect.cpp b/effects/internal/volumeeffect.cpp index 7e5ced506..21dab6655 100644 --- a/effects/internal/volumeeffect.cpp +++ b/effects/internal/volumeeffect.cpp @@ -1,90 +1,90 @@ -/*** - - 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 "volumeeffect.h" - -#include -#include -#include -#include - -#include "ui/labelslider.h" -#include "ui/collapsiblewidget.h" - -VolumeEffect::VolumeEffect(Clip* c) : OldEffectNode(c) { - volume_val = new DoubleInput(this, "volume", tr("Volume")); - - // set defaults - volume_val->SetDefault(1); - volume_val->SetDisplayType(LabelSlider::Decibel); -} - -QString VolumeEffect::name() -{ - return tr("Volume"); -} - -QString VolumeEffect::id() -{ - return "org.olivevideoeditor.Olive.volume"; -} - -QString VolumeEffect::description() -{ - return tr("Adjust the volume of this clip's audio"); -} - -EffectType VolumeEffect::type() -{ - return EFFECT_TYPE_EFFECT; -} - -olive::TrackType VolumeEffect::subtype() -{ - return olive::kTypeAudio; -} - -OldEffectNodePtr VolumeEffect::Create(Clip *c) -{ - return std::make_shared(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)); - - for (int j=0;j. + +***/ + +#include "volumeeffect.h" + +#include +#include +#include +#include + +#include "ui/labelslider.h" +#include "ui/collapsiblewidget.h" + +VolumeEffect::VolumeEffect(Clip* c) : OldEffectNode(c) { + volume_val = new DoubleInput(this, "volume", tr("Volume")); + + // set defaults + volume_val->SetDefault(1); + volume_val->SetDisplayType(LabelSlider::Decibel); +} + +QString VolumeEffect::name() +{ + return tr("Volume"); +} + +QString VolumeEffect::id() +{ + return "org.olivevideoeditor.Olive.volume"; +} + +QString VolumeEffect::description() +{ + return tr("Adjust the volume of this clip's audio"); +} + +EffectType VolumeEffect::type() +{ + return EFFECT_TYPE_EFFECT; +} + +olive::TrackType VolumeEffect::subtype() +{ + return olive::kTypeAudio; +} + +OldEffectNodePtr VolumeEffect::Create(Clip *c) +{ + return std::make_shared(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)); + + for (int j=0;j. - -***/ - -#ifndef VOLUMEEFFECT_H -#define VOLUMEEFFECT_H - -#include "nodes/oldeffectnode.h" - -class VolumeEffect : public OldEffectNode { - Q_OBJECT -public: - VolumeEffect(Clip* c); - - virtual QString name() override; - virtual QString id() override; - virtual QString description() override; - virtual EffectType type() override; - virtual olive::TrackType subtype() override; - virtual OldEffectNodePtr 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 +/*** + + 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 VOLUMEEFFECT_H +#define VOLUMEEFFECT_H + +#include "nodes/oldeffectnode.h" + +class VolumeEffect : public OldEffectNode { + Q_OBJECT +public: + VolumeEffect(Clip* c); + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual OldEffectNodePtr 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 b4ef5a503..46cb50b10 100644 --- a/effects/internal/vsthost.cpp +++ b/effects/internal/vsthost.cpp @@ -1,432 +1,432 @@ -/*** - - 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 "vsthost.h" - -// adapted from http://teragonaudio.com/article/How-to-make-your-own-VST-host.html - -#include -#include -#include -#include -#include -#include - -#include "rendering/audio.h" -#include "ui/mainwindow.h" -#include "global/global.h" -#include "global/debug.h" - -// Load libraries for retrieving the native window handle. Used for VST plugins that have a separate window -// dedicated to controls. -#if defined(Q_OS_WIN) -#include -#elif defined(Q_OS_MACOS) -#include -class NSWindow; -#elif defined(Q_OS_LINUX) -#include -#endif - -#define BLOCK_SIZE 512 - -struct VSTRect { - int16_t top; - int16_t left; - int16_t bottom; - int16_t right; -}; - -#define effGetChunk 23 -#define effSetChunk 24 - -// C callbacks -extern "C" { -// Main host callback -intptr_t hostCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { - Q_UNUSED(value) - - switch(opcode) { - case audioMasterAutomate: - effect->setParameter(effect, index, opt); - break; - case audioMasterVersion: - return 2400; - case audioMasterIdle: - effect->dispatcher(effect, effEditIdle, 0, 0, nullptr, 0); - break; - case audioMasterWantMidi: - // no midi support, return 0 - break; - case audioMasterGetSampleRate: - return current_audio_freq(); - case audioMasterGetBlockSize: - return BLOCK_SIZE; - case audioMasterGetCurrentProcessLevel: - // process level happens to be 0 - break; - case audioMasterGetProductString: - strcpy(static_cast(ptr), "OLIVETEAM"); - break; - case audioMasterBeginEdit: - // we don't really care about this - // but we are aware of it - break; - case audioMasterEndEdit: // change made - olive::Global->set_modified(true); - break; - default: - qInfo() << "Plugin requested unhandled opcode" << opcode; - } - return 0; -} -} - -// Plugin's entry point -typedef AEffect *(*vstPluginFuncPtr)(audioMasterCallback host); -// Plugin's getParameter() method -typedef float (*getParameterFuncPtr)(AEffect *effect, int32_t index); -// Plugin's setParameter() method -typedef void (*setParameterFuncPtr)(AEffect *effect, int32_t index, float value); -// Plugin's processEvents() method -typedef int32_t (*processEventsFuncPtr)(VstEvents *events); -// Plugin's process() method -typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, int32_t sampleFrames); - -void VSTHost::loadPlugin() { - - QString dll_fn = file_field->GetFileAt(0); - - if (dll_fn.isEmpty()) { - return; - } - - // Try to load the plugin - modulePtr.setFileName(dll_fn); - if (!modulePtr.load()) { - - // Show an error if the plugin fails to load - - qCritical() << "Failed to load VST plugin" << dll_fn << "-" << modulePtr.errorString(); - QMessageBox::critical(olive::MainWindow, - tr("Error loading VST plugin"), - tr("Failed to load VST plugin \"%1\": %2").arg(dll_fn, modulePtr.errorString())); - return; - - } - - // Try to find the VST entry point (first using VSTPluginMain() ) - vstPluginFuncPtr mainEntryPoint = reinterpret_cast(modulePtr.resolve("VSTPluginMain")); - - if (mainEntryPoint == nullptr) { - // If there's no VSTPluginMain(), the plugin may use main() instead - mainEntryPoint = reinterpret_cast(modulePtr.resolve("main")); - } - - if (mainEntryPoint == nullptr) { - QMessageBox::critical(olive::MainWindow, - tr("Error loading VST plugin"), - tr("Failed to locate entry point for dynamic library.")); - modulePtr.unload(); - return; - } - - // Instantiate the plugin - plugin = mainEntryPoint(hostCallback); - -} - -void VSTHost::freePlugin() { - if (plugin != nullptr) { - stopPlugin(); - data_cache.clear(); - modulePtr.unload(); - plugin = nullptr; - } -} - -bool VSTHost::configurePluginCallbacks() { - // Check plugin's magic number - // If incorrect, then the file either was not loaded properly, is not a - // real VST plugin, or is otherwise corrupt. - if(plugin->magic != kEffectMagic) { - qCritical() << "Plugin's magic number is bad"; - QMessageBox::critical(olive::MainWindow, tr("VST Error"), tr("Plugin's magic number is invalid")); - return false; - } - - // Create dispatcher handle - dispatcher = reinterpret_cast(plugin->dispatcher); - - // Set up plugin callback functions - plugin->getParameter = reinterpret_cast(plugin->getParameter); - plugin->processReplacing = reinterpret_cast(plugin->processReplacing); - plugin->setParameter = reinterpret_cast(plugin->setParameter); - - return true; -} - -void VSTHost::startPlugin() { - dispatcher(plugin, effOpen, 0, 0, nullptr, 0.0f); - - // Set some default properties - dispatcher(plugin, effSetSampleRate, 0, 0, nullptr, current_audio_freq()); - dispatcher(plugin, effSetBlockSize, 0, BLOCK_SIZE, nullptr, 0.0f); - - resumePlugin(); -} - -void VSTHost::stopPlugin() { - suspendPlugin(); - - dispatcher(plugin, effClose, 0, 0, nullptr, 0); -} - -void VSTHost::resumePlugin() { - dispatcher(plugin, effMainsChanged, 0, 1, nullptr, 0.0f); -} - -void VSTHost::suspendPlugin() { - dispatcher(plugin, effMainsChanged, 0, 0, nullptr, 0.0f); -} - -bool VSTHost::canPluginDo(char *canDoString) { - return (dispatcher(plugin, effCanDo, 0, 0, static_cast(canDoString), 0.0f) > 0); -} - -void VSTHost::CreateDialogIfNull() -{ - if (dialog == nullptr) { - dialog = new QDialog(olive::MainWindow); - dialog->setWindowTitle(tr("VST Plugin")); - dialog->setAttribute(Qt::WA_NativeWindow, true); - dialog->setWindowFlags(dialog->windowFlags() | Qt::MSWindowsFixedSizeDialogHint); - connect(dialog, SIGNAL(finished(int)), this, SLOT(uncheck_show_button())); - } -} - -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) : - OldEffectNode(c), - plugin(nullptr), - dialog(nullptr), - input_cache(BLOCK_SIZE), - output_cache(BLOCK_SIZE) -{ - plugin = nullptr; - - file_field = new FileInput(this, "filename", tr("Plugin"), true, false); - connect(file_field, SIGNAL(Changed()), this, SLOT(change_plugin()), Qt::QueuedConnection); - - 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() { - freePlugin(); -} - -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; -} - -OldEffectNodePtr 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) { - - // Make copy of audio - input_cache.Create(channel_count); - output_cache.Create(channel_count); - - for (int i=0;iprocessReplacing(plugin, input_cache.data(), output_cache.data(), sample_size); - - // Copy output cache back to samples - for (int j=0;j 0) { - stream.writeTextElement("plugindata", data_cache.toBase64()); - } -} - -void VSTHost::show_interface(bool show) { - CreateDialogIfNull(); - dialog->setVisible(show); - - if (show) { -#if defined(Q_OS_WIN) - dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0); -#elif defined(Q_OS_MACOS) - dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0); -#elif defined(Q_OS_LINUX) || defined(__HAIKU__) - dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0); -#endif - } else { - dispatcher(plugin, effEditClose, 0, 0, nullptr, 0); - } -} - -void VSTHost::uncheck_show_button() { - show_interface_btn->SetChecked(false); -} - -void VSTHost::change_plugin() { - freePlugin(); - loadPlugin(); - if (plugin != nullptr) { - if (configurePluginCallbacks()) { - startPlugin(); - - VSTRect* eRect = nullptr; - plugin->dispatcher(plugin, effEditGetRect, 0, 0, &eRect, 0); - - if (!data_cache.isEmpty()) { - send_data_cache_to_plugin(); - } - - CreateDialogIfNull(); - dialog->setFixedSize(eRect->right - eRect->left, eRect->bottom - eRect->top); - - } else { - - modulePtr.unload(); - plugin = nullptr; - - } - } - show_interface_btn->SetEnabled(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. + +***/ + +#include "vsthost.h" + +// adapted from http://teragonaudio.com/article/How-to-make-your-own-VST-host.html + +#include +#include +#include +#include +#include +#include + +#include "rendering/audio.h" +#include "ui/mainwindow.h" +#include "global/global.h" +#include "global/debug.h" + +// Load libraries for retrieving the native window handle. Used for VST plugins that have a separate window +// dedicated to controls. +#if defined(Q_OS_WIN) +#include +#elif defined(Q_OS_MACOS) +#include +class NSWindow; +#elif defined(Q_OS_LINUX) +#include +#endif + +#define BLOCK_SIZE 512 + +struct VSTRect { + int16_t top; + int16_t left; + int16_t bottom; + int16_t right; +}; + +#define effGetChunk 23 +#define effSetChunk 24 + +// C callbacks +extern "C" { +// Main host callback +intptr_t hostCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { + Q_UNUSED(value) + + switch(opcode) { + case audioMasterAutomate: + effect->setParameter(effect, index, opt); + break; + case audioMasterVersion: + return 2400; + case audioMasterIdle: + effect->dispatcher(effect, effEditIdle, 0, 0, nullptr, 0); + break; + case audioMasterWantMidi: + // no midi support, return 0 + break; + case audioMasterGetSampleRate: + return current_audio_freq(); + case audioMasterGetBlockSize: + return BLOCK_SIZE; + case audioMasterGetCurrentProcessLevel: + // process level happens to be 0 + break; + case audioMasterGetProductString: + strcpy(static_cast(ptr), "OLIVETEAM"); + break; + case audioMasterBeginEdit: + // we don't really care about this + // but we are aware of it + break; + case audioMasterEndEdit: // change made + olive::Global->set_modified(true); + break; + default: + qInfo() << "Plugin requested unhandled opcode" << opcode; + } + return 0; +} +} + +// Plugin's entry point +typedef AEffect *(*vstPluginFuncPtr)(audioMasterCallback host); +// Plugin's getParameter() method +typedef float (*getParameterFuncPtr)(AEffect *effect, int32_t index); +// Plugin's setParameter() method +typedef void (*setParameterFuncPtr)(AEffect *effect, int32_t index, float value); +// Plugin's processEvents() method +typedef int32_t (*processEventsFuncPtr)(VstEvents *events); +// Plugin's process() method +typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, int32_t sampleFrames); + +void VSTHost::loadPlugin() { + + QString dll_fn = file_field->GetFileAt(0); + + if (dll_fn.isEmpty()) { + return; + } + + // Try to load the plugin + modulePtr.setFileName(dll_fn); + if (!modulePtr.load()) { + + // Show an error if the plugin fails to load + + qCritical() << "Failed to load VST plugin" << dll_fn << "-" << modulePtr.errorString(); + QMessageBox::critical(olive::MainWindow, + tr("Error loading VST plugin"), + tr("Failed to load VST plugin \"%1\": %2").arg(dll_fn, modulePtr.errorString())); + return; + + } + + // Try to find the VST entry point (first using VSTPluginMain() ) + vstPluginFuncPtr mainEntryPoint = reinterpret_cast(modulePtr.resolve("VSTPluginMain")); + + if (mainEntryPoint == nullptr) { + // If there's no VSTPluginMain(), the plugin may use main() instead + mainEntryPoint = reinterpret_cast(modulePtr.resolve("main")); + } + + if (mainEntryPoint == nullptr) { + QMessageBox::critical(olive::MainWindow, + tr("Error loading VST plugin"), + tr("Failed to locate entry point for dynamic library.")); + modulePtr.unload(); + return; + } + + // Instantiate the plugin + plugin = mainEntryPoint(hostCallback); + +} + +void VSTHost::freePlugin() { + if (plugin != nullptr) { + stopPlugin(); + data_cache.clear(); + modulePtr.unload(); + plugin = nullptr; + } +} + +bool VSTHost::configurePluginCallbacks() { + // Check plugin's magic number + // If incorrect, then the file either was not loaded properly, is not a + // real VST plugin, or is otherwise corrupt. + if(plugin->magic != kEffectMagic) { + qCritical() << "Plugin's magic number is bad"; + QMessageBox::critical(olive::MainWindow, tr("VST Error"), tr("Plugin's magic number is invalid")); + return false; + } + + // Create dispatcher handle + dispatcher = reinterpret_cast(plugin->dispatcher); + + // Set up plugin callback functions + plugin->getParameter = reinterpret_cast(plugin->getParameter); + plugin->processReplacing = reinterpret_cast(plugin->processReplacing); + plugin->setParameter = reinterpret_cast(plugin->setParameter); + + return true; +} + +void VSTHost::startPlugin() { + dispatcher(plugin, effOpen, 0, 0, nullptr, 0.0f); + + // Set some default properties + dispatcher(plugin, effSetSampleRate, 0, 0, nullptr, current_audio_freq()); + dispatcher(plugin, effSetBlockSize, 0, BLOCK_SIZE, nullptr, 0.0f); + + resumePlugin(); +} + +void VSTHost::stopPlugin() { + suspendPlugin(); + + dispatcher(plugin, effClose, 0, 0, nullptr, 0); +} + +void VSTHost::resumePlugin() { + dispatcher(plugin, effMainsChanged, 0, 1, nullptr, 0.0f); +} + +void VSTHost::suspendPlugin() { + dispatcher(plugin, effMainsChanged, 0, 0, nullptr, 0.0f); +} + +bool VSTHost::canPluginDo(char *canDoString) { + return (dispatcher(plugin, effCanDo, 0, 0, static_cast(canDoString), 0.0f) > 0); +} + +void VSTHost::CreateDialogIfNull() +{ + if (dialog == nullptr) { + dialog = new QDialog(olive::MainWindow); + dialog->setWindowTitle(tr("VST Plugin")); + dialog->setAttribute(Qt::WA_NativeWindow, true); + dialog->setWindowFlags(dialog->windowFlags() | Qt::MSWindowsFixedSizeDialogHint); + connect(dialog, SIGNAL(finished(int)), this, SLOT(uncheck_show_button())); + } +} + +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) : + OldEffectNode(c), + plugin(nullptr), + dialog(nullptr), + input_cache(BLOCK_SIZE), + output_cache(BLOCK_SIZE) +{ + plugin = nullptr; + + file_field = new FileInput(this, "filename", tr("Plugin"), true, false); + connect(file_field, SIGNAL(Changed()), this, SLOT(change_plugin()), Qt::QueuedConnection); + + 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() { + freePlugin(); +} + +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; +} + +OldEffectNodePtr 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) { + + // Make copy of audio + input_cache.Create(channel_count); + output_cache.Create(channel_count); + + for (int i=0;iprocessReplacing(plugin, input_cache.data(), output_cache.data(), sample_size); + + // Copy output cache back to samples + for (int j=0;j 0) { + stream.writeTextElement("plugindata", data_cache.toBase64()); + } +} + +void VSTHost::show_interface(bool show) { + CreateDialogIfNull(); + dialog->setVisible(show); + + if (show) { +#if defined(Q_OS_WIN) + dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0); +#elif defined(Q_OS_MACOS) + dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0); +#elif defined(Q_OS_LINUX) || defined(__HAIKU__) + dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0); +#endif + } else { + dispatcher(plugin, effEditClose, 0, 0, nullptr, 0); + } +} + +void VSTHost::uncheck_show_button() { + show_interface_btn->SetChecked(false); +} + +void VSTHost::change_plugin() { + freePlugin(); + loadPlugin(); + if (plugin != nullptr) { + if (configurePluginCallbacks()) { + startPlugin(); + + VSTRect* eRect = nullptr; + plugin->dispatcher(plugin, effEditGetRect, 0, 0, &eRect, 0); + + if (!data_cache.isEmpty()) { + send_data_cache_to_plugin(); + } + + CreateDialogIfNull(); + dialog->setFixedSize(eRect->right - eRect->left, eRect->bottom - eRect->top); + + } else { + + modulePtr.unload(); + plugin = nullptr; + + } + } + show_interface_btn->SetEnabled(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. - -***/ - -#ifndef VSTHOSTWIN_H -#define VSTHOSTWIN_H - -#include -#include - -#include "nodes/oldeffectnode.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 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 OldEffectNode { - Q_OBJECT -public: - VSTHost(Clip* c); - virtual ~VSTHost() override; - - virtual QString name() override; - virtual QString id() override; - virtual QString description() override; - virtual EffectType type() override; - virtual olive::TrackType subtype() override; - virtual OldEffectNodePtr 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: - FileInput* file_field; - ButtonWidget* show_interface_btn; - - void loadPlugin(); - void freePlugin(); - dispatcherFuncPtr dispatcher; - AEffect* plugin; - bool configurePluginCallbacks(); - void startPlugin(); - void stopPlugin(); - void resumePlugin(); - void suspendPlugin(); - bool canPluginDo(char *canDoString); - void CreateDialogIfNull(); - QDialog* dialog; - QByteArray data_cache; - SampleCache input_cache; - SampleCache output_cache; - - void send_data_cache_to_plugin(); - - QLibrary modulePtr; -}; - -#endif // VSTHOSTWIN_H +/*** + + 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 VSTHOSTWIN_H +#define VSTHOSTWIN_H + +#include +#include + +#include "nodes/oldeffectnode.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 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 OldEffectNode { + Q_OBJECT +public: + VSTHost(Clip* c); + virtual ~VSTHost() override; + + virtual QString name() override; + virtual QString id() override; + virtual QString description() override; + virtual EffectType type() override; + virtual olive::TrackType subtype() override; + virtual OldEffectNodePtr 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: + FileInput* file_field; + ButtonWidget* show_interface_btn; + + void loadPlugin(); + void freePlugin(); + dispatcherFuncPtr dispatcher; + AEffect* plugin; + bool configurePluginCallbacks(); + void startPlugin(); + void stopPlugin(); + void resumePlugin(); + void suspendPlugin(); + bool canPluginDo(char *canDoString); + void CreateDialogIfNull(); + QDialog* dialog; + QByteArray data_cache; + SampleCache input_cache; + SampleCache output_cache; + + void send_data_cache_to_plugin(); + + QLibrary modulePtr; +}; + +#endif // VSTHOSTWIN_H diff --git a/effects/keyframe.cpp b/effects/keyframe.cpp index 4939fcd87..4ec5c0def 100644 --- a/effects/keyframe.cpp +++ b/effects/keyframe.cpp @@ -1,66 +1,66 @@ -/*** - - 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 "keyframe.h" - -#include - -#include "effectfields.h" -#include "undo/undo.h" -#include "undo/undostack.h" -#include "panels/panels.h" - -EffectKeyframe::EffectKeyframe() -{ - pre_handle = QPointF(-40, 0); - post_handle = QPointF(40, 0); -} - -void delete_keyframes(QVector& selected_key_fields, QVector &selected_keys) { - QVector fields; - QVector key_indices; - - for (int i=0;i 0) { - ComboAction* ca = new ComboAction(); - for (int i=0;iappend(new KeyframeDelete(fields.at(i), key_indices.at(i))); - } - olive::undo_stack.push(ca); - selected_keys.clear(); - selected_key_fields.clear(); - update_ui(false); - } -} +/*** + + 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 "keyframe.h" + +#include + +#include "effectfields.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "panels/panels.h" + +EffectKeyframe::EffectKeyframe() +{ + pre_handle = QPointF(-40, 0); + post_handle = QPointF(40, 0); +} + +void delete_keyframes(QVector& selected_key_fields, QVector &selected_keys) { + QVector fields; + QVector key_indices; + + for (int i=0;i 0) { + ComboAction* ca = new ComboAction(); + for (int i=0;iappend(new KeyframeDelete(fields.at(i), key_indices.at(i))); + } + 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 5db0a026c..924d554a2 100644 --- a/effects/keyframe.h +++ b/effects/keyframe.h @@ -1,44 +1,44 @@ -/*** - - 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 KEYFRAME_H -#define KEYFRAME_H - -#include -#include - -class EffectField; - -class EffectKeyframe { -public: - EffectKeyframe(); - - int type; - double time; - QVariant data; - - // only for bezier type - QPointF pre_handle; - QPointF post_handle; -}; - -void delete_keyframes(QVector &selected_key_fields, QVector &selected_keys); - -#endif // KEYFRAME_H +/*** + + 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 KEYFRAME_H +#define KEYFRAME_H + +#include +#include + +class EffectField; + +class EffectKeyframe { +public: + EffectKeyframe(); + + int type; + double time; + QVariant data; + + // only for bezier type + QPointF pre_handle; + QPointF post_handle; +}; + +void delete_keyframes(QVector &selected_key_fields, QVector &selected_keys); + +#endif // KEYFRAME_H diff --git a/effects/shaders/boxblur.frag b/effects/shaders/boxblur.frag index a9479c135..d2b29fc8f 100644 --- a/effects/shaders/boxblur.frag +++ b/effects/shaders/boxblur.frag @@ -1,27 +1,27 @@ -uniform float radius; -uniform bool horiz_blur; -uniform bool vert_blur; -uniform int iteration; -uniform vec2 resolution; - -vec4 process(vec4 col) { - float rad = ceil(radius); - - float divider = 1.0 / rad; - vec4 color = vec4(0.0); - bool radius_is_zero = (rad == 0.0); - - if (iteration == 0 && horiz_blur && !radius_is_zero) { - for (float x=-rad+0.5;x<=rad;x+=2.0) { - color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y)/resolution)*(divider); - } - return color; - } else if (iteration == 1 && vert_blur && !radius_is_zero) { - for (float x=-rad+0.5;x<=rad;x+=2.0) { - color += texture2D(texture, vec2(gl_FragCoord.x, gl_FragCoord.y+x)/resolution)*(divider); - } - return color; - } else { - return col; - } -} +uniform float radius; +uniform bool horiz_blur; +uniform bool vert_blur; +uniform int iteration; +uniform vec2 resolution; + +vec4 process(vec4 col) { + float rad = ceil(radius); + + float divider = 1.0 / rad; + vec4 color = vec4(0.0); + bool radius_is_zero = (rad == 0.0); + + if (iteration == 0 && horiz_blur && !radius_is_zero) { + for (float x=-rad+0.5;x<=rad;x+=2.0) { + color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y)/resolution)*(divider); + } + return color; + } else if (iteration == 1 && vert_blur && !radius_is_zero) { + for (float x=-rad+0.5;x<=rad;x+=2.0) { + color += texture2D(texture, vec2(gl_FragCoord.x, gl_FragCoord.y+x)/resolution)*(divider); + } + return color; + } else { + return col; + } +} diff --git a/effects/shaders/boxblur.xml b/effects/shaders/boxblur.xml index 53140ed99..d1de2b8fc 100644 --- a/effects/shaders/boxblur.xml +++ b/effects/shaders/boxblur.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/effects/shaders/bulge.frag b/effects/shaders/bulge.frag index 68b55b2ca..18b3271ac 100644 --- a/effects/shaders/bulge.frag +++ b/effects/shaders/bulge.frag @@ -1,28 +1,28 @@ -const float PI = 3.1415926535; - -uniform vec2 resolution; -uniform float amount; -uniform float xoff; -uniform float yoff; - -vec2 distort(vec2 p, vec2 offset) { - float theta = atan(p.y, p.x); - float radius = length(p); - radius = pow(radius, (1.0+amount*0.01)); - p.x = radius * cos(theta) + offset.x; - p.y = radius * sin(theta) + offset.y; - return 0.5 * (p + 1.0); -} - -vec4 process(vec4 col) { - vec2 offset = vec2(xoff/resolution.x, yoff/resolution.y); - 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) { - return texture2D(texture, uv); - } else { - return vec4(0.0); - } +const float PI = 3.1415926535; + +uniform vec2 resolution; +uniform float amount; +uniform float xoff; +uniform float yoff; + +vec2 distort(vec2 p, vec2 offset) { + float theta = atan(p.y, p.x); + float radius = length(p); + radius = pow(radius, (1.0+amount*0.01)); + p.x = radius * cos(theta) + offset.x; + p.y = radius * sin(theta) + offset.y; + return 0.5 * (p + 1.0); +} + +vec4 process(vec4 col) { + vec2 offset = vec2(xoff/resolution.x, yoff/resolution.y); + 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) { + return texture2D(texture, uv); + } else { + return vec4(0.0); + } } \ No newline at end of file diff --git a/effects/shaders/bulge.xml b/effects/shaders/bulge.xml index bad26c9e8..6fe41571f 100644 --- a/effects/shaders/bulge.xml +++ b/effects/shaders/bulge.xml @@ -1,11 +1,11 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/chromaticaberration.xml b/effects/shaders/chromaticaberration.xml index 0f48f123c..5f90a5737 100644 --- a/effects/shaders/chromaticaberration.xml +++ b/effects/shaders/chromaticaberration.xml @@ -1,13 +1,13 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/crop.frag b/effects/shaders/crop.frag index 509596b34..65b95df39 100644 --- a/effects/shaders/crop.frag +++ b/effects/shaders/crop.frag @@ -1,47 +1,47 @@ -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; - -vec4 process(vec4 col) { - float alpha = 1.0; - - - if (feather == 0.0) { - 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.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) { - return col * (1.0-alpha); - } else { - return col * alpha; - } +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; + +vec4 process(vec4 col) { + float alpha = 1.0; + + + if (feather == 0.0) { + 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.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) { + return col * (1.0-alpha); + } else { + return col * alpha; + } } \ No newline at end of file diff --git a/effects/shaders/crop.xml b/effects/shaders/crop.xml index be819a988..80d1717ea 100644 --- a/effects/shaders/crop.xml +++ b/effects/shaders/crop.xml @@ -1,22 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/directionalblur.frag b/effects/shaders/directionalblur.frag index 86c8ac079..4384746c6 100644 --- a/effects/shaders/directionalblur.frag +++ b/effects/shaders/directionalblur.frag @@ -1,26 +1,26 @@ -#define M_PI 3.1415926535897932384626433832795 - -uniform float angle; // degrees -uniform float length; - -uniform vec2 resolution; - -vec4 process(vec4 col) { - if (length > 0.0) { - float ceillen = ceil(length); - float radians = (angle*M_PI)/180.0; - float divider = 1.0 / ceillen; - float sin_angle = sin(radians); - float cos_angle = cos(radians); - - vec4 color = vec4(0.0); - for (float i=-ceillen+0.5;i<=ceillen;i+=2.0) { - float y = sin_angle * i; - float x = cos_angle * i; - color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); - } - return color; - } else { - return col; - } +#define M_PI 3.1415926535897932384626433832795 + +uniform float angle; // degrees +uniform float length; + +uniform vec2 resolution; + +vec4 process(vec4 col) { + if (length > 0.0) { + float ceillen = ceil(length); + float radians = (angle*M_PI)/180.0; + float divider = 1.0 / ceillen; + float sin_angle = sin(radians); + float cos_angle = cos(radians); + + vec4 color = vec4(0.0); + for (float i=-ceillen+0.5;i<=ceillen;i+=2.0) { + float y = sin_angle * i; + float x = cos_angle * i; + color += texture2D(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); + } + return color; + } else { + return col; + } } \ No newline at end of file diff --git a/effects/shaders/directionalblur.xml b/effects/shaders/directionalblur.xml index 9a66dac1d..654f6cdad 100644 --- a/effects/shaders/directionalblur.xml +++ b/effects/shaders/directionalblur.xml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/flip.frag b/effects/shaders/flip.frag index 6bdb05a9c..dbc62f67e 100644 --- a/effects/shaders/flip.frag +++ b/effects/shaders/flip.frag @@ -1,14 +1,14 @@ -uniform bool horiz; -uniform bool vert; - -vec4 process(vec4 col) { - float x = v_texcoord.x; - float y = v_texcoord.y; - - if (!horiz && !vert) return col; - - if (horiz) x = 1.0 - x; - if (vert) y = 1.0 - y; - - return texture2D(texture, vec2(x, y)); +uniform bool horiz; +uniform bool vert; + +vec4 process(vec4 col) { + float x = v_texcoord.x; + float y = v_texcoord.y; + + if (!horiz && !vert) return col; + + if (horiz) x = 1.0 - x; + if (vert) y = 1.0 - y; + + return texture2D(texture, vec2(x, y)); } \ No newline at end of file diff --git a/effects/shaders/flip.xml b/effects/shaders/flip.xml index f3d762733..7e083aafa 100644 --- a/effects/shaders/flip.xml +++ b/effects/shaders/flip.xml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/gaussianblur.frag b/effects/shaders/gaussianblur.frag index e1d08a367..ae332c7c3 100644 --- a/effects/shaders/gaussianblur.frag +++ b/effects/shaders/gaussianblur.frag @@ -1,47 +1,47 @@ -#define M_PI 3.1415926535897932384626433832795 - -uniform float sigma; -uniform vec2 resolution; -uniform bool horiz_blur; -uniform bool vert_blur; -uniform int iteration; - -float gaussian(float x, float sigma) { - return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0)); -} - -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))); -} - -vec4 process(vec4 col) { - float rad = ceil(3.0 * sigma); - - float sum = 0.0; - - vec4 color = vec4(0.0); - - bool radius_is_zero = (rad == 0.0 || sigma == 0.0); - - if (!radius_is_zero) { - for (float x=-rad+0.5;x<=rad;x+=2.0) { - sum += gaussian2(x, 0.0, sigma); - } - } - - 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(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y)/resolution)*(weight); - } - 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(texture, vec2(gl_FragCoord.x, gl_FragCoord.y+x)/resolution)*(weight); - } - return color; - } else { - return col; - } +#define M_PI 3.1415926535897932384626433832795 + +uniform float sigma; +uniform vec2 resolution; +uniform bool horiz_blur; +uniform bool vert_blur; +uniform int iteration; + +float gaussian(float x, float sigma) { + return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0)); +} + +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))); +} + +vec4 process(vec4 col) { + float rad = ceil(3.0 * sigma); + + float sum = 0.0; + + vec4 color = vec4(0.0); + + bool radius_is_zero = (rad == 0.0 || sigma == 0.0); + + if (!radius_is_zero) { + for (float x=-rad+0.5;x<=rad;x+=2.0) { + sum += gaussian2(x, 0.0, sigma); + } + } + + 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(texture, vec2(gl_FragCoord.x+x, gl_FragCoord.y)/resolution)*(weight); + } + 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(texture, vec2(gl_FragCoord.x, gl_FragCoord.y+x)/resolution)*(weight); + } + return color; + } else { + return col; + } } \ No newline at end of file diff --git a/effects/shaders/gaussianblur.xml b/effects/shaders/gaussianblur.xml index d0d669f6d..a2710cac0 100644 --- a/effects/shaders/gaussianblur.xml +++ b/effects/shaders/gaussianblur.xml @@ -1,16 +1,16 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/chromakey.frag b/effects/shaders/need checking/chromakey.frag index e24cbb1cf..9b48a6213 100644 --- a/effects/shaders/need checking/chromakey.frag +++ b/effects/shaders/need checking/chromakey.frag @@ -1,67 +1,67 @@ -/*a fast implementation of the chromakey program -(c) 2008 Edward Cannon (adapted to GLSL by Olive Team) -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 vec3 key_color; -uniform float tola; -uniform float tolb; -uniform bool opt; -uniform int mode; - -float rgb2y (vec3 c) { - /*a utility function to convert colors from REC.709 RGB into YCbCr*/ - -// This isn’t colour managed and is a huge mess, but in the -// short term, using correct weights will give significantly -// more ideal results. The correct weights for REC.709 are -// 0.2126 R, 0.7152 G, and 0.0722 B. Easy change. -// The coefficients for YCbCr are calculated off of the -// REC.709 values. - return (0.2126*c.r + 0.7152*c.g + 0.0722*c.b); -} - -float rgb2cb (vec3 c) { - /*a utility function to convert colors from REC.709 RGB into YCbCr*/ - return (0.5 + -0.1145721061*c.r - 0.3854278939*c.g + 0.5*c.b); -} - -float rgb2cr (vec3 c) { - /*a utility function to convert colors from REC.709 RGB into YCbCr*/ - return (0.5 + 0.5*c.r - 0.4541529083*c.g - 0.0458470917*c.b); -} - -float colorclose(float Cb_p,float Cr_p,float Cb_key,float Cr_key,float tola,float tolb) { - /*decides if a color is close to the specified hue*/ - float temp = sqrt(((Cb_key-Cb_p)*(Cb_key-Cb_p))+((Cr_key-Cr_p)*(Cr_key-Cr_p))); - if (temp < tola) {return (0.0);} - if (temp < tolb) {return ((temp-tola)/(tolb-tola));} - return (1.0); -} - -vec4 process(vec4 texture_color) { - float cb_key = rgb2cb(key_color); - float cr_key = rgb2cr(key_color); - - 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)); - - if (mode == 0) { // composite - //float submask = 1.0-mask; - float submask = 0.0; - texture_color.r = max(texture_color.r - submask*key_color.r, 0.0) + submask; - texture_color.g = max(texture_color.g - submask*key_color.g, 0.0) + submask; - texture_color.b = max(texture_color.b - submask*key_color.b, 0.0) + submask; - texture_color.a *= mask; - - // premultiply - texture_color.rgb *= texture_color.a; - } else if (mode == 1) { // alpha - texture_color.rgb = vec3(mask); - } else if (mode == 2) { // original - } - - return texture_color; -} +/*a fast implementation of the chromakey program +(c) 2008 Edward Cannon (adapted to GLSL by Olive Team) +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 vec3 key_color; +uniform float tola; +uniform float tolb; +uniform bool opt; +uniform int mode; + +float rgb2y (vec3 c) { + /*a utility function to convert colors from REC.709 RGB into YCbCr*/ + +// This isn’t colour managed and is a huge mess, but in the +// short term, using correct weights will give significantly +// more ideal results. The correct weights for REC.709 are +// 0.2126 R, 0.7152 G, and 0.0722 B. Easy change. +// The coefficients for YCbCr are calculated off of the +// REC.709 values. + return (0.2126*c.r + 0.7152*c.g + 0.0722*c.b); +} + +float rgb2cb (vec3 c) { + /*a utility function to convert colors from REC.709 RGB into YCbCr*/ + return (0.5 + -0.1145721061*c.r - 0.3854278939*c.g + 0.5*c.b); +} + +float rgb2cr (vec3 c) { + /*a utility function to convert colors from REC.709 RGB into YCbCr*/ + return (0.5 + 0.5*c.r - 0.4541529083*c.g - 0.0458470917*c.b); +} + +float colorclose(float Cb_p,float Cr_p,float Cb_key,float Cr_key,float tola,float tolb) { + /*decides if a color is close to the specified hue*/ + float temp = sqrt(((Cb_key-Cb_p)*(Cb_key-Cb_p))+((Cr_key-Cr_p)*(Cr_key-Cr_p))); + if (temp < tola) {return (0.0);} + if (temp < tolb) {return ((temp-tola)/(tolb-tola));} + return (1.0); +} + +vec4 process(vec4 texture_color) { + float cb_key = rgb2cb(key_color); + float cr_key = rgb2cr(key_color); + + 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)); + + if (mode == 0) { // composite + //float submask = 1.0-mask; + float submask = 0.0; + texture_color.r = max(texture_color.r - submask*key_color.r, 0.0) + submask; + texture_color.g = max(texture_color.g - submask*key_color.g, 0.0) + submask; + texture_color.b = max(texture_color.b - submask*key_color.b, 0.0) + submask; + texture_color.a *= mask; + + // premultiply + texture_color.rgb *= texture_color.a; + } else if (mode == 1) { // alpha + texture_color.rgb = vec3(mask); + } else if (mode == 2) { // original + } + + return texture_color; +} diff --git a/effects/shaders/need checking/chromakey.xml b/effects/shaders/need checking/chromakey.xml index 9c72859c7..ebe442dbf 100644 --- a/effects/shaders/need checking/chromakey.xml +++ b/effects/shaders/need checking/chromakey.xml @@ -1,20 +1,20 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/colorcorrection.frag b/effects/shaders/need checking/colorcorrection.frag index 46f2713f6..58f36a277 100644 --- a/effects/shaders/need checking/colorcorrection.frag +++ b/effects/shaders/need checking/colorcorrection.frag @@ -1,74 +1,74 @@ -#version 110 - -uniform float temperature; -uniform float tint; -uniform float exposure; -uniform float contrast; -uniform float highlights; -uniform float shadows; -uniform float whites; -uniform float blacks; -uniform float saturation; - -uniform sampler2D myTexture; -varying vec2 vTexCoord; - -void main(void) { - vec4 textureColor = texture2D(myTexture, vTexCoord); - - vec3 rgb = textureColor.rgb; - - // temperature - float temp = temperature * 0.01; - float redTemp = (temp <= 66.0) ? 1.0 : min(1.0, max(0.0, - (1.2929361861 * pow(temp - 60.0, -0.1332047592)) - )); - float greenTemp = min(1.0, max(0.0, - (temp <= 66.0) ? (0.3900815788 * log(temp) - 0.6318414438) : (1.1298908609 * pow(temp - 60.0, -0.0755148492)) - )); - float blueTemp = (temp >= 66.0) ? 1.0 : min(1.0, max(0.0, - (0.5432067891 * log(temp - 10.0) - 1.1962540891) - )); - rgb *= vec3(redTemp, greenTemp, blueTemp); - - // tint - rgb.g *= ((tint*0.01)+1.0); - - // exposure - rgb *= pow(2.0, exposure*0.01); - - // contrast - float contr_val = (contrast*0.01); - rgb = (rgb*contr_val)-((contr_val-1.0)*0.5); - - - // shadows/highlights - //float luma = 0.33*rgb.r + 0.5*rgb.g + 0.16*rgb.b; - if (rgb.r < 0.5 && rgb.g < 0.5 && rgb.b < 0.5) { - float shadow_val = 1.0-(shadows*0.01); - rgb = vec3(pow(rgb.r*2.0, shadow_val)*0.5, pow(rgb.g*2.0, shadow_val)*0.5, pow(rgb.b*2.0, shadow_val)*0.5); - } else if (rgb.r > 0.5 && rgb.g > 0.5 && rgb.b > 0.5) { - float highlight_val = 1.0-(highlights*0.01); - rgb = vec3((pow((rgb.r-0.5)*2.0, highlight_val)*0.5)+0.5, (pow((rgb.g-0.5)*2.0, highlight_val)*0.5)+0.5, (pow((rgb.b-0.5)*2.0, highlight_val)*0.5)+0.5); - } - - - // whites - rgb *= (whites*0.01); - - // blacks - float black_val = 2.0-(blacks*0.01); - rgb = (rgb*black_val)-(black_val-1.0); - - // saturation - const vec3 W = vec3(0.2125, 0.7154, 0.0721); - vec3 intensity = vec3(dot(rgb, W)); - rgb = mix(intensity, rgb, saturation*0.01); - - gl_FragColor = vec4( - rgb.r, - rgb.g, - rgb.b, - textureColor.a - ); -} +#version 110 + +uniform float temperature; +uniform float tint; +uniform float exposure; +uniform float contrast; +uniform float highlights; +uniform float shadows; +uniform float whites; +uniform float blacks; +uniform float saturation; + +uniform sampler2D myTexture; +varying vec2 vTexCoord; + +void main(void) { + vec4 textureColor = texture2D(myTexture, vTexCoord); + + vec3 rgb = textureColor.rgb; + + // temperature + float temp = temperature * 0.01; + float redTemp = (temp <= 66.0) ? 1.0 : min(1.0, max(0.0, + (1.2929361861 * pow(temp - 60.0, -0.1332047592)) + )); + float greenTemp = min(1.0, max(0.0, + (temp <= 66.0) ? (0.3900815788 * log(temp) - 0.6318414438) : (1.1298908609 * pow(temp - 60.0, -0.0755148492)) + )); + float blueTemp = (temp >= 66.0) ? 1.0 : min(1.0, max(0.0, + (0.5432067891 * log(temp - 10.0) - 1.1962540891) + )); + rgb *= vec3(redTemp, greenTemp, blueTemp); + + // tint + rgb.g *= ((tint*0.01)+1.0); + + // exposure + rgb *= pow(2.0, exposure*0.01); + + // contrast + float contr_val = (contrast*0.01); + rgb = (rgb*contr_val)-((contr_val-1.0)*0.5); + + + // shadows/highlights + //float luma = 0.33*rgb.r + 0.5*rgb.g + 0.16*rgb.b; + if (rgb.r < 0.5 && rgb.g < 0.5 && rgb.b < 0.5) { + float shadow_val = 1.0-(shadows*0.01); + rgb = vec3(pow(rgb.r*2.0, shadow_val)*0.5, pow(rgb.g*2.0, shadow_val)*0.5, pow(rgb.b*2.0, shadow_val)*0.5); + } else if (rgb.r > 0.5 && rgb.g > 0.5 && rgb.b > 0.5) { + float highlight_val = 1.0-(highlights*0.01); + rgb = vec3((pow((rgb.r-0.5)*2.0, highlight_val)*0.5)+0.5, (pow((rgb.g-0.5)*2.0, highlight_val)*0.5)+0.5, (pow((rgb.b-0.5)*2.0, highlight_val)*0.5)+0.5); + } + + + // whites + rgb *= (whites*0.01); + + // blacks + float black_val = 2.0-(blacks*0.01); + rgb = (rgb*black_val)-(black_val-1.0); + + // saturation + const vec3 W = vec3(0.2125, 0.7154, 0.0721); + vec3 intensity = vec3(dot(rgb, W)); + rgb = mix(intensity, rgb, saturation*0.01); + + gl_FragColor = vec4( + rgb.r, + rgb.g, + rgb.b, + textureColor.a + ); +} diff --git a/effects/shaders/need checking/colorcorrection.xml b/effects/shaders/need checking/colorcorrection.xml index f103f3d82..14c39a28c 100644 --- a/effects/shaders/need checking/colorcorrection.xml +++ b/effects/shaders/need checking/colorcorrection.xml @@ -1,31 +1,31 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/colorsel.frag b/effects/shaders/need checking/colorsel.frag index dc4b7f280..785d13d3a 100644 --- a/effects/shaders/need checking/colorsel.frag +++ b/effects/shaders/need checking/colorsel.frag @@ -1,78 +1,78 @@ -/* Filter by color characteristic simple program -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 */ - -uniform float loc; -uniform float hic; -uniform int compo; -uniform bool invert; - -float rgb2luma(vec3 c) { - return (max(max(c.r,c.g), c.b) + min(min(c.r,c.g), c.b))/2.0; -} - -vec3 rgb2hsv(vec3 c) -{ - float r = c.r; - float b = c.b; - float g = c.g; - float minv, maxv, delta; - vec3 res; - - minv = min(min(r, g), b); - maxv = max(max(r, g), b); - res.z = maxv; // v - - delta = maxv - minv; - - if( maxv != 0.0 ) - res.y = delta / maxv; // s - else { - // r = g = b = 0 // s = 0, v is undefined - res.y = 0.0; - res.x = -1.0; - return res; - } - - if( r == maxv ) - res.x = ( g - b ) / delta; // between yellow & magenta - else if( g == maxv ) - res.x = 2.0 + ( b - r ) / delta; // between cyan & yellow - else - res.x = 4.0 + ( r - g ) / delta; // between magenta & cyan - - res.x = res.x * 60.0; // degrees - if( res.x < 0.0 ) - res.x = res.x + 360.0; - - return res; -} - -bool isNotIncreasingSequence(float a, float b, float c) { - return (c < b || a > b); -} - -vec4 process(vec4 texture_color) { - vec3 color = texture_color.rgb; - float toCheck = 0.0; - - if (compo == 0) { - toCheck = rgb2luma(color)*100.0; - } else if (compo == 4) { - toCheck = color.r*100.0; - } else if (compo == 5) { - toCheck = color.g*100.0; - } else if (compo == 6) { - toCheck = color.b*100.0; - } else if (compo == 1) { - toCheck = rgb2hsv(color).z*100.0; - } else if (compo == 2) { - toCheck = rgb2hsv(color).x/3.6; - } else if (compo == 3) { - toCheck = rgb2hsv(color).y*100.0; - } - 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; - return texture_color; -} +/* Filter by color characteristic simple program +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 */ + +uniform float loc; +uniform float hic; +uniform int compo; +uniform bool invert; + +float rgb2luma(vec3 c) { + return (max(max(c.r,c.g), c.b) + min(min(c.r,c.g), c.b))/2.0; +} + +vec3 rgb2hsv(vec3 c) +{ + float r = c.r; + float b = c.b; + float g = c.g; + float minv, maxv, delta; + vec3 res; + + minv = min(min(r, g), b); + maxv = max(max(r, g), b); + res.z = maxv; // v + + delta = maxv - minv; + + if( maxv != 0.0 ) + res.y = delta / maxv; // s + else { + // r = g = b = 0 // s = 0, v is undefined + res.y = 0.0; + res.x = -1.0; + return res; + } + + if( r == maxv ) + res.x = ( g - b ) / delta; // between yellow & magenta + else if( g == maxv ) + res.x = 2.0 + ( b - r ) / delta; // between cyan & yellow + else + res.x = 4.0 + ( r - g ) / delta; // between magenta & cyan + + res.x = res.x * 60.0; // degrees + if( res.x < 0.0 ) + res.x = res.x + 360.0; + + return res; +} + +bool isNotIncreasingSequence(float a, float b, float c) { + return (c < b || a > b); +} + +vec4 process(vec4 texture_color) { + vec3 color = texture_color.rgb; + float toCheck = 0.0; + + if (compo == 0) { + toCheck = rgb2luma(color)*100.0; + } else if (compo == 4) { + toCheck = color.r*100.0; + } else if (compo == 5) { + toCheck = color.g*100.0; + } else if (compo == 6) { + toCheck = color.b*100.0; + } else if (compo == 1) { + toCheck = rgb2hsv(color).z*100.0; + } else if (compo == 2) { + toCheck = rgb2hsv(color).x/3.6; + } else if (compo == 3) { + toCheck = rgb2hsv(color).y*100.0; + } + 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; + return texture_color; +} diff --git a/effects/shaders/need checking/colorsel.xml b/effects/shaders/need checking/colorsel.xml index 00a4c865a..7a3e7cf7a 100644 --- a/effects/shaders/need checking/colorsel.xml +++ b/effects/shaders/need checking/colorsel.xml @@ -1,24 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/effects/shaders/need checking/common.frag b/effects/shaders/need checking/common.frag index 560ccee97..a8d338af5 100644 --- a/effects/shaders/need checking/common.frag +++ b/effects/shaders/need checking/common.frag @@ -1,9 +1,9 @@ -#version 110 - -uniform sampler2D tex; -varying vec2 vTexCoord; - -void main(void) { - vec4 textureColor = texture2D(tex, vTexCoord); - gl_FragColor = textureColor; +#version 110 + +uniform sampler2D tex; +varying vec2 vTexCoord; + +void main(void) { + vec4 textureColor = texture2D(tex, vTexCoord); + gl_FragColor = textureColor; } \ No newline at end of file diff --git a/effects/shaders/need checking/common.vert b/effects/shaders/need checking/common.vert index 2d088fa2f..d698c628e 100644 --- a/effects/shaders/need checking/common.vert +++ b/effects/shaders/need checking/common.vert @@ -1,8 +1,8 @@ -#version 110 - -varying vec2 vTexCoord; - -void main() { - vTexCoord = gl_MultiTexCoord0.xy; - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +#version 110 + +varying vec2 vTexCoord; + +void main() { + vTexCoord = gl_MultiTexCoord0.xy; + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } \ No newline at end of file diff --git a/effects/shaders/need checking/crossstitch.xml b/effects/shaders/need checking/crossstitch.xml index e31158bcc..4b628f2ba 100644 --- a/effects/shaders/need checking/crossstitch.xml +++ b/effects/shaders/need checking/crossstitch.xml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/emboss.xml b/effects/shaders/need checking/emboss.xml index d849cd914..df8cef189 100644 --- a/effects/shaders/need checking/emboss.xml +++ b/effects/shaders/need checking/emboss.xml @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/effects/shaders/need checking/findedges.frag b/effects/shaders/need checking/findedges.frag index c8fe24c5e..5431590ee 100644 --- a/effects/shaders/need checking/findedges.frag +++ b/effects/shaders/need checking/findedges.frag @@ -1,53 +1,53 @@ -#version 110 - -/*varying vec4 vertColor; -varying vec4 vertTexCoord; -uniform sampler2D texture; -//uniform vec2 texOffset; - -void main(void) { - // Grouping texcoord variables in order to make it work in the GMA 950. See post #13 - // in this thread: - // http://www.idevgames.com/forums/thread-3467.html - vec2 texOffset = vec2(1.0); - - vec2 tc0 = vertTexCoord.st + vec2(-texOffset.s, -texOffset.t); - vec2 tc1 = vertTexCoord.st + vec2( 0.0, -texOffset.t); - vec2 tc2 = vertTexCoord.st + vec2(+texOffset.s, -texOffset.t); - vec2 tc3 = vertTexCoord.st + vec2(-texOffset.s, 0.0); - vec2 tc4 = vertTexCoord.st + vec2( 0.0, 0.0); - vec2 tc5 = vertTexCoord.st + vec2(+texOffset.s, 0.0); - vec2 tc6 = vertTexCoord.st + vec2(-texOffset.s, +texOffset.t); - vec2 tc7 = vertTexCoord.st + vec2( 0.0, +texOffset.t); - vec2 tc8 = vertTexCoord.st + vec2(+texOffset.s, +texOffset.t); - - vec4 col0 = texture2D(texture, tc0); - vec4 col1 = texture2D(texture, tc1); - vec4 col2 = texture2D(texture, tc2); - vec4 col3 = texture2D(texture, tc3); - vec4 col4 = texture2D(texture, tc4); - vec4 col5 = texture2D(texture, tc5); - vec4 col6 = texture2D(texture, tc6); - vec4 col7 = texture2D(texture, tc7); - vec4 col8 = texture2D(texture, tc8); - - vec4 sum = 8.0 * col4 - (col0 + col1 + col2 + col3 + col5 + col6 + col7 + col8); - gl_FragColor = vec4(sum.rgb, 1.0);// * vertColor; -}*/ - -varying vec4 vertTexCoord; -uniform sampler2D texture; -//uniform vec2 pixels; - -void main(void) -{ - vec2 pixels = vec2(16.0, 9.0); - - vec2 p = vertTexCoord.st; - - p.x -= mod(p.x, 1.0 / pixels.x); - p.y -= mod(p.y, 1.0 / pixels.y); - - vec3 col = texture2D(texture, p).rgb; - gl_FragColor = vec4(col, 1.0); +#version 110 + +/*varying vec4 vertColor; +varying vec4 vertTexCoord; +uniform sampler2D texture; +//uniform vec2 texOffset; + +void main(void) { + // Grouping texcoord variables in order to make it work in the GMA 950. See post #13 + // in this thread: + // http://www.idevgames.com/forums/thread-3467.html + vec2 texOffset = vec2(1.0); + + vec2 tc0 = vertTexCoord.st + vec2(-texOffset.s, -texOffset.t); + vec2 tc1 = vertTexCoord.st + vec2( 0.0, -texOffset.t); + vec2 tc2 = vertTexCoord.st + vec2(+texOffset.s, -texOffset.t); + vec2 tc3 = vertTexCoord.st + vec2(-texOffset.s, 0.0); + vec2 tc4 = vertTexCoord.st + vec2( 0.0, 0.0); + vec2 tc5 = vertTexCoord.st + vec2(+texOffset.s, 0.0); + vec2 tc6 = vertTexCoord.st + vec2(-texOffset.s, +texOffset.t); + vec2 tc7 = vertTexCoord.st + vec2( 0.0, +texOffset.t); + vec2 tc8 = vertTexCoord.st + vec2(+texOffset.s, +texOffset.t); + + vec4 col0 = texture2D(texture, tc0); + vec4 col1 = texture2D(texture, tc1); + vec4 col2 = texture2D(texture, tc2); + vec4 col3 = texture2D(texture, tc3); + vec4 col4 = texture2D(texture, tc4); + vec4 col5 = texture2D(texture, tc5); + vec4 col6 = texture2D(texture, tc6); + vec4 col7 = texture2D(texture, tc7); + vec4 col8 = texture2D(texture, tc8); + + vec4 sum = 8.0 * col4 - (col0 + col1 + col2 + col3 + col5 + col6 + col7 + col8); + gl_FragColor = vec4(sum.rgb, 1.0);// * vertColor; +}*/ + +varying vec4 vertTexCoord; +uniform sampler2D texture; +//uniform vec2 pixels; + +void main(void) +{ + vec2 pixels = vec2(16.0, 9.0); + + vec2 p = vertTexCoord.st; + + p.x -= mod(p.x, 1.0 / pixels.x); + p.y -= mod(p.y, 1.0 / pixels.y); + + vec3 col = texture2D(texture, p).rgb; + gl_FragColor = vec4(col, 1.0); } \ No newline at end of file diff --git a/effects/shaders/need checking/findedges.xml.disabled b/effects/shaders/need checking/findedges.xml.disabled index 0ea0f822c..7f4ec3e5b 100644 --- a/effects/shaders/need checking/findedges.xml.disabled +++ b/effects/shaders/need checking/findedges.xml.disabled @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/fisheye.xml b/effects/shaders/need checking/fisheye.xml index b94302289..2074ccf31 100644 --- a/effects/shaders/need checking/fisheye.xml +++ b/effects/shaders/need checking/fisheye.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/huesatbri.frag b/effects/shaders/need checking/huesatbri.frag index 0582fa501..0efe90540 100644 --- a/effects/shaders/need checking/huesatbri.frag +++ b/effects/shaders/need checking/huesatbri.frag @@ -1,33 +1,33 @@ -uniform float hue; -uniform float saturation; -uniform float brightness; - -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)); - vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -vec4 process(vec4 tex_color) { - vec3 hsv = rgb2hsv(tex_color.rgb); - hsv.r += (hue/360.0); - hsv.g *= (saturation*0.01); - hsv.b *= (brightness*0.01); - - vec3 rgb = hsv2rgb(hsv); - - return vec4( - rgb.rgb, - tex_color.a - ); +uniform float hue; +uniform float saturation; +uniform float brightness; + +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)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +vec4 process(vec4 tex_color) { + vec3 hsv = rgb2hsv(tex_color.rgb); + hsv.r += (hue/360.0); + hsv.g *= (saturation*0.01); + hsv.b *= (brightness*0.01); + + vec3 rgb = hsv2rgb(hsv); + + return vec4( + rgb.rgb, + tex_color.a + ); } \ No newline at end of file diff --git a/effects/shaders/need checking/huesatbri.xml b/effects/shaders/need checking/huesatbri.xml index b20b5219b..9ee74aae5 100644 --- a/effects/shaders/need checking/huesatbri.xml +++ b/effects/shaders/need checking/huesatbri.xml @@ -1,13 +1,13 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/invert.frag b/effects/shaders/need checking/invert.frag index bcfa87612..454d8e74a 100644 --- a/effects/shaders/need checking/invert.frag +++ b/effects/shaders/need checking/invert.frag @@ -1,7 +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); +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/need checking/invert.xml b/effects/shaders/need checking/invert.xml index bc2ee42fb..6c93dc92e 100644 --- a/effects/shaders/need checking/invert.xml +++ b/effects/shaders/need checking/invert.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/lumakey.frag b/effects/shaders/need checking/lumakey.frag index 16dfdfa08..b376a60ac 100644 --- a/effects/shaders/need checking/lumakey.frag +++ b/effects/shaders/need checking/lumakey.frag @@ -1,29 +1,29 @@ -/* Luma key simple program -Based on Edward Cannon's Simple Chroma Key (adaptation by Olive Team) -Feel free to modify and use at will */ - -uniform sampler2D tex; -varying vec2 vTexCoord; - -uniform float loc; -uniform float hic; -uniform bool invert; - -void main(void) { - vec4 texture_color = texture2D(tex,vTexCoord); - - float luma = max(max(texture_color.r,texture_color.g), texture_color.b) + min(min(texture_color.r,texture_color.g), texture_color.b); - - luma /= 2.0; - - if (luma > hic/100.0) { - texture_color.a = (invert ? 0.0 : 1.0); - } else if (luma < loc/100.0) { - texture_color.a = (invert ? 1.0 : 0.0); - } else { - texture_color.a = (invert ? 1.0-luma : luma); - } - - texture_color.rgb *= texture_color.a; - gl_FragColor = texture_color; -} +/* Luma key simple program +Based on Edward Cannon's Simple Chroma Key (adaptation by Olive Team) +Feel free to modify and use at will */ + +uniform sampler2D tex; +varying vec2 vTexCoord; + +uniform float loc; +uniform float hic; +uniform bool invert; + +void main(void) { + vec4 texture_color = texture2D(tex,vTexCoord); + + float luma = max(max(texture_color.r,texture_color.g), texture_color.b) + min(min(texture_color.r,texture_color.g), texture_color.b); + + luma /= 2.0; + + if (luma > hic/100.0) { + texture_color.a = (invert ? 0.0 : 1.0); + } else if (luma < loc/100.0) { + texture_color.a = (invert ? 1.0 : 0.0); + } else { + texture_color.a = (invert ? 1.0-luma : luma); + } + + texture_color.rgb *= texture_color.a; + gl_FragColor = texture_color; +} diff --git a/effects/shaders/need checking/lumakey.xml b/effects/shaders/need checking/lumakey.xml index 0c3175c2e..ba297ef71 100644 --- a/effects/shaders/need checking/lumakey.xml +++ b/effects/shaders/need checking/lumakey.xml @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/effects/shaders/need checking/posterize.xml b/effects/shaders/need checking/posterize.xml index ac08598a5..f4ce8e9cd 100644 --- a/effects/shaders/need checking/posterize.xml +++ b/effects/shaders/need checking/posterize.xml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/toonify.xml b/effects/shaders/need checking/toonify.xml index 81e9a5c68..7cad76af4 100644 --- a/effects/shaders/need checking/toonify.xml +++ b/effects/shaders/need checking/toonify.xml @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/need checking/volumetriclight.xml b/effects/shaders/need checking/volumetriclight.xml index 0e5d38a64..4247548a2 100644 --- a/effects/shaders/need checking/volumetriclight.xml +++ b/effects/shaders/need checking/volumetriclight.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/effects/shaders/noise.frag b/effects/shaders/noise.frag index 59a7904a6..352560d40 100644 --- a/effects/shaders/noise.frag +++ b/effects/shaders/noise.frag @@ -1,33 +1,33 @@ -uniform vec2 resolution; -uniform float time; - -uniform float amount; -uniform bool color; -uniform bool blend; - -// precision lowp float; - -float PHI = 1.61803398874989484820459 * 00000.1; // Golden Ratio -float PI = 3.14159265358979323846264 * 00000.1; // PI -float SQ2 = 1.41421356237309504880169 * 10000.0; // Square Root of Two - -float gold_noise(vec2 coordinate, float seed){ - return fract(tan(distance(coordinate*(seed+PHI), vec2(PHI, PI)))*SQ2)*(amount*0.01); -} - -vec4 process(vec4 col) { - vec3 noise; - if (color) { - noise = vec3(gold_noise(v_texcoord, time + 42069.0), gold_noise(v_texcoord, time + 69220.0), gold_noise(v_texcoord, time + 1337.0)); - } else { - noise = vec3(gold_noise(v_texcoord, time + 69420.0)); - } - - if (blend) { - noise = (noise - vec3(amount*0.005))*vec3(2.0); - - return vec4(col.rgb+noise, col.a); - } else { - return vec4(noise, 1.0); - } -} +uniform vec2 resolution; +uniform float time; + +uniform float amount; +uniform bool color; +uniform bool blend; + +// precision lowp float; + +float PHI = 1.61803398874989484820459 * 00000.1; // Golden Ratio +float PI = 3.14159265358979323846264 * 00000.1; // PI +float SQ2 = 1.41421356237309504880169 * 10000.0; // Square Root of Two + +float gold_noise(vec2 coordinate, float seed){ + return fract(tan(distance(coordinate*(seed+PHI), vec2(PHI, PI)))*SQ2)*(amount*0.01); +} + +vec4 process(vec4 col) { + vec3 noise; + if (color) { + noise = vec3(gold_noise(v_texcoord, time + 42069.0), gold_noise(v_texcoord, time + 69220.0), gold_noise(v_texcoord, time + 1337.0)); + } else { + noise = vec3(gold_noise(v_texcoord, time + 69420.0)); + } + + if (blend) { + noise = (noise - vec3(amount*0.005))*vec3(2.0); + + return vec4(col.rgb+noise, col.a); + } else { + return vec4(noise, 1.0); + } +} diff --git a/effects/shaders/noise.xml b/effects/shaders/noise.xml index b90478c03..229f967d2 100644 --- a/effects/shaders/noise.xml +++ b/effects/shaders/noise.xml @@ -1,13 +1,13 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/pixelate.frag b/effects/shaders/pixelate.frag index c5e00af00..7f9a57bb1 100644 --- a/effects/shaders/pixelate.frag +++ b/effects/shaders/pixelate.frag @@ -1,16 +1,16 @@ -uniform float pixels_x; -uniform float pixels_y; -uniform bool bypass; - -vec4 process(vec4 col) { - if (bypass) { - return col; - } else { - vec2 p = v_texcoord; - - p.x -= mod(p.x, 1.0 / pixels_x); - p.y -= mod(p.y, 1.0 / pixels_y); - - return texture2D(texture, p); - } +uniform float pixels_x; +uniform float pixels_y; +uniform bool bypass; + +vec4 process(vec4 col) { + if (bypass) { + return col; + } else { + vec2 p = v_texcoord; + + p.x -= mod(p.x, 1.0 / pixels_x); + p.y -= mod(p.y, 1.0 / pixels_y); + + return texture2D(texture, p); + } } \ No newline at end of file diff --git a/effects/shaders/pixelate.xml b/effects/shaders/pixelate.xml index 414c54c8d..8fa63a932 100644 --- a/effects/shaders/pixelate.xml +++ b/effects/shaders/pixelate.xml @@ -1,13 +1,13 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/radialblur.xml b/effects/shaders/radialblur.xml index 1bd3af991..547ffe8f4 100644 --- a/effects/shaders/radialblur.xml +++ b/effects/shaders/radialblur.xml @@ -1,11 +1,11 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/ripple.frag b/effects/shaders/ripple.frag index b9fc951f0..89f053208 100644 --- a/effects/shaders/ripple.frag +++ b/effects/shaders/ripple.frag @@ -1,29 +1,29 @@ -uniform float speed; -uniform float intensity; -uniform float frequency; -uniform float xoff; -uniform float yoff; -uniform bool reverse; -uniform bool stretch; - -uniform vec2 resolution; // Screen resolution -uniform float time; // time in seconds - -vec4 process(vec4 col) { - vec2 texCoord = v_texcoord; - vec2 center = vec2(1.0); - - if (!stretch) { - texCoord.x *= (resolution.x/resolution.y); - center.x += (resolution.x/resolution.y)/2.0; - } - - center += vec2(xoff, yoff)*0.01; - - float real_time = (reverse) ? -time : time; - - vec2 p = 2.0 * texCoord - center; - float len = length(p); - 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); +uniform float speed; +uniform float intensity; +uniform float frequency; +uniform float xoff; +uniform float yoff; +uniform bool reverse; +uniform bool stretch; + +uniform vec2 resolution; // Screen resolution +uniform float time; // time in seconds + +vec4 process(vec4 col) { + vec2 texCoord = v_texcoord; + vec2 center = vec2(1.0); + + if (!stretch) { + texCoord.x *= (resolution.x/resolution.y); + center.x += (resolution.x/resolution.y)/2.0; + } + + center += vec2(xoff, yoff)*0.01; + + float real_time = (reverse) ? -time : time; + + vec2 p = 2.0 * texCoord - center; + float len = length(p); + 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/ripple.xml b/effects/shaders/ripple.xml index 3e7aede75..15f4a09cf 100644 --- a/effects/shaders/ripple.xml +++ b/effects/shaders/ripple.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/sphere.frag b/effects/shaders/sphere.frag index 6496dd5b3..ff20d28fa 100644 --- a/effects/shaders/sphere.frag +++ b/effects/shaders/sphere.frag @@ -1,40 +1,40 @@ -uniform vec2 resolution; // Screen resolution - -uniform float xoff; -uniform float yoff; -uniform float scale; -uniform bool tile; -uniform bool hide_edges; -uniform bool stretch; - -vec4 process(vec4 col) { - vec2 texCoord = v_texcoord; - - vec2 offset = vec2(1.0); - - if (!stretch) { - if (resolution.x > resolution.y) { - offset.x = (resolution.x/resolution.y); - texCoord.x *= offset.x; - } else { - offset.y = (resolution.y/resolution.x); - texCoord.y *= offset.y; - } - } - - vec2 p = 2.0 * texCoord - offset; - 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); - vec2 uv; - uv.x = (adj_tc.x*(1.5-scale*0.01))*f+0.5-(xoff*0.01); - uv.y = (adj_tc.y*(1.5-scale*0.01))*f+0.5-(yoff*0.01); - if (tile) { - 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)) { - return vec4(0.0); - } - return vec4(texture2D(texture,uv)); -} +uniform vec2 resolution; // Screen resolution + +uniform float xoff; +uniform float yoff; +uniform float scale; +uniform bool tile; +uniform bool hide_edges; +uniform bool stretch; + +vec4 process(vec4 col) { + vec2 texCoord = v_texcoord; + + vec2 offset = vec2(1.0); + + if (!stretch) { + if (resolution.x > resolution.y) { + offset.x = (resolution.x/resolution.y); + texCoord.x *= offset.x; + } else { + offset.y = (resolution.y/resolution.x); + texCoord.y *= offset.y; + } + } + + vec2 p = 2.0 * texCoord - offset; + 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); + vec2 uv; + uv.x = (adj_tc.x*(1.5-scale*0.01))*f+0.5-(xoff*0.01); + uv.y = (adj_tc.y*(1.5-scale*0.01))*f+0.5-(yoff*0.01); + if (tile) { + 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)) { + return vec4(0.0); + } + return vec4(texture2D(texture,uv)); +} diff --git a/effects/shaders/sphere.xml b/effects/shaders/sphere.xml index 4bcd06c26..8b469d84a 100644 --- a/effects/shaders/sphere.xml +++ b/effects/shaders/sphere.xml @@ -1,20 +1,20 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/swirl.frag b/effects/shaders/swirl.frag index 2a3b0f569..3463e01a3 100644 --- a/effects/shaders/swirl.frag +++ b/effects/shaders/swirl.frag @@ -1,26 +1,26 @@ -// Swirl effect parameters -uniform float radius; -uniform float angle; -uniform float center_x; -uniform float center_y; -uniform vec2 resolution; - -vec4 process(vec4 col) { - vec2 center = vec2((resolution.x*0.5)+center_x, (resolution.y*0.5)+center_y); - - vec2 uv = v_texcoord.st; - - vec2 tc = uv * resolution; - tc -= center; - float dist = length(tc); - if (dist < radius) { - float percent = (radius - dist) / radius; - float theta = percent * percent * (-angle*0.05) * 8.0; - float s = sin(theta); - float c = cos(theta); - tc = vec2(dot(tc, vec2(c, -s)), dot(tc, vec2(s, c))); - } - tc += center; - - return texture2D(texture, tc / resolution); +// Swirl effect parameters +uniform float radius; +uniform float angle; +uniform float center_x; +uniform float center_y; +uniform vec2 resolution; + +vec4 process(vec4 col) { + vec2 center = vec2((resolution.x*0.5)+center_x, (resolution.y*0.5)+center_y); + + vec2 uv = v_texcoord.st; + + vec2 tc = uv * resolution; + tc -= center; + float dist = length(tc); + if (dist < radius) { + float percent = (radius - dist) / radius; + float theta = percent * percent * (-angle*0.05) * 8.0; + float s = sin(theta); + float c = cos(theta); + tc = vec2(dot(tc, vec2(c, -s)), dot(tc, vec2(s, c))); + } + tc += center; + + return texture2D(texture, tc / resolution); } \ No newline at end of file diff --git a/effects/shaders/swirl.xml b/effects/shaders/swirl.xml index fad929dfa..7acdec918 100644 --- a/effects/shaders/swirl.xml +++ b/effects/shaders/swirl.xml @@ -1,14 +1,14 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/tile.frag b/effects/shaders/tile.frag index c7194a246..ac3b6d65d 100644 --- a/effects/shaders/tile.frag +++ b/effects/shaders/tile.frag @@ -1,25 +1,25 @@ -uniform float scale; -uniform float centerx; -uniform float centery; -uniform bool mirrorx; -uniform bool mirrory; - -vec4 process(vec4 col) { - - float adj_scale = scale*0.01; - - 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); - - if (mirrorx && mod(coord.x, 2.0) > 1.0) { - modcoord.x = 1.0 - modcoord.x; - } - - if (mirrory && mod(coord.y, 2.0) > 1.0) { - modcoord.y = 1.0 - modcoord.y; - } - - return vec4(texture2D(texture, modcoord)); - +uniform float scale; +uniform float centerx; +uniform float centery; +uniform bool mirrorx; +uniform bool mirrory; + +vec4 process(vec4 col) { + + float adj_scale = scale*0.01; + + 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); + + if (mirrorx && mod(coord.x, 2.0) > 1.0) { + modcoord.x = 1.0 - modcoord.x; + } + + if (mirrory && mod(coord.y, 2.0) > 1.0) { + modcoord.y = 1.0 - modcoord.y; + } + + return vec4(texture2D(texture, modcoord)); + } \ No newline at end of file diff --git a/effects/shaders/tile.xml b/effects/shaders/tile.xml index e002fd65d..7bb54ec45 100644 --- a/effects/shaders/tile.xml +++ b/effects/shaders/tile.xml @@ -1,17 +1,17 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/vignette.xml b/effects/shaders/vignette.xml index 849e6393c..d479157ed 100644 --- a/effects/shaders/vignette.xml +++ b/effects/shaders/vignette.xml @@ -1,20 +1,20 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/shaders/wave.frag b/effects/shaders/wave.frag index bb1bc608c..89f9276ea 100644 --- a/effects/shaders/wave.frag +++ b/effects/shaders/wave.frag @@ -1,21 +1,21 @@ -uniform float frequency; -uniform float intensity; -uniform float evolution; -uniform bool vertical; - -vec4 process(vec4 col) { - float x = v_texcoord.x; - float y = v_texcoord.y; - - if (vertical) { - x -= sin((v_texcoord.y-(evolution*0.01))*frequency)*intensity*0.01; - } else { - y -= sin((v_texcoord.x-(evolution*0.01))*frequency)*intensity*0.01; - } - - if (y < 0.0 || y > 1.0 || x < 0.0 || x > 1.0) { - return vec4(0.0); - } else { - return texture2D(texture, vec2(x, y)); - } +uniform float frequency; +uniform float intensity; +uniform float evolution; +uniform bool vertical; + +vec4 process(vec4 col) { + float x = v_texcoord.x; + float y = v_texcoord.y; + + if (vertical) { + x -= sin((v_texcoord.y-(evolution*0.01))*frequency)*intensity*0.01; + } else { + y -= sin((v_texcoord.x-(evolution*0.01))*frequency)*intensity*0.01; + } + + if (y < 0.0 || y > 1.0 || x < 0.0 || x > 1.0) { + return vec4(0.0); + } else { + return texture2D(texture, vec2(x, y)); + } } \ No newline at end of file diff --git a/effects/shaders/wave.xml b/effects/shaders/wave.xml index 789739616..76be754da 100644 --- a/effects/shaders/wave.xml +++ b/effects/shaders/wave.xml @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/effects/transition.cpp b/effects/transition.cpp index defb79b99..493463b24 100644 --- a/effects/transition.cpp +++ b/effects/transition.cpp @@ -1,165 +1,165 @@ -/*** - - 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 "transition.h" - -#include "ui/mainwindow.h" -#include "timeline/clip.h" -#include "timeline/sequence.h" -#include "global/debug.h" -#include "global/clipboard.h" - -#include "effects/internal/crossdissolvetransition.h" -#include "effects/internal/linearfadetransition.h" -#include "effects/internal/exponentialfadetransition.h" -#include "effects/internal/logarithmicfadetransition.h" -#include "effects/internal/cubetransition.h" - -#include "ui/labelslider.h" - -#include "panels/panels.h" -#include "panels/timeline.h" - -#include -#include - -Transition::Transition(Clip *c) : - OldEffectNode(c), - secondary_clip(nullptr) -{ - length_field = new DoubleInput(this, "length", tr("Length"), false, false); - length_field->SetDefault(30); - length_field->SetMinimum(1); - length_field->SetDisplayType(LabelSlider::FrameNumber); - - 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())); -} - -OldEffectNodePtr Transition::copy(Clip *c) { - OldEffectNodePtr node = OldEffectNode::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())); - OldEffectNode::save(stream); -} - -void Transition::set_length(int l) { - length_field->SetValueAt(0, l); -} - -int Transition::get_true_length() { - return length_field->GetValueAt(0).toInt(); -} - -int Transition::get_length() { - if (secondary_clip != nullptr) { - return get_true_length() * 2; - } - return get_true_length(); -} - -Clip* Transition::get_opened_clip() { - if (parent_clip->opening_transition.get() == this) { - return parent_clip; - } else if (secondary_clip != nullptr && secondary_clip->opening_transition.get() == this) { - return secondary_clip; - } - return nullptr; -} - -Clip* Transition::get_closed_clip() { - if (parent_clip->closing_transition.get() == this) { - return parent_clip; - } else if (secondary_clip != nullptr && secondary_clip->closing_transition.get() == this) { - return secondary_clip; - } - return nullptr; -} - -/* -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 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 { - qCritical() << "Invalid transition data"; - QMessageBox::critical(olive::MainWindow, - QCoreApplication::translate("transition", "Invalid transition"), - QCoreApplication::translate("transition", "No candidate for transition '%1'. This transition may be corrupt. Try reinstalling it or Olive.").arg(em->name) - ); - } - return nullptr; -} -*/ - -void Transition::UpdateMaximumLength() -{ - // Get the maximum area this transition can occupy on the clip - long maximum_length = GetMaximumEmptySpaceOnClip(parent_clip); - - // If this clip is a shared transition, get the maximum area this can occupy on the other clip too - if (secondary_clip != nullptr) { - long secondary_max_length = GetMaximumEmptySpaceOnClip(secondary_clip); - - maximum_length = qMin(secondary_max_length, maximum_length); - } - - length_field->SetMaximum(maximum_length); -} - -long Transition::GetMaximumEmptySpaceOnClip(Clip *c) -{ - long maximum_transition_length = c->length(); - - Transition* opposite_transition; - - // See if this clip has a transition on the opposite side that we need to account for - if (c->opening_transition.get() == this) { - opposite_transition = c->closing_transition.get(); - } else { - opposite_transition = c->opening_transition.get(); - } - - // If is does, subtract the maximum length by the transition's length - if (opposite_transition != nullptr) { - maximum_transition_length -= opposite_transition->get_true_length(); - } - - return maximum_transition_length; -} +/*** + + 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 "transition.h" + +#include "ui/mainwindow.h" +#include "timeline/clip.h" +#include "timeline/sequence.h" +#include "global/debug.h" +#include "global/clipboard.h" + +#include "effects/internal/crossdissolvetransition.h" +#include "effects/internal/linearfadetransition.h" +#include "effects/internal/exponentialfadetransition.h" +#include "effects/internal/logarithmicfadetransition.h" +#include "effects/internal/cubetransition.h" + +#include "ui/labelslider.h" + +#include "panels/panels.h" +#include "panels/timeline.h" + +#include +#include + +Transition::Transition(Clip *c) : + OldEffectNode(c), + secondary_clip(nullptr) +{ + length_field = new DoubleInput(this, "length", tr("Length"), false, false); + length_field->SetDefault(30); + length_field->SetMinimum(1); + length_field->SetDisplayType(LabelSlider::FrameNumber); + + 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())); +} + +OldEffectNodePtr Transition::copy(Clip *c) { + OldEffectNodePtr node = OldEffectNode::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())); + OldEffectNode::save(stream); +} + +void Transition::set_length(int l) { + length_field->SetValueAt(0, l); +} + +int Transition::get_true_length() { + return length_field->GetValueAt(0).toInt(); +} + +int Transition::get_length() { + if (secondary_clip != nullptr) { + return get_true_length() * 2; + } + return get_true_length(); +} + +Clip* Transition::get_opened_clip() { + if (parent_clip->opening_transition.get() == this) { + return parent_clip; + } else if (secondary_clip != nullptr && secondary_clip->opening_transition.get() == this) { + return secondary_clip; + } + return nullptr; +} + +Clip* Transition::get_closed_clip() { + if (parent_clip->closing_transition.get() == this) { + return parent_clip; + } else if (secondary_clip != nullptr && secondary_clip->closing_transition.get() == this) { + return secondary_clip; + } + return nullptr; +} + +/* +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 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 { + qCritical() << "Invalid transition data"; + QMessageBox::critical(olive::MainWindow, + QCoreApplication::translate("transition", "Invalid transition"), + QCoreApplication::translate("transition", "No candidate for transition '%1'. This transition may be corrupt. Try reinstalling it or Olive.").arg(em->name) + ); + } + return nullptr; +} +*/ + +void Transition::UpdateMaximumLength() +{ + // Get the maximum area this transition can occupy on the clip + long maximum_length = GetMaximumEmptySpaceOnClip(parent_clip); + + // If this clip is a shared transition, get the maximum area this can occupy on the other clip too + if (secondary_clip != nullptr) { + long secondary_max_length = GetMaximumEmptySpaceOnClip(secondary_clip); + + maximum_length = qMin(secondary_max_length, maximum_length); + } + + length_field->SetMaximum(maximum_length); +} + +long Transition::GetMaximumEmptySpaceOnClip(Clip *c) +{ + long maximum_transition_length = c->length(); + + Transition* opposite_transition; + + // See if this clip has a transition on the opposite side that we need to account for + if (c->opening_transition.get() == this) { + opposite_transition = c->closing_transition.get(); + } else { + opposite_transition = c->opening_transition.get(); + } + + // If is does, subtract the maximum length by the transition's length + if (opposite_transition != nullptr) { + maximum_transition_length -= opposite_transition->get_true_length(); + } + + return maximum_transition_length; +} diff --git a/effects/transition.h b/effects/transition.h index d17c1fdcb..6ff842e0d 100644 --- a/effects/transition.h +++ b/effects/transition.h @@ -1,63 +1,63 @@ -/*** - - 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 TRANSITION_H -#define TRANSITION_H - -#include "nodes/oldeffectnode.h" -#include "nodes/inputs.h" - -enum TransitionType { - kTransitionNone, - kTransitionOpening, - kTransitionClosing -}; - -class Transition; -using TransitionPtr = std::shared_ptr; - -class Transition : public OldEffectNode { - Q_OBJECT -public: - Transition(Clip* c); - - virtual OldEffectNodePtr copy(Clip* c) override; - - Clip* secondary_clip; - - virtual void save(QXmlStreamWriter& stream) override; - - void set_length(int l); - int get_true_length(); - int get_length(); - - Clip* get_opened_clip(); - Clip* get_closed_clip(); - - -private: - DoubleInput* length_field; - -private slots: - void UpdateMaximumLength(); - long GetMaximumEmptySpaceOnClip(Clip* c); -}; - -#endif // TRANSITION_H +/*** + + 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 TRANSITION_H +#define TRANSITION_H + +#include "nodes/oldeffectnode.h" +#include "nodes/inputs.h" + +enum TransitionType { + kTransitionNone, + kTransitionOpening, + kTransitionClosing +}; + +class Transition; +using TransitionPtr = std::shared_ptr; + +class Transition : public OldEffectNode { + Q_OBJECT +public: + Transition(Clip* c); + + virtual OldEffectNodePtr copy(Clip* c) override; + + Clip* secondary_clip; + + virtual void save(QXmlStreamWriter& stream) override; + + void set_length(int l); + int get_true_length(); + int get_length(); + + Clip* get_opened_clip(); + Clip* get_closed_clip(); + + +private: + DoubleInput* length_field; + +private slots: + void UpdateMaximumLength(); + long GetMaximumEmptySpaceOnClip(Clip* c); +}; + +#endif // TRANSITION_H diff --git a/global/config.cpp b/global/config.cpp index 14a37c832..82cf4fac9 100644 --- a/global/config.cpp +++ b/global/config.cpp @@ -1,365 +1,365 @@ -/*** - - 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 "config.h" - -#include -#include -#include - -#include "panels/project.h" -#include "panels/panels.h" - -#include "debug.h" - -Config olive::config; -RuntimeConfig olive::runtime_config; - -Config::Config() - : scroll_zooms(false), - edit_tool_selects_links(false), - edit_tool_also_seeks(false), - select_also_seeks(false), - paste_seeks(true), - img_seq_formats("jpg|jpeg|bmp|tiff|tif|psd|png|tga|jp2|gif"), - rectified_waveforms(false), - default_transition_length(30), - timecode_view(olive::kTimecodeDrop), - show_title_safe_area(false), - use_custom_title_safe_ratio(false), - custom_title_safe_ratio(1), - enable_drag_files_to_timeline(true), - autoscale_by_default(false), - recording_mode(2), - enable_seek_to_import(false), - enable_audio_scrubbing(true), - drop_on_media_to_replace(true), - autoscroll(olive::AUTOSCROLL_PAGE_SCROLL), - audio_rate(48000), - hover_focus(false), - project_view_type(olive::PROJECT_VIEW_TREE), - set_name_with_marker(true), - show_project_toolbar(true), - previous_queue_size(3), - previous_queue_type(olive::FRAME_QUEUE_TYPE_FRAMES), - upcoming_queue_size(0.5), - upcoming_queue_type(olive::FRAME_QUEUE_TYPE_SECONDS), - loop(false), - seek_also_selects(false), - auto_seek_to_beginning(true), - effect_textbox_lines(3), - use_software_fallback(false), - center_timeline_timecodes(true), - waveform_resolution(64), - 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), - default_sequence_height(1080), - 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) -{} - -void Config::load(QString path) { - QFile f(path); - if (f.exists() && f.open(QIODevice::ReadOnly)) { - QXmlStreamReader stream(&f); - - while (!stream.atEnd()) { - stream.readNext(); - if (stream.isStartElement()) { - if (stream.name() == "ScrollZooms") { - stream.readNext(); - scroll_zooms = (stream.text() == "1"); - } else if (stream.name() == "InvertTimelineScrollAxes") { - stream.readNext(); - invert_timeline_scroll_axes = (stream.text() == "1"); - } else if (stream.name() == "EditToolSelectsLinks") { - stream.readNext(); - edit_tool_selects_links = (stream.text() == "1"); - } else if (stream.name() == "EditToolAlsoSeeks") { - stream.readNext(); - edit_tool_also_seeks = (stream.text() == "1"); - } else if (stream.name() == "SelectAlsoSeeks") { - stream.readNext(); - select_also_seeks = (stream.text() == "1"); - } else if (stream.name() == "PasteSeeks") { - stream.readNext(); - paste_seeks = (stream.text() == "1"); - } else if (stream.name() == "ImageSequenceFormats") { - stream.readNext(); - img_seq_formats = stream.text().toString(); - } else if (stream.name() == "RectifiedWaveforms") { - stream.readNext(); - rectified_waveforms = (stream.text() == "1"); - } else if (stream.name() == "DefaultTransitionLength") { - stream.readNext(); - default_transition_length = stream.text().toInt(); - } else if (stream.name() == "TimecodeView") { - stream.readNext(); - timecode_view = stream.text().toInt(); - } else if (stream.name() == "ShowTitleSafeArea") { - stream.readNext(); - show_title_safe_area = (stream.text() == "1"); - } else if (stream.name() == "UseCustomTitleSafeRatio") { - stream.readNext(); - use_custom_title_safe_ratio = (stream.text() == "1"); - } else if (stream.name() == "CustomTitleSafeRatio") { - stream.readNext(); - custom_title_safe_ratio = stream.text().toDouble(); - } else if (stream.name() == "EnableDragFilesToTimeline") { - stream.readNext(); - enable_drag_files_to_timeline = (stream.text() == "1");; - } else if (stream.name() == "AutoscaleByDefault") { - stream.readNext(); - autoscale_by_default = (stream.text() == "1"); - } else if (stream.name() == "RecordingMode") { - stream.readNext(); - recording_mode = stream.text().toInt(); - } else if (stream.name() == "EnableSeekToImport") { - stream.readNext(); - enable_seek_to_import = (stream.text() == "1"); - } else if (stream.name() == "AudioScrubbing") { - stream.readNext(); - enable_audio_scrubbing = (stream.text() == "1"); - } else if (stream.name() == "DropFileOnMediaToReplace") { - stream.readNext(); - drop_on_media_to_replace = (stream.text() == "1"); - } else if (stream.name() == "Autoscroll") { - stream.readNext(); - autoscroll = stream.text().toInt(); - } else if (stream.name() == "AudioRate") { - stream.readNext(); - audio_rate = stream.text().toInt(); - } else if (stream.name() == "HoverFocus") { - stream.readNext(); - hover_focus = (stream.text() == "1"); - } else if (stream.name() == "ProjectViewType") { - stream.readNext(); - project_view_type = stream.text().toInt(); - } else if (stream.name() == "SetNameWithMarker") { - stream.readNext(); - set_name_with_marker = (stream.text() == "1"); - } else if (stream.name() == "ShowProjectToolbar") { - stream.readNext(); - show_project_toolbar = (stream.text() == "1"); - } else if (stream.name() == "PreviousFrameQueueSize") { - stream.readNext(); - previous_queue_size = stream.text().toDouble(); - } else if (stream.name() == "PreviousFrameQueueType") { - stream.readNext(); - previous_queue_type = stream.text().toInt(); - } else if (stream.name() == "UpcomingFrameQueueSize") { - stream.readNext(); - upcoming_queue_size = stream.text().toDouble(); - } else if (stream.name() == "UpcomingFrameQueueType") { - stream.readNext(); - upcoming_queue_type = stream.text().toInt(); - } else if (stream.name() == "Loop") { - stream.readNext(); - loop = (stream.text() == "1"); - } else if (stream.name() == "SeekAlsoSelects") { - stream.readNext(); - seek_also_selects = (stream.text() == "1"); - } else if (stream.name() == "AutoSeekToBeginning") { - stream.readNext(); - auto_seek_to_beginning = (stream.text() == "1"); - } else if (stream.name() == "CSSPath") { - stream.readNext(); - css_path = stream.text().toString(); - } else if (stream.name() == "EffectTextboxLines") { - stream.readNext(); - effect_textbox_lines = stream.text().toInt(); - } else if (stream.name() == "UseSoftwareFallback") { - stream.readNext(); - use_software_fallback = (stream.text() == "1"); - } else if (stream.name() == "CenterTimelineTimecodes") { - stream.readNext(); - center_timeline_timecodes = (stream.text() == "1"); - } else if (stream.name() == "PreferredAudioOutput") { - stream.readNext(); - preferred_audio_output = stream.text().toString(); - } else if (stream.name() == "PreferredAudioInput") { - stream.readNext(); - preferred_audio_input = stream.text().toString(); - } else if (stream.name() == "LanguageFile") { - stream.readNext(); - language_file = stream.text().toString(); - } else if (stream.name() == "ThumbnailResolution") { - stream.readNext(); - thumbnail_resolution = stream.text().toInt(); - } else if (stream.name() == "WaveformResolution") { - stream.readNext(); - waveform_resolution = stream.text().toInt(); - } 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()); - } else if (stream.name() == "NativeMenuStyling") { - stream.readNext(); - use_native_menu_styling = (stream.text() == "1"); - } else if (stream.name() == "DefaultSequenceWidth") { - stream.readNext(); - default_sequence_width = stream.text().toInt(); - } else if (stream.name() == "DefaultSequenceHeight") { - stream.readNext(); - default_sequence_height = stream.text().toInt(); - } else if (stream.name() == "DefaultSequenceFrameRate") { - stream.readNext(); - default_sequence_framerate = stream.text().toDouble(); - } else if (stream.name() == "DefaultSequenceAudioFrequency") { - stream.readNext(); - default_sequence_audio_frequency = stream.text().toInt(); - } 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 = static_cast(stream.text().toInt()); - } else if (stream.name() == "ExportBitDepth") { - stream.readNext(); - export_bit_depth = static_cast(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"); - } - } - } - if (stream.hasError()) { - qCritical() << "Error parsing config XML." << stream.errorString(); - } - - f.close(); - } -} - -void Config::save(QString path) { - QFile f(path); - if (!f.open(QIODevice::WriteOnly)) { - qCritical() << "Could not save configuration"; - return; - } - - QXmlStreamWriter stream(&f); - stream.setAutoFormatting(true); - stream.writeStartDocument(); // doc - stream.writeStartElement("Configuration"); // configuration - - stream.writeTextElement("Version", QString::number(olive::kSaveVersion)); - 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)); - stream.writeTextElement("EditToolAlsoSeeks", QString::number(edit_tool_also_seeks)); - stream.writeTextElement("SelectAlsoSeeks", QString::number(select_also_seeks)); - stream.writeTextElement("PasteSeeks", QString::number(paste_seeks)); - stream.writeTextElement("ImageSequenceFormats", img_seq_formats); - stream.writeTextElement("RectifiedWaveforms", QString::number(rectified_waveforms)); - stream.writeTextElement("DefaultTransitionLength", QString::number(default_transition_length)); - stream.writeTextElement("TimecodeView", QString::number(timecode_view)); - stream.writeTextElement("ShowTitleSafeArea", QString::number(show_title_safe_area)); - stream.writeTextElement("UseCustomTitleSafeRatio", QString::number(use_custom_title_safe_ratio)); - stream.writeTextElement("CustomTitleSafeRatio", QString::number(custom_title_safe_ratio)); - stream.writeTextElement("EnableDragFilesToTimeline", QString::number(enable_drag_files_to_timeline)); - stream.writeTextElement("AutoscaleByDefault", QString::number(autoscale_by_default)); - stream.writeTextElement("RecordingMode", QString::number(recording_mode)); - stream.writeTextElement("EnableSeekToImport", QString::number(enable_seek_to_import)); - stream.writeTextElement("AudioScrubbing", QString::number(enable_audio_scrubbing)); - stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); - stream.writeTextElement("Autoscroll", QString::number(autoscroll)); - stream.writeTextElement("AudioRate", QString::number(audio_rate)); - 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.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)); - stream.writeTextElement("UpcomingFrameQueueType", QString::number(upcoming_queue_type)); - stream.writeTextElement("Loop", QString::number(loop)); - stream.writeTextElement("SeekAlsoSelects", QString::number(seek_also_selects)); - stream.writeTextElement("AutoSeekToBeginning", QString::number(auto_seek_to_beginning)); - stream.writeTextElement("CSSPath", css_path); - stream.writeTextElement("EffectTextboxLines", QString::number(effect_textbox_lines)); - stream.writeTextElement("UseSoftwareFallback", QString::number(use_software_fallback)); - stream.writeTextElement("CenterTimelineTimecodes", QString::number(center_timeline_timecodes)); - stream.writeTextElement("PreferredAudioOutput", preferred_audio_output); - stream.writeTextElement("PreferredAudioInput", preferred_audio_input); - stream.writeTextElement("LanguageFile", language_file); - 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)); - stream.writeTextElement("DefaultSequenceHeight", QString::number(default_sequence_height)); - 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 - stream.writeEndDocument(); // doc - f.close(); -} - -RuntimeConfig::RuntimeConfig() : - shaders_are_enabled(true) -{} +/*** + + 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 "config.h" + +#include +#include +#include + +#include "panels/project.h" +#include "panels/panels.h" + +#include "debug.h" + +Config olive::config; +RuntimeConfig olive::runtime_config; + +Config::Config() + : scroll_zooms(false), + edit_tool_selects_links(false), + edit_tool_also_seeks(false), + select_also_seeks(false), + paste_seeks(true), + img_seq_formats("jpg|jpeg|bmp|tiff|tif|psd|png|tga|jp2|gif"), + rectified_waveforms(false), + default_transition_length(30), + timecode_view(olive::kTimecodeDrop), + show_title_safe_area(false), + use_custom_title_safe_ratio(false), + custom_title_safe_ratio(1), + enable_drag_files_to_timeline(true), + autoscale_by_default(false), + recording_mode(2), + enable_seek_to_import(false), + enable_audio_scrubbing(true), + drop_on_media_to_replace(true), + autoscroll(olive::AUTOSCROLL_PAGE_SCROLL), + audio_rate(48000), + hover_focus(false), + project_view_type(olive::PROJECT_VIEW_TREE), + set_name_with_marker(true), + show_project_toolbar(true), + previous_queue_size(3), + previous_queue_type(olive::FRAME_QUEUE_TYPE_FRAMES), + upcoming_queue_size(0.5), + upcoming_queue_type(olive::FRAME_QUEUE_TYPE_SECONDS), + loop(false), + seek_also_selects(false), + auto_seek_to_beginning(true), + effect_textbox_lines(3), + use_software_fallback(false), + center_timeline_timecodes(true), + waveform_resolution(64), + 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), + default_sequence_height(1080), + 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) +{} + +void Config::load(QString path) { + QFile f(path); + if (f.exists() && f.open(QIODevice::ReadOnly)) { + QXmlStreamReader stream(&f); + + while (!stream.atEnd()) { + stream.readNext(); + if (stream.isStartElement()) { + if (stream.name() == "ScrollZooms") { + stream.readNext(); + scroll_zooms = (stream.text() == "1"); + } else if (stream.name() == "InvertTimelineScrollAxes") { + stream.readNext(); + invert_timeline_scroll_axes = (stream.text() == "1"); + } else if (stream.name() == "EditToolSelectsLinks") { + stream.readNext(); + edit_tool_selects_links = (stream.text() == "1"); + } else if (stream.name() == "EditToolAlsoSeeks") { + stream.readNext(); + edit_tool_also_seeks = (stream.text() == "1"); + } else if (stream.name() == "SelectAlsoSeeks") { + stream.readNext(); + select_also_seeks = (stream.text() == "1"); + } else if (stream.name() == "PasteSeeks") { + stream.readNext(); + paste_seeks = (stream.text() == "1"); + } else if (stream.name() == "ImageSequenceFormats") { + stream.readNext(); + img_seq_formats = stream.text().toString(); + } else if (stream.name() == "RectifiedWaveforms") { + stream.readNext(); + rectified_waveforms = (stream.text() == "1"); + } else if (stream.name() == "DefaultTransitionLength") { + stream.readNext(); + default_transition_length = stream.text().toInt(); + } else if (stream.name() == "TimecodeView") { + stream.readNext(); + timecode_view = stream.text().toInt(); + } else if (stream.name() == "ShowTitleSafeArea") { + stream.readNext(); + show_title_safe_area = (stream.text() == "1"); + } else if (stream.name() == "UseCustomTitleSafeRatio") { + stream.readNext(); + use_custom_title_safe_ratio = (stream.text() == "1"); + } else if (stream.name() == "CustomTitleSafeRatio") { + stream.readNext(); + custom_title_safe_ratio = stream.text().toDouble(); + } else if (stream.name() == "EnableDragFilesToTimeline") { + stream.readNext(); + enable_drag_files_to_timeline = (stream.text() == "1");; + } else if (stream.name() == "AutoscaleByDefault") { + stream.readNext(); + autoscale_by_default = (stream.text() == "1"); + } else if (stream.name() == "RecordingMode") { + stream.readNext(); + recording_mode = stream.text().toInt(); + } else if (stream.name() == "EnableSeekToImport") { + stream.readNext(); + enable_seek_to_import = (stream.text() == "1"); + } else if (stream.name() == "AudioScrubbing") { + stream.readNext(); + enable_audio_scrubbing = (stream.text() == "1"); + } else if (stream.name() == "DropFileOnMediaToReplace") { + stream.readNext(); + drop_on_media_to_replace = (stream.text() == "1"); + } else if (stream.name() == "Autoscroll") { + stream.readNext(); + autoscroll = stream.text().toInt(); + } else if (stream.name() == "AudioRate") { + stream.readNext(); + audio_rate = stream.text().toInt(); + } else if (stream.name() == "HoverFocus") { + stream.readNext(); + hover_focus = (stream.text() == "1"); + } else if (stream.name() == "ProjectViewType") { + stream.readNext(); + project_view_type = stream.text().toInt(); + } else if (stream.name() == "SetNameWithMarker") { + stream.readNext(); + set_name_with_marker = (stream.text() == "1"); + } else if (stream.name() == "ShowProjectToolbar") { + stream.readNext(); + show_project_toolbar = (stream.text() == "1"); + } else if (stream.name() == "PreviousFrameQueueSize") { + stream.readNext(); + previous_queue_size = stream.text().toDouble(); + } else if (stream.name() == "PreviousFrameQueueType") { + stream.readNext(); + previous_queue_type = stream.text().toInt(); + } else if (stream.name() == "UpcomingFrameQueueSize") { + stream.readNext(); + upcoming_queue_size = stream.text().toDouble(); + } else if (stream.name() == "UpcomingFrameQueueType") { + stream.readNext(); + upcoming_queue_type = stream.text().toInt(); + } else if (stream.name() == "Loop") { + stream.readNext(); + loop = (stream.text() == "1"); + } else if (stream.name() == "SeekAlsoSelects") { + stream.readNext(); + seek_also_selects = (stream.text() == "1"); + } else if (stream.name() == "AutoSeekToBeginning") { + stream.readNext(); + auto_seek_to_beginning = (stream.text() == "1"); + } else if (stream.name() == "CSSPath") { + stream.readNext(); + css_path = stream.text().toString(); + } else if (stream.name() == "EffectTextboxLines") { + stream.readNext(); + effect_textbox_lines = stream.text().toInt(); + } else if (stream.name() == "UseSoftwareFallback") { + stream.readNext(); + use_software_fallback = (stream.text() == "1"); + } else if (stream.name() == "CenterTimelineTimecodes") { + stream.readNext(); + center_timeline_timecodes = (stream.text() == "1"); + } else if (stream.name() == "PreferredAudioOutput") { + stream.readNext(); + preferred_audio_output = stream.text().toString(); + } else if (stream.name() == "PreferredAudioInput") { + stream.readNext(); + preferred_audio_input = stream.text().toString(); + } else if (stream.name() == "LanguageFile") { + stream.readNext(); + language_file = stream.text().toString(); + } else if (stream.name() == "ThumbnailResolution") { + stream.readNext(); + thumbnail_resolution = stream.text().toInt(); + } else if (stream.name() == "WaveformResolution") { + stream.readNext(); + waveform_resolution = stream.text().toInt(); + } 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()); + } else if (stream.name() == "NativeMenuStyling") { + stream.readNext(); + use_native_menu_styling = (stream.text() == "1"); + } else if (stream.name() == "DefaultSequenceWidth") { + stream.readNext(); + default_sequence_width = stream.text().toInt(); + } else if (stream.name() == "DefaultSequenceHeight") { + stream.readNext(); + default_sequence_height = stream.text().toInt(); + } else if (stream.name() == "DefaultSequenceFrameRate") { + stream.readNext(); + default_sequence_framerate = stream.text().toDouble(); + } else if (stream.name() == "DefaultSequenceAudioFrequency") { + stream.readNext(); + default_sequence_audio_frequency = stream.text().toInt(); + } 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 = static_cast(stream.text().toInt()); + } else if (stream.name() == "ExportBitDepth") { + stream.readNext(); + export_bit_depth = static_cast(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"); + } + } + } + if (stream.hasError()) { + qCritical() << "Error parsing config XML." << stream.errorString(); + } + + f.close(); + } +} + +void Config::save(QString path) { + QFile f(path); + if (!f.open(QIODevice::WriteOnly)) { + qCritical() << "Could not save configuration"; + return; + } + + QXmlStreamWriter stream(&f); + stream.setAutoFormatting(true); + stream.writeStartDocument(); // doc + stream.writeStartElement("Configuration"); // configuration + + stream.writeTextElement("Version", QString::number(olive::kSaveVersion)); + 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)); + stream.writeTextElement("EditToolAlsoSeeks", QString::number(edit_tool_also_seeks)); + stream.writeTextElement("SelectAlsoSeeks", QString::number(select_also_seeks)); + stream.writeTextElement("PasteSeeks", QString::number(paste_seeks)); + stream.writeTextElement("ImageSequenceFormats", img_seq_formats); + stream.writeTextElement("RectifiedWaveforms", QString::number(rectified_waveforms)); + stream.writeTextElement("DefaultTransitionLength", QString::number(default_transition_length)); + stream.writeTextElement("TimecodeView", QString::number(timecode_view)); + stream.writeTextElement("ShowTitleSafeArea", QString::number(show_title_safe_area)); + stream.writeTextElement("UseCustomTitleSafeRatio", QString::number(use_custom_title_safe_ratio)); + stream.writeTextElement("CustomTitleSafeRatio", QString::number(custom_title_safe_ratio)); + stream.writeTextElement("EnableDragFilesToTimeline", QString::number(enable_drag_files_to_timeline)); + stream.writeTextElement("AutoscaleByDefault", QString::number(autoscale_by_default)); + stream.writeTextElement("RecordingMode", QString::number(recording_mode)); + stream.writeTextElement("EnableSeekToImport", QString::number(enable_seek_to_import)); + stream.writeTextElement("AudioScrubbing", QString::number(enable_audio_scrubbing)); + stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); + stream.writeTextElement("Autoscroll", QString::number(autoscroll)); + stream.writeTextElement("AudioRate", QString::number(audio_rate)); + 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.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)); + stream.writeTextElement("UpcomingFrameQueueType", QString::number(upcoming_queue_type)); + stream.writeTextElement("Loop", QString::number(loop)); + stream.writeTextElement("SeekAlsoSelects", QString::number(seek_also_selects)); + stream.writeTextElement("AutoSeekToBeginning", QString::number(auto_seek_to_beginning)); + stream.writeTextElement("CSSPath", css_path); + stream.writeTextElement("EffectTextboxLines", QString::number(effect_textbox_lines)); + stream.writeTextElement("UseSoftwareFallback", QString::number(use_software_fallback)); + stream.writeTextElement("CenterTimelineTimecodes", QString::number(center_timeline_timecodes)); + stream.writeTextElement("PreferredAudioOutput", preferred_audio_output); + stream.writeTextElement("PreferredAudioInput", preferred_audio_input); + stream.writeTextElement("LanguageFile", language_file); + 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)); + stream.writeTextElement("DefaultSequenceHeight", QString::number(default_sequence_height)); + 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 + stream.writeEndDocument(); // doc + f.close(); +} + +RuntimeConfig::RuntimeConfig() : + shaders_are_enabled(true) +{} diff --git a/global/config.h b/global/config.h index ddb6b12cd..8b9e31c13 100644 --- a/global/config.h +++ b/global/config.h @@ -1,681 +1,681 @@ -/*** - - 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 CONFIG_H -#define CONFIG_H - -#include - -#include "ui/styling.h" -#include "timeline/timelinetools.h" -#include "rendering/pixelformats.h" - -namespace olive { - /** - * @brief Version identifier for saved projects - * - * This constant is used to identify what version of Olive a project file was saved with. Every project file - * is saved with the current version number and the version is checked whenever an Olive project is loaded to - * determine how compatible it'll be with the current version. - * - * Sometimes this version identifier is used to invoke backwards compatibility in order to keep older project files - * able to load, but in this early rapidly developing stage, often backwards compatibility is abandoned. Ideally - * in the future, a class should be made that's able to "convert" an older project into one that the current - * loading system understands (so that the loading system doesn't get too bloated with backwards compatibility - * functions). - */ - const int kSaveVersion = 190219; // YYMMDD - - /** - * @brief Minimum project version that this version of Olive can open - * - * When loading a project, the project's version number is actually checked whether it is somewhere between - * kSaveVersion and this value (inclusive). This is used if the current version of Olive contains backwards - * compatibility functionality for older project versions, and is bumped up if such backwards compatibility is - * ever removed. - * - * As stated in kSaveVersion documentation, ideally in the future, a system would be in place to account for all - * project version differences and bring them up to date for the current loading algorithm. This means ideally, this - * constant stays the same forever, but in this early stage it's not strictly necessary. - */ - const int kMinimumSaveVersion = 190219; // lowest compatible project version - - /** - * @brief The TimecodeType enum - * - * Frame numbers can be displayed in various different ways. The timecode_to_frame() and frame_to_timecode() - * functions (which should be used for all frame <-> timecode conversions) respond to the timecode display type - * set in Config::timecode_view corresponding to a value from this enum. - */ - enum TimecodeType { - /** Show frame number as a drop-frame timecode */ - kTimecodeDrop, - - /** Show frame number as a non-drop-frame timecode */ - kTimecodeNonDrop, - - /** Show frame number as-is with no modifications */ - kTimecodeFrames, - - /** Show frame number as milliseconds */ - kTimecodeMilliseconds - }; - - /** - * @brief The RecordingMode enum - * - * Olive currently supports recording mono or stereo and gives users the option of which mode to use when - * recording audio in-app. Audio recording responds to Config::recording_mode to a value from this enum. - */ - enum RecordingMode { - /** Record all audio in mono */ - RECORD_MODE_MONO, - - /** Record all audio in stereo */ - RECORD_MODE_STEREO - }; - - /** - * @brief The AutoScrollMode enum - * - * The Timeline in Olive can automatically scroll to follow the playhead when the sequence is playing. - * The Timeline will respond to Config::autoscroll set to a value from this enum. - */ - enum AutoScrollMode { - /** Don't auto-scroll, scrolling will not follow the playhead */ - AUTOSCROLL_NO_SCROLL, - - /** Page auto-scroll (default), if the playhead goes off-screen while playing, the scroll will jump ahead - * one "page" to follow it */ - AUTOSCROLL_PAGE_SCROLL, - - /** Smooth auto-scroll, Olive will scroll to keep the playhead in the center of the screen at all times while - * playing */ - AUTOSCROLL_SMOOTH_SCROLL - }; - - /** - * @brief The ProjectView enum - * - * The media in the Project panel can be displayed as a tree hierarchy or as an icon view. The Project panel - * responds to Config::project_view_type set to a value from this enum. - */ - enum ProjectView { - /** Display project media in tree hierarchy */ - PROJECT_VIEW_TREE, - - /** Display project media in icon browser */ - PROJECT_VIEW_ICON, - - /** Display project media in list browser */ - PROJECT_VIEW_LIST - }; - - /** - * @brief The FrameQueueType enum - * - * Olive keeps a "frame queue" in memory to allow smoother playback/seeking. In order to give users control over - * the amount of memory consumption vs. playback performance, they can control how many frames are cached into - * memory. For extra fidelity, they can choose this value as a metric of either frames or seconds. - * - * The playback engine (playback/playback.h) responds to both Config::previous_queue_type and - * Config::upcoming_queue_type set to a value from this enum. - */ - enum FrameQueueType { - /** Queue size value is in frames */ - FRAME_QUEUE_TYPE_FRAMES, - - /** Queue size value is in seconds */ - FRAME_QUEUE_TYPE_SECONDS - }; -} - -/** - * @brief The Config struct - * - * This struct handles any configuration that should persist between restarting Olive. It contains several variables - * as well as functions that load and save all the variables to file. - */ -struct Config { - /** - * @brief Config Constructor - * - * Sets all configuration variables to their defaults. - */ - Config(); - - /** - * @brief The scroll wheel zooms rather than scrolls - * - * **TRUE** if the scroll wheel should zoom in and out rather than scroll up and down. - * The Control key temporarily toggles this setting. - */ - bool scroll_zooms; - - /** - * @brief Edit tool selects links - * - * **TRUE** if the edit tool should also select links when the user selects a clip. - */ - bool edit_tool_selects_links; - - /** - * @brief Edit tool also seeks - * - * **TRUE** if using the edit tool should also seek the sequence's playhead - */ - bool edit_tool_also_seeks; - - /** - * @brief Selecting also seeks - * - * **TRUE** if the playhead should automatically seek to the start of any clip that gets selected - */ - bool select_also_seeks; - - /** - * @brief Paste also seeks - * - * **TRUE** if the playhead should seek to the end of clips that are pasted - */ - bool paste_seeks; - - /** - * @brief Image sequence formats - * - * A '|' separated list of file extensions that Olive will perform an image sequence test heuristic on when importing - */ - QString img_seq_formats; - - /** - * @brief Use rectified waveforms - * - * **TRUE** if Olive should display waveforms as "rectified". Rectified waveforms start at the bottom rather than - * from the middle. - */ - bool rectified_waveforms; - - /** - * @brief Default transition length - * - * The default transition length used when making a transition without the transition tool - */ - int default_transition_length; - - /** - * @brief Timecode display mode - * - * The display mode with which timecode_to_frame() and frame_to_timecode() will use to convert frame numbers to - * more human-readable values. - * - * Set to a member of enum TimecodeType. - */ - int timecode_view; - - /** - * @brief Show title/action safe area - * - * **TRUE** if the title/action safe area should be shown on the Viewer. - */ - bool show_title_safe_area; - - /** - * @brief Use custom title/action safe area aspect ratio - * - * **TRUE** if the title/action save area should use a custom aspect ratio - */ - bool use_custom_title_safe_ratio; - - /** - * @brief Custom title/action safe area aspect ratio - * - * If Config::use_custom_title_safe_ratio is true, this is the aspect ratio to use. - * - * Set to the result of an aspect ratio division (i.e. for 4:3 set to 1.333333 (4.0 / 3.0) - */ - double custom_title_safe_ratio; - - /** - * @brief Enable dragging files outside Olive directly into the Timeline - * - * **TRUE** if the Timeline should respond to files dropped from outside Olive. - */ - bool enable_drag_files_to_timeline; - - /** - * @brief Auto-scale by default - * - * **TRUE** if clips imported into the timeline should have Clip::autoscale **TRUE** by default. If a Clip is - * smaller or larger than the Sequence, auto-scale will automatically resize it to fit to the Sequence boundaries. - */ - bool autoscale_by_default; - - /** - * @brief Recording mode/channel layout - * - * When recording audio within Olive, use this mode/channel layout (e.g. mono or stereo). - * - * Set to a member of enum RecordingMode. - */ - int recording_mode; - - /** - * @brief Enable seek to import - * - * **TRUE** if the playhead should automatically seek to any newly imported clips - */ - bool enable_seek_to_import; - - /** - * @brief Enable audio scrubbing - * - * **TRUE** if audio should "scrub" as the user drags the playhead around - */ - bool enable_audio_scrubbing; - - /** - * @brief Enable drop on media to replace - * - * **TRUE** if dropping a file from outside Olive onto a media item in the Project panel should prompt the user - * whether the dropped file should replace the media item that the file was dropped on. - */ - bool drop_on_media_to_replace; - - /** - * @brief Auto-scroll mode - * - * The Timeline behavior regarding scrolling to keep the playhead within view while a Sequence is playing. - * - * Set to a member of enum AutoScrollMode. - */ - int autoscroll; - - /** - * @brief Current audio sample rate - * - * The sample rate to set the audio output device to. Also used as the value to resample audio to during playback - * (but not during rendering). - */ - int audio_rate; - - /** - * @brief Enable hover focus - * - * Default behavior is panels are focused (and therefore respond to certain keyboard shortcuts)when they are clicked - * on, but Olive also supports panels being considered "focused" if the mouse is hovered over them. - * - * **TRUE** to enable hover focus mode. - */ - bool hover_focus; - - /** - * @brief Project view type - * - * Whether to show media in the Project panel as a tree hierarchy or as a browser of icons. - * - * Set to a member of enum ProjectView. - */ - int project_view_type; - - /** - * @brief Ask for a marker name when setting a marker - * - * **TRUE** if Olive should ask the user to name a marker when setting one. **FALSE** if markers should just be - * created without asking. - */ - bool set_name_with_marker; - - /** - * @brief Show the project toolbar - * - * Olive has an optional toolbar for the Project panel. - * - * Set to **TRUE** to show it. - */ - bool show_project_toolbar; - - /** - * @brief Previous frame queue size - * - * Olive caches frames in memory to improve playback performance (see enum FrameQueueType documentation for more - * details). This variable states how many frames to keep in memory prior to the playhead (in most cases, frames that - * have already been played, but are kept in memory in case the user wants to backtrack at any time). - * - * This value corresponds to Config::previous_queue_type. - */ - double previous_queue_size; - - /** - * @brief Previous frame queue type - * - * The metric of which Config::previous_queue_size is using. For example, if Config::previous_queue_size is - * 3, this variable states whether that is 3 frames or 3 seconds. - * - * Set to a member of enum FrameQueueType. - */ - int previous_queue_type; - - /** - * @brief Upcoming frame queue size - * - * Olive caches frames in memory to improve playback performance (see enum FrameQueueType documentation for more - * details). This variable states how many upcoming frames are stored in memory. Generally this value will be higher - * than Config::previous_queue_size since the user will be playing forwards most of the time. - * - * This value corresponds to Config::upcoming_queue_type. - */ - double upcoming_queue_size; - - /** - * @brief Upcoming frame queue type - * - * The metric of which Config::upcoming_queue_size is using. For example, if Config::upcoming_queue_size is - * 3, this variable states whether that is 3 frames or 3 seconds. - * - * Set to a member of enum FrameQueueType. - */ - int upcoming_queue_type; - - /** - * @brief Loop - * - * If an in/out point are set on the Sequence (Sequence::using_workarea is **TRUE**), set this to **TRUE** if Olive - * should rewind to the in point and start playing again after it reaches the out point repeatedly until the user - * pauses. - */ - bool loop; - - /** - * @brief Seeking also selects - * - * Olive supports automatically selecting clips that the playhead is currently touching for a more efficient workflow. - * - * **TRUE** if this mode should be enabled. - */ - bool seek_also_selects; - - /** - * @brief Automatically seek to the beginning of a sequence if the user plays beyond the end of it - * - * TRUE if this behavior should be enabled. - */ - bool auto_seek_to_beginning; - - /** - * @brief CSS Path - * - * The URL to a CSS file if the user has loaded a custom stylesheet in. **EMPTY** if the user has not set a - * stylesheet. - */ - QString css_path; - - /** - * @brief Number of lines that an Effect's textbox has - * - * The height of a Effect's textbox field in terms of lines. - * - * Set to a value >= 1 - */ - int effect_textbox_lines; - - /** - * @brief Use software fallbacks when possible - * - * Olive uses a lot of OpenGL-based hardware acceleration for performance. Some older hardware has difficulty - * supporting this functionality, so some of it has software-based (not hardware accelerated) fallbacks for these - * users. - * - * **TRUE** if Olive should prefer software fallbacks to hardware acceleration when they're available. - */ - bool use_software_fallback; - - /** - * @brief Center Timeline timecodes - * - * By default, Olive shows timecodes in the TimelineHeader centered to the corresponding frame point. This may not - * always be desirable as, for example, this forces the initial 00:00:00;00 timecode's left half to be cut off. - * Olive supports aligning the timecode to the right of the frame rather than the center to address this. - */ - bool center_timeline_timecodes; - - /** - * @brief Preferred audio output device - * - * Sets the audio device Olive should use to output audio to. - * - * Set to the name of the audio device or **EMPTY** to try using the default. - */ - QString preferred_audio_output; - - /** - * @brief Preferred audio input device - * - * Sets the audio device Olive should use to input audio from. - * - * Set to the name of the audio device or **EMPTY** to try using the default. - */ - QString preferred_audio_input; - - /** - * @brief Language/translation file - * - * Sets the translation file to load to display Olive in a different language. - * - * Set to the URL of the language file to load, or **EMPTY** to use default en-US language. - */ - QString language_file; - - /** - * @brief Waveform resolution - * - * Sets how detailed the waveforms should be in the Timeline. Higher value = more detail. - * - * Specifically sets how many samples per second should be "cached" for preview. If the waveforms are too blocky, - * set this higher. If Timeline performance is slow, set this lower. - */ - int waveform_resolution; - - /** - * @brief Thumbnail resolution - * - * The vertical pixel height to use for generating thumbnails. - */ - int thumbnail_resolution; - - /** - * @brief Add default effects to clips - * - * **TRUE** if new clips imported into the Timeline should have a set of default effects (TransformEffect, - * VolumeEffect, and PanEffect) added to them by default. - */ - bool add_default_effects_to_clips; - - /** - * @brief Invert Timeline scroll axes - * - * **TRUE** if scrolling vertically on the Timeline should scroll it horizontally - */ - 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. - * - * Set to a member of olive::styling::Style. - */ - olive::styling::Style style; - - /** - * @brief Use native menu styling - * - * Use native styling on menus rather than cross-platform Fusion. - */ - bool use_native_menu_styling; - - /** - * @brief Default Sequence video width - */ - int default_sequence_width; - - /** - * @brief Default Sequence video height - */ - int default_sequence_height; - - /** - * @brief Default Sequence video frame rate - */ - double default_sequence_framerate; - - /** - * @brief Default Sequence audio frequency - */ - int default_sequence_audio_frequency; - - /** - * @brief Default Sequence audio channel layout - */ - int default_sequence_audio_channel_layout; - - /** - * @brief Playback bit depth (an index of olive::rendering::bit_depths) - */ - olive::PixelFormat playback_bit_depth; - - /** - * @brief Export bit depth (an index of olive::rendering::bit_depths) - */ - olive::PixelFormat 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 - */ - bool locked_panels; - - /** - * @brief Load config from file - * - * Load configuration parameters from file - * - * @param path - * - * URL to the configuration file to load - */ - void load(QString path); - - /** - * @brief Save config to file - * - * Save current configuration parameters to file - * - * @param path - * - * URL to save the configuration file to. - */ - void save(QString path); -}; - -/** - * @brief The RuntimeConfig struct - * - * This struct handles any configuration that's set as a command-line argument to Olive, and shouldn't be persistent - * between restarts of Olive. - */ -struct RuntimeConfig { - /** - * @brief RuntimeConfig Constructor - * - * Sets default runtime configuration - */ - RuntimeConfig(); - - /** - * @brief Enable shaders - * - * Debugging tool. Set to **FALSE** to bypass OpenGL shaders. - */ - bool shaders_are_enabled; - - /** - * @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 config; -extern RuntimeConfig runtime_config; -} - -#endif // CONFIG_H +/*** + + 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 CONFIG_H +#define CONFIG_H + +#include + +#include "ui/styling.h" +#include "timeline/timelinetools.h" +#include "rendering/pixelformats.h" + +namespace olive { + /** + * @brief Version identifier for saved projects + * + * This constant is used to identify what version of Olive a project file was saved with. Every project file + * is saved with the current version number and the version is checked whenever an Olive project is loaded to + * determine how compatible it'll be with the current version. + * + * Sometimes this version identifier is used to invoke backwards compatibility in order to keep older project files + * able to load, but in this early rapidly developing stage, often backwards compatibility is abandoned. Ideally + * in the future, a class should be made that's able to "convert" an older project into one that the current + * loading system understands (so that the loading system doesn't get too bloated with backwards compatibility + * functions). + */ + const int kSaveVersion = 190219; // YYMMDD + + /** + * @brief Minimum project version that this version of Olive can open + * + * When loading a project, the project's version number is actually checked whether it is somewhere between + * kSaveVersion and this value (inclusive). This is used if the current version of Olive contains backwards + * compatibility functionality for older project versions, and is bumped up if such backwards compatibility is + * ever removed. + * + * As stated in kSaveVersion documentation, ideally in the future, a system would be in place to account for all + * project version differences and bring them up to date for the current loading algorithm. This means ideally, this + * constant stays the same forever, but in this early stage it's not strictly necessary. + */ + const int kMinimumSaveVersion = 190219; // lowest compatible project version + + /** + * @brief The TimecodeType enum + * + * Frame numbers can be displayed in various different ways. The timecode_to_frame() and frame_to_timecode() + * functions (which should be used for all frame <-> timecode conversions) respond to the timecode display type + * set in Config::timecode_view corresponding to a value from this enum. + */ + enum TimecodeType { + /** Show frame number as a drop-frame timecode */ + kTimecodeDrop, + + /** Show frame number as a non-drop-frame timecode */ + kTimecodeNonDrop, + + /** Show frame number as-is with no modifications */ + kTimecodeFrames, + + /** Show frame number as milliseconds */ + kTimecodeMilliseconds + }; + + /** + * @brief The RecordingMode enum + * + * Olive currently supports recording mono or stereo and gives users the option of which mode to use when + * recording audio in-app. Audio recording responds to Config::recording_mode to a value from this enum. + */ + enum RecordingMode { + /** Record all audio in mono */ + RECORD_MODE_MONO, + + /** Record all audio in stereo */ + RECORD_MODE_STEREO + }; + + /** + * @brief The AutoScrollMode enum + * + * The Timeline in Olive can automatically scroll to follow the playhead when the sequence is playing. + * The Timeline will respond to Config::autoscroll set to a value from this enum. + */ + enum AutoScrollMode { + /** Don't auto-scroll, scrolling will not follow the playhead */ + AUTOSCROLL_NO_SCROLL, + + /** Page auto-scroll (default), if the playhead goes off-screen while playing, the scroll will jump ahead + * one "page" to follow it */ + AUTOSCROLL_PAGE_SCROLL, + + /** Smooth auto-scroll, Olive will scroll to keep the playhead in the center of the screen at all times while + * playing */ + AUTOSCROLL_SMOOTH_SCROLL + }; + + /** + * @brief The ProjectView enum + * + * The media in the Project panel can be displayed as a tree hierarchy or as an icon view. The Project panel + * responds to Config::project_view_type set to a value from this enum. + */ + enum ProjectView { + /** Display project media in tree hierarchy */ + PROJECT_VIEW_TREE, + + /** Display project media in icon browser */ + PROJECT_VIEW_ICON, + + /** Display project media in list browser */ + PROJECT_VIEW_LIST + }; + + /** + * @brief The FrameQueueType enum + * + * Olive keeps a "frame queue" in memory to allow smoother playback/seeking. In order to give users control over + * the amount of memory consumption vs. playback performance, they can control how many frames are cached into + * memory. For extra fidelity, they can choose this value as a metric of either frames or seconds. + * + * The playback engine (playback/playback.h) responds to both Config::previous_queue_type and + * Config::upcoming_queue_type set to a value from this enum. + */ + enum FrameQueueType { + /** Queue size value is in frames */ + FRAME_QUEUE_TYPE_FRAMES, + + /** Queue size value is in seconds */ + FRAME_QUEUE_TYPE_SECONDS + }; +} + +/** + * @brief The Config struct + * + * This struct handles any configuration that should persist between restarting Olive. It contains several variables + * as well as functions that load and save all the variables to file. + */ +struct Config { + /** + * @brief Config Constructor + * + * Sets all configuration variables to their defaults. + */ + Config(); + + /** + * @brief The scroll wheel zooms rather than scrolls + * + * **TRUE** if the scroll wheel should zoom in and out rather than scroll up and down. + * The Control key temporarily toggles this setting. + */ + bool scroll_zooms; + + /** + * @brief Edit tool selects links + * + * **TRUE** if the edit tool should also select links when the user selects a clip. + */ + bool edit_tool_selects_links; + + /** + * @brief Edit tool also seeks + * + * **TRUE** if using the edit tool should also seek the sequence's playhead + */ + bool edit_tool_also_seeks; + + /** + * @brief Selecting also seeks + * + * **TRUE** if the playhead should automatically seek to the start of any clip that gets selected + */ + bool select_also_seeks; + + /** + * @brief Paste also seeks + * + * **TRUE** if the playhead should seek to the end of clips that are pasted + */ + bool paste_seeks; + + /** + * @brief Image sequence formats + * + * A '|' separated list of file extensions that Olive will perform an image sequence test heuristic on when importing + */ + QString img_seq_formats; + + /** + * @brief Use rectified waveforms + * + * **TRUE** if Olive should display waveforms as "rectified". Rectified waveforms start at the bottom rather than + * from the middle. + */ + bool rectified_waveforms; + + /** + * @brief Default transition length + * + * The default transition length used when making a transition without the transition tool + */ + int default_transition_length; + + /** + * @brief Timecode display mode + * + * The display mode with which timecode_to_frame() and frame_to_timecode() will use to convert frame numbers to + * more human-readable values. + * + * Set to a member of enum TimecodeType. + */ + int timecode_view; + + /** + * @brief Show title/action safe area + * + * **TRUE** if the title/action safe area should be shown on the Viewer. + */ + bool show_title_safe_area; + + /** + * @brief Use custom title/action safe area aspect ratio + * + * **TRUE** if the title/action save area should use a custom aspect ratio + */ + bool use_custom_title_safe_ratio; + + /** + * @brief Custom title/action safe area aspect ratio + * + * If Config::use_custom_title_safe_ratio is true, this is the aspect ratio to use. + * + * Set to the result of an aspect ratio division (i.e. for 4:3 set to 1.333333 (4.0 / 3.0) + */ + double custom_title_safe_ratio; + + /** + * @brief Enable dragging files outside Olive directly into the Timeline + * + * **TRUE** if the Timeline should respond to files dropped from outside Olive. + */ + bool enable_drag_files_to_timeline; + + /** + * @brief Auto-scale by default + * + * **TRUE** if clips imported into the timeline should have Clip::autoscale **TRUE** by default. If a Clip is + * smaller or larger than the Sequence, auto-scale will automatically resize it to fit to the Sequence boundaries. + */ + bool autoscale_by_default; + + /** + * @brief Recording mode/channel layout + * + * When recording audio within Olive, use this mode/channel layout (e.g. mono or stereo). + * + * Set to a member of enum RecordingMode. + */ + int recording_mode; + + /** + * @brief Enable seek to import + * + * **TRUE** if the playhead should automatically seek to any newly imported clips + */ + bool enable_seek_to_import; + + /** + * @brief Enable audio scrubbing + * + * **TRUE** if audio should "scrub" as the user drags the playhead around + */ + bool enable_audio_scrubbing; + + /** + * @brief Enable drop on media to replace + * + * **TRUE** if dropping a file from outside Olive onto a media item in the Project panel should prompt the user + * whether the dropped file should replace the media item that the file was dropped on. + */ + bool drop_on_media_to_replace; + + /** + * @brief Auto-scroll mode + * + * The Timeline behavior regarding scrolling to keep the playhead within view while a Sequence is playing. + * + * Set to a member of enum AutoScrollMode. + */ + int autoscroll; + + /** + * @brief Current audio sample rate + * + * The sample rate to set the audio output device to. Also used as the value to resample audio to during playback + * (but not during rendering). + */ + int audio_rate; + + /** + * @brief Enable hover focus + * + * Default behavior is panels are focused (and therefore respond to certain keyboard shortcuts)when they are clicked + * on, but Olive also supports panels being considered "focused" if the mouse is hovered over them. + * + * **TRUE** to enable hover focus mode. + */ + bool hover_focus; + + /** + * @brief Project view type + * + * Whether to show media in the Project panel as a tree hierarchy or as a browser of icons. + * + * Set to a member of enum ProjectView. + */ + int project_view_type; + + /** + * @brief Ask for a marker name when setting a marker + * + * **TRUE** if Olive should ask the user to name a marker when setting one. **FALSE** if markers should just be + * created without asking. + */ + bool set_name_with_marker; + + /** + * @brief Show the project toolbar + * + * Olive has an optional toolbar for the Project panel. + * + * Set to **TRUE** to show it. + */ + bool show_project_toolbar; + + /** + * @brief Previous frame queue size + * + * Olive caches frames in memory to improve playback performance (see enum FrameQueueType documentation for more + * details). This variable states how many frames to keep in memory prior to the playhead (in most cases, frames that + * have already been played, but are kept in memory in case the user wants to backtrack at any time). + * + * This value corresponds to Config::previous_queue_type. + */ + double previous_queue_size; + + /** + * @brief Previous frame queue type + * + * The metric of which Config::previous_queue_size is using. For example, if Config::previous_queue_size is + * 3, this variable states whether that is 3 frames or 3 seconds. + * + * Set to a member of enum FrameQueueType. + */ + int previous_queue_type; + + /** + * @brief Upcoming frame queue size + * + * Olive caches frames in memory to improve playback performance (see enum FrameQueueType documentation for more + * details). This variable states how many upcoming frames are stored in memory. Generally this value will be higher + * than Config::previous_queue_size since the user will be playing forwards most of the time. + * + * This value corresponds to Config::upcoming_queue_type. + */ + double upcoming_queue_size; + + /** + * @brief Upcoming frame queue type + * + * The metric of which Config::upcoming_queue_size is using. For example, if Config::upcoming_queue_size is + * 3, this variable states whether that is 3 frames or 3 seconds. + * + * Set to a member of enum FrameQueueType. + */ + int upcoming_queue_type; + + /** + * @brief Loop + * + * If an in/out point are set on the Sequence (Sequence::using_workarea is **TRUE**), set this to **TRUE** if Olive + * should rewind to the in point and start playing again after it reaches the out point repeatedly until the user + * pauses. + */ + bool loop; + + /** + * @brief Seeking also selects + * + * Olive supports automatically selecting clips that the playhead is currently touching for a more efficient workflow. + * + * **TRUE** if this mode should be enabled. + */ + bool seek_also_selects; + + /** + * @brief Automatically seek to the beginning of a sequence if the user plays beyond the end of it + * + * TRUE if this behavior should be enabled. + */ + bool auto_seek_to_beginning; + + /** + * @brief CSS Path + * + * The URL to a CSS file if the user has loaded a custom stylesheet in. **EMPTY** if the user has not set a + * stylesheet. + */ + QString css_path; + + /** + * @brief Number of lines that an Effect's textbox has + * + * The height of a Effect's textbox field in terms of lines. + * + * Set to a value >= 1 + */ + int effect_textbox_lines; + + /** + * @brief Use software fallbacks when possible + * + * Olive uses a lot of OpenGL-based hardware acceleration for performance. Some older hardware has difficulty + * supporting this functionality, so some of it has software-based (not hardware accelerated) fallbacks for these + * users. + * + * **TRUE** if Olive should prefer software fallbacks to hardware acceleration when they're available. + */ + bool use_software_fallback; + + /** + * @brief Center Timeline timecodes + * + * By default, Olive shows timecodes in the TimelineHeader centered to the corresponding frame point. This may not + * always be desirable as, for example, this forces the initial 00:00:00;00 timecode's left half to be cut off. + * Olive supports aligning the timecode to the right of the frame rather than the center to address this. + */ + bool center_timeline_timecodes; + + /** + * @brief Preferred audio output device + * + * Sets the audio device Olive should use to output audio to. + * + * Set to the name of the audio device or **EMPTY** to try using the default. + */ + QString preferred_audio_output; + + /** + * @brief Preferred audio input device + * + * Sets the audio device Olive should use to input audio from. + * + * Set to the name of the audio device or **EMPTY** to try using the default. + */ + QString preferred_audio_input; + + /** + * @brief Language/translation file + * + * Sets the translation file to load to display Olive in a different language. + * + * Set to the URL of the language file to load, or **EMPTY** to use default en-US language. + */ + QString language_file; + + /** + * @brief Waveform resolution + * + * Sets how detailed the waveforms should be in the Timeline. Higher value = more detail. + * + * Specifically sets how many samples per second should be "cached" for preview. If the waveforms are too blocky, + * set this higher. If Timeline performance is slow, set this lower. + */ + int waveform_resolution; + + /** + * @brief Thumbnail resolution + * + * The vertical pixel height to use for generating thumbnails. + */ + int thumbnail_resolution; + + /** + * @brief Add default effects to clips + * + * **TRUE** if new clips imported into the Timeline should have a set of default effects (TransformEffect, + * VolumeEffect, and PanEffect) added to them by default. + */ + bool add_default_effects_to_clips; + + /** + * @brief Invert Timeline scroll axes + * + * **TRUE** if scrolling vertically on the Timeline should scroll it horizontally + */ + 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. + * + * Set to a member of olive::styling::Style. + */ + olive::styling::Style style; + + /** + * @brief Use native menu styling + * + * Use native styling on menus rather than cross-platform Fusion. + */ + bool use_native_menu_styling; + + /** + * @brief Default Sequence video width + */ + int default_sequence_width; + + /** + * @brief Default Sequence video height + */ + int default_sequence_height; + + /** + * @brief Default Sequence video frame rate + */ + double default_sequence_framerate; + + /** + * @brief Default Sequence audio frequency + */ + int default_sequence_audio_frequency; + + /** + * @brief Default Sequence audio channel layout + */ + int default_sequence_audio_channel_layout; + + /** + * @brief Playback bit depth (an index of olive::rendering::bit_depths) + */ + olive::PixelFormat playback_bit_depth; + + /** + * @brief Export bit depth (an index of olive::rendering::bit_depths) + */ + olive::PixelFormat 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 + */ + bool locked_panels; + + /** + * @brief Load config from file + * + * Load configuration parameters from file + * + * @param path + * + * URL to the configuration file to load + */ + void load(QString path); + + /** + * @brief Save config to file + * + * Save current configuration parameters to file + * + * @param path + * + * URL to save the configuration file to. + */ + void save(QString path); +}; + +/** + * @brief The RuntimeConfig struct + * + * This struct handles any configuration that's set as a command-line argument to Olive, and shouldn't be persistent + * between restarts of Olive. + */ +struct RuntimeConfig { + /** + * @brief RuntimeConfig Constructor + * + * Sets default runtime configuration + */ + RuntimeConfig(); + + /** + * @brief Enable shaders + * + * Debugging tool. Set to **FALSE** to bypass OpenGL shaders. + */ + bool shaders_are_enabled; + + /** + * @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 config; +extern RuntimeConfig runtime_config; +} + +#endif // CONFIG_H diff --git a/global/crashhandler.cpp b/global/crashhandler.cpp index ac4c10630..41d1a7061 100644 --- a/global/crashhandler.cpp +++ b/global/crashhandler.cpp @@ -1,119 +1,119 @@ -#include "crashhandler.h" - -#include -#include - -#include "dialogs/crashdialog.h" - -#ifdef __GNUC__ -#ifdef Q_OS_WIN -#include -#include -#elif defined(Q_OS_LINUX) -#include -#endif -#include -#endif - -#ifdef __GNUC__ -void handler(int sig) { - QStringList strings; - -#ifdef Q_OS_WIN - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - - CONTEXT context; - memset(&context, 0, sizeof(CONTEXT)); - context.ContextFlags = CONTEXT_FULL; - RtlCaptureContext(&context); - - SymInitialize(process, NULL, TRUE); - - DWORD image; - STACKFRAME64 stackframe; - ZeroMemory(&stackframe, sizeof(STACKFRAME64)); - -#ifdef _M_IX86 - image = IMAGE_FILE_MACHINE_I386; - stackframe.AddrPC.Offset = context.Eip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Ebp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Esp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_X64 - image = IMAGE_FILE_MACHINE_AMD64; - stackframe.AddrPC.Offset = context.Rip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Rsp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Rsp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_IA64 - image = IMAGE_FILE_MACHINE_IA64; - stackframe.AddrPC.Offset = context.StIIP; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.IntSp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrBStore.Offset = context.RsBSP; - stackframe.AddrBStore.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.IntSp; - stackframe.AddrStack.Mode = AddrModeFlat; -#endif - - int counter = 0; - QString line_template = "[%1] %2"; - while (StackWalk64( - image, process, thread, - &stackframe, &context, NULL, - SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { - - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - - - QString sym_name; - if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) { - sym_name = symbol->Name; - } else { - sym_name = "???"; - } - QString s = line_template.arg(QString::number(counter), sym_name); - strings.append(s); - std::cout << s.data() << std::endl << std::flush; - - counter++; - } - - SymCleanup(process); -#elif defined(Q_OS_LINUX) - void *array[10]; - size_t size; - - // get void*'s for all entries on the stack - size = backtrace(array, 10); - - // print out all the frames to stderr - fprintf(stderr, "Signal: %d\n\n", sig); - backtrace_symbols_fd(array, size, STDERR_FILENO); - - // try to show a GUI crash report - char** bt_syms = backtrace_symbols(array, size); - for (int i=0;iSetData(sig, strings); - olive::crash_dialog->exec(); - - abort(); -} -#endif +#include "crashhandler.h" + +#include +#include + +#include "dialogs/crashdialog.h" + +#ifdef __GNUC__ +#ifdef Q_OS_WIN +#include +#include +#elif defined(Q_OS_LINUX) +#include +#endif +#include +#endif + +#ifdef __GNUC__ +void handler(int sig) { + QStringList strings; + +#ifdef Q_OS_WIN + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + CONTEXT context; + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + + SymInitialize(process, NULL, TRUE); + + DWORD image; + STACKFRAME64 stackframe; + ZeroMemory(&stackframe, sizeof(STACKFRAME64)); + +#ifdef _M_IX86 + image = IMAGE_FILE_MACHINE_I386; + stackframe.AddrPC.Offset = context.Eip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Ebp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Esp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + image = IMAGE_FILE_MACHINE_AMD64; + stackframe.AddrPC.Offset = context.Rip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Rsp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Rsp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + image = IMAGE_FILE_MACHINE_IA64; + stackframe.AddrPC.Offset = context.StIIP; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.IntSp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrBStore.Offset = context.RsBSP; + stackframe.AddrBStore.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.IntSp; + stackframe.AddrStack.Mode = AddrModeFlat; +#endif + + int counter = 0; + QString line_template = "[%1] %2"; + while (StackWalk64( + image, process, thread, + &stackframe, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + + + QString sym_name; + if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) { + sym_name = symbol->Name; + } else { + sym_name = "???"; + } + QString s = line_template.arg(QString::number(counter), sym_name); + strings.append(s); + std::cout << s.data() << std::endl << std::flush; + + counter++; + } + + SymCleanup(process); +#elif defined(Q_OS_LINUX) + void *array[10]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr + fprintf(stderr, "Signal: %d\n\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); + + // try to show a GUI crash report + char** bt_syms = backtrace_symbols(array, size); + for (int i=0;iSetData(sig, strings); + olive::crash_dialog->exec(); + + abort(); +} +#endif diff --git a/global/crashhandler.h b/global/crashhandler.h index af3e58198..4dab03089 100644 --- a/global/crashhandler.h +++ b/global/crashhandler.h @@ -1,9 +1,9 @@ -#ifndef CRASHHANDLER_H -#define CRASHHANDLER_H - -#ifdef __GNUC__ -#include -void handler(int sig); -#endif - -#endif // CRASHHANDLER_H +#ifndef CRASHHANDLER_H +#define CRASHHANDLER_H + +#ifdef __GNUC__ +#include +void handler(int sig); +#endif + +#endif // CRASHHANDLER_H diff --git a/global/debug.cpp b/global/debug.cpp index d9aee5e21..f5a3b4ac0 100644 --- a/global/debug.cpp +++ b/global/debug.cpp @@ -1,112 +1,112 @@ -/*** - - 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 "debug.h" - -#include -#include -#include -#include -#include - -#include "dialogs/debugdialog.h" - -QString debug_info; -QMutex debug_mutex; -QFile debug_file; -QTextStream debug_stream; - -void open_debug_file() { - QDir debug_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); - debug_dir.mkpath("."); - if (debug_dir.exists()) { - debug_file.setFileName(debug_dir.path() + "/debug_log"); - if (debug_file.open(QFile::WriteOnly)) { - debug_stream.setDevice(&debug_file); - } else { - qWarning() << "Couldn't open debug log file, debug log will not be saved"; - } - } -} - -void close_debug_file() -{ - if (debug_file.isOpen()) { - debug_file.close(); - } -} - -void debug_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - debug_mutex.lock(); - const QByteArray localMsg = msg.toLocal8Bit(); - const QDateTime now = QDateTime::currentDateTime(); - const QByteArray timeRepr(now.toString(Qt::ISODate).toLocal8Bit()); - QString msgTag; - QString fontColor; - switch (type) { - case QtDebugMsg: - msgTag = "DEBUG"; - fontColor = "grey"; - break; - case QtInfoMsg: - msgTag = "INFO"; - fontColor = "blue"; - break; - case QtWarningMsg: - msgTag = "WARNING"; - fontColor = "yellow"; - break; - case QtCriticalMsg: - msgTag = "ERROR"; - fontColor = "red"; - break; - case QtFatalMsg: - msgTag = "FATAL"; - fontColor = "red"; - break; - default: - fprintf(stderr, "Unknown debug msg type"); - fflush(stderr); - break; - }//switch - - /*fprintf(stderr, "%s [%s] %s (%s:%u, %s)\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data(), - context.file, context.line, context.function);*/ - - fprintf(stderr, "%s [%s] %s\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data()); - - if (debug_file.isOpen()) { - debug_stream << QString("[%1] %2 (%3:%4, %5)\n") - .arg(msgTag, localMsg, context.file, QString::number(context.line), context.function); - } - debug_info.append(QString("[%2] %3 (%4:%5, %6)
") - .arg(fontColor, msgTag, localMsg, context.file, QString::number(context.line), context.function)); - fflush(stderr); - if (olive::DebugDialog != nullptr && olive::DebugDialog->isVisible()) { - QMetaObject::invokeMethod(olive::DebugDialog, "update_log", Qt::QueuedConnection); - } - debug_mutex.unlock(); -} - -const QString &get_debug_str() -{ - return debug_info; -} +/*** + + 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 "debug.h" + +#include +#include +#include +#include +#include + +#include "dialogs/debugdialog.h" + +QString debug_info; +QMutex debug_mutex; +QFile debug_file; +QTextStream debug_stream; + +void open_debug_file() { + QDir debug_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + debug_dir.mkpath("."); + if (debug_dir.exists()) { + debug_file.setFileName(debug_dir.path() + "/debug_log"); + if (debug_file.open(QFile::WriteOnly)) { + debug_stream.setDevice(&debug_file); + } else { + qWarning() << "Couldn't open debug log file, debug log will not be saved"; + } + } +} + +void close_debug_file() +{ + if (debug_file.isOpen()) { + debug_file.close(); + } +} + +void debug_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + debug_mutex.lock(); + const QByteArray localMsg = msg.toLocal8Bit(); + const QDateTime now = QDateTime::currentDateTime(); + const QByteArray timeRepr(now.toString(Qt::ISODate).toLocal8Bit()); + QString msgTag; + QString fontColor; + switch (type) { + case QtDebugMsg: + msgTag = "DEBUG"; + fontColor = "grey"; + break; + case QtInfoMsg: + msgTag = "INFO"; + fontColor = "blue"; + break; + case QtWarningMsg: + msgTag = "WARNING"; + fontColor = "yellow"; + break; + case QtCriticalMsg: + msgTag = "ERROR"; + fontColor = "red"; + break; + case QtFatalMsg: + msgTag = "FATAL"; + fontColor = "red"; + break; + default: + fprintf(stderr, "Unknown debug msg type"); + fflush(stderr); + break; + }//switch + + /*fprintf(stderr, "%s [%s] %s (%s:%u, %s)\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data(), + context.file, context.line, context.function);*/ + + fprintf(stderr, "%s [%s] %s\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data()); + + if (debug_file.isOpen()) { + debug_stream << QString("[%1] %2 (%3:%4, %5)\n") + .arg(msgTag, localMsg, context.file, QString::number(context.line), context.function); + } + debug_info.append(QString("[%2] %3 (%4:%5, %6)
") + .arg(fontColor, msgTag, localMsg, context.file, QString::number(context.line), context.function)); + fflush(stderr); + if (olive::DebugDialog != nullptr && olive::DebugDialog->isVisible()) { + QMetaObject::invokeMethod(olive::DebugDialog, "update_log", Qt::QueuedConnection); + } + debug_mutex.unlock(); +} + +const QString &get_debug_str() +{ + return debug_info; +} diff --git a/global/debug.h b/global/debug.h index b6b4be132..0d686794a 100644 --- a/global/debug.h +++ b/global/debug.h @@ -1,31 +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 DEBUG_H -#define DEBUG_H - -#include - -void debug_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg); -const QString& get_debug_str(); -void open_debug_file(); -void close_debug_file(); - -#endif // DEBUG_H +/*** + + 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 DEBUG_H +#define DEBUG_H + +#include + +void debug_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg); +const QString& get_debug_str(); +void open_debug_file(); +void close_debug_file(); + +#endif // DEBUG_H diff --git a/global/global.cpp b/global/global.cpp index 479c4d960..03c2a8b9a 100644 --- a/global/global.cpp +++ b/global/global.cpp @@ -1,831 +1,831 @@ -/*** - - 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 "global/global.h" - -#include -#include -#include -#include -#include -#include - -#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" -#include "dialogs/exportdialog.h" -#include "dialogs/debugdialog.h" -#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" - -std::unique_ptr olive::Global; -QString olive::ActiveProjectFilename; -QString olive::AppName; - -OliveGlobal::OliveGlobal() : - changed_since_last_autorecovery(false), - rendering_(false) -{ - // sets current app name - QString version_id; - - // if available, append the current Git hash (defined by `qmake` and the Makefile) -#ifdef GITHASH - version_id = QString(" | %1").arg(GITHASH); -#endif - - olive::AppName = QString("Olive (May 2019 | Alpha%1)").arg(version_id); - - // set the file filter used in all file dialogs pertaining to Olive project files. - project_file_filter = tr("Olive Project %1").arg("(*.ove)"); - - // set default value - enable_load_project_on_init = false; - - // alloc QTranslator - translator = std::unique_ptr(new QTranslator()); -} - -const QString &OliveGlobal::get_project_file_filter() { - return project_file_filter; -} - -void OliveGlobal::update_project_filename(const QString &s) { - // set filename to s - olive::ActiveProjectFilename = s; - - // update main window title to reflect new project filename - olive::MainWindow->updateTitle(); -} - -void OliveGlobal::check_for_autorecovery_file() { - QString data_dir = get_data_path(); - if (!data_dir.isEmpty()) { - // detect auto-recovery file - autorecovery_filename = data_dir + "/autorecovery.ove"; - if (QFile::exists(autorecovery_filename)) { - if (QMessageBox::question(nullptr, - tr("Auto-recovery"), - tr("Olive didn't close properly and an autorecovery file " - "was detected. Would you like to open it?"), - QMessageBox::Yes, - QMessageBox::No) == QMessageBox::Yes) { - enable_load_project_on_init = false; - OpenProjectWorker(autorecovery_filename, true); - } - } - autorecovery_timer.setInterval(60000); - QObject::connect(&autorecovery_timer, SIGNAL(timeout()), this, SLOT(save_autorecovery_file())); - autorecovery_timer.start(); - } -} - -bool OliveGlobal::is_exporting() -{ - return rendering_; -} - -const olive::PixelFormat &OliveGlobal::effective_bit_depth() -{ - // FIXME uncomment this -// return olive::Global->is_exporting() ? olive::config.export_bit_depth : olive::config.playback_bit_depth; - return olive::config.export_bit_depth; -} - -void OliveGlobal::set_export_state(bool rendering) { - rendering_ = rendering; - if (rendering) { - autorecovery_timer.stop(); - } else { - autorecovery_timer.start(); - } -} - -void OliveGlobal::set_modified(bool modified) -{ - olive::MainWindow->setWindowModified(modified); - changed_since_last_autorecovery = modified; -} - -bool OliveGlobal::is_modified() -{ - return olive::MainWindow->isWindowModified(); -} - -void OliveGlobal::load_project_on_launch(const QString& s) { - olive::ActiveProjectFilename = s; - enable_load_project_on_init = true; -} - -QString OliveGlobal::get_recent_project_list_file() { - return get_data_dir().filePath("recents"); -} - -void OliveGlobal::load_translation_from_config() { - 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::runtime_config.external_translation_file.clear(); - - // remove current translation if there is one - QApplication::removeTranslator(translator.get()); - - if (!language_file.isEmpty()) { - - // translation files are stored relative to app path (see GitHub issue #454) - QString full_language_path = QDir(get_app_path()).filePath(language_file); - - // load translation file - if (QFileInfo::exists(full_language_path) - && translator->load(full_language_path)) { - QApplication::installTranslator(translator.get()); - } else { - qWarning() << "Failed to load translation file" << full_language_path << ". No language will be loaded."; - } - } -} - -void OliveGlobal::SetNativeStyling(QWidget *w) -{ -#ifdef Q_OS_WIN - 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. - - for (int i=0;iDisconnectFilterToModel(); - } - - LoadDialog ld(olive::MainWindow); - - ld.open(); - - LoadThread* lt = new LoadThread(fn, autorecovery); - connect(&ld, SIGNAL(cancel()), lt, SLOT(cancel())); - connect(lt, SIGNAL(success()), &ld, SLOT(accept())); - connect(lt, SIGNAL(error()), &ld, SLOT(reject())); - connect(lt, SIGNAL(error()), this, SLOT(new_project())); - connect(lt, SIGNAL(report_progress(int)), &ld, SLOT(setValue(int))); - lt->start(); - - for (int i=0;iConnectFilterToModel(); - } -} - -void OliveGlobal::ClearProject() -{ - // clear graph editor - panel_graph_editor->set_row(nullptr); - - // clear effects panel - panel_effect_controls->Clear(true); - - // clear existing project - 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.) - olive::project_model.clear(); - - // clear undo stack - olive::undo_stack.clear(); - - // empty current project filename - update_project_filename(""); - - // full update of all panels - update_ui(false); - - // set to unmodified - 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->TrackAt(c->track()->type(), 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()); - OldEffectNode* 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) -{ - LoadProject(fn, false); - set_modified(true); -} - -void OliveGlobal::new_project() { - if (can_close_project()) { - ClearProject(); - } -} - -void OliveGlobal::OpenProject() { - QString fn = QFileDialog::getOpenFileName(olive::MainWindow, tr("Open Project..."), "", project_file_filter); - if (!fn.isEmpty() && can_close_project()) { - OpenProjectWorker(fn, false); - } -} - -void OliveGlobal::open_recent(int index) { - QString recent_url = recent_projects.at(index); - if (!QFile::exists(recent_url)) { - if (QMessageBox::question( - olive::MainWindow, - tr("Missing recent project"), - 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); - save_recent_projects(); - } - } else if (can_close_project()) { - OpenProjectWorker(recent_url, false); - } -} - -bool OliveGlobal::save_project_as() { - QString fn = QFileDialog::getSaveFileName(olive::MainWindow, tr("Save Project As..."), "", project_file_filter); - if (!fn.isEmpty()) { - if (!fn.endsWith(".ove", Qt::CaseInsensitive)) { - fn += ".ove"; - } - update_project_filename(fn); - olive::Save(false); - return true; - } - return false; -} - -bool OliveGlobal::save_project() { - if (olive::ActiveProjectFilename.isEmpty()) { - return save_project_as(); - } else { - olive::Save(false); - return true; - } -} - -bool OliveGlobal::can_close_project() { - if (is_modified()) { - QMessageBox* m = new QMessageBox( - QMessageBox::Question, - tr("Unsaved Project"), - tr("This project has changed since it was last saved. Would you like to save it before closing?"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, - olive::MainWindow - ); - m->setWindowModality(Qt::WindowModal); - int r = m->exec(); - delete m; - if (r == QMessageBox::Yes) { - return save_project(); - } else if (r == QMessageBox::Cancel) { - return false; - } - } - 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, Timeline::GetTopSequence().get()); - e.exec(); - } -} - -void OliveGlobal::finished_initialize() { - if (enable_load_project_on_init) { - - // if a project was set as a command line argument, we load it here - if (QFileInfo::exists(olive::ActiveProjectFilename)) { - OpenProjectWorker(olive::ActiveProjectFilename, false); - } else { - QMessageBox::critical(olive::MainWindow, - tr("Missing Project File"), - tr("Specified project '%1' does not exist.").arg(olive::ActiveProjectFilename), - QMessageBox::Ok); - update_project_filename(nullptr); - } - - enable_load_project_on_init = false; - - } else { - // if we are not loading a project on launch and are running a release build, open the demo notice dialog -#ifndef QT_DEBUG - DemoNotice* d = new DemoNotice(olive::MainWindow); - connect(d, SIGNAL(finished(int)), d, SLOT(deleteLater())); - d->open(); -#endif - } - - // Check for updates - olive::update_notifier.check(); -} - -void OliveGlobal::save_autorecovery_file() { - if (changed_since_last_autorecovery) { - olive::Save(true); - - changed_since_last_autorecovery = false; - - qInfo() << "Auto-recovery project saved"; - } -} - -void OliveGlobal::open_preferences() { - panel_sequence_viewer->pause(); - panel_footage_viewer->pause(); - - PreferencesDialog pd(olive::MainWindow); - pd.exec(); -} - -void OliveGlobal::PrimarySequenceChanged() -{ - panel_graph_editor->set_row(nullptr); - panel_effect_controls->Clear(true); -} - -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::undo_stack.clear(); -} - -bool OliveGlobal::CheckForActiveSequence(bool show_msg) -{ - if (Timeline::GetTopSequence() == nullptr) { - - if (show_msg) { - QMessageBox::information(olive::MainWindow, - tr("No active sequence"), - tr("Please open the sequence to perform this action."), - QMessageBox::Ok); - } - - return false; - } - 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 (!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 (!Timeline::IsImporting()) { - olive::undo_stack.redo(); - update_ui(true); - } -} - -void OliveGlobal::paste() { - PasteInternal(Timeline::GetTopSequence().get(), false); -} - -void OliveGlobal::paste_insert() { - PasteInternal(Timeline::GetTopSequence().get(), true); -} - -void OliveGlobal::open_about_dialog() { - AboutDialog a(olive::MainWindow); - a.exec(); -} - -void OliveGlobal::open_debug_log() { - olive::DebugDialog->show(); -} - -void OliveGlobal::open_speed_dialog() { - if (Timeline::GetTopSequence() != nullptr) { - - QVector selected_clips = Timeline::GetTopSequence()->SelectedClips(); - - if (!selected_clips.isEmpty()) { - SpeedDialog s(olive::MainWindow, selected_clips); - s.exec(); - } - } -} - -void OliveGlobal::open_autocut_silence_dialog() { - if (CheckForActiveSequence()) { - - QVector selected_clips = Timeline::GetTopSequence()->SelectedClips(); - - if (selected_clips.isEmpty()) { - QMessageBox::critical(olive::MainWindow, - tr("No clips selected"), - tr("Select the clips you wish to auto-cut"), - QMessageBox::Ok); - } else { - AutoCutSilenceDialog s(olive::MainWindow, selected_clips); - s.exec(); - } - - } -} - -void OliveGlobal::clear_undo_stack() { - olive::undo_stack.clear(); -} - -void OliveGlobal::open_action_search() { - ActionSearch as(olive::MainWindow); - as.exec(); -} +/*** + + 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 "global/global.h" + +#include +#include +#include +#include +#include +#include + +#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" +#include "dialogs/exportdialog.h" +#include "dialogs/debugdialog.h" +#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" + +std::unique_ptr olive::Global; +QString olive::ActiveProjectFilename; +QString olive::AppName; + +OliveGlobal::OliveGlobal() : + changed_since_last_autorecovery(false), + rendering_(false) +{ + // sets current app name + QString version_id; + + // if available, append the current Git hash (defined by `qmake` and the Makefile) +#ifdef GITHASH + version_id = QString(" | %1").arg(GITHASH); +#endif + + olive::AppName = QString("Olive (May 2019 | Alpha%1)").arg(version_id); + + // set the file filter used in all file dialogs pertaining to Olive project files. + project_file_filter = tr("Olive Project %1").arg("(*.ove)"); + + // set default value + enable_load_project_on_init = false; + + // alloc QTranslator + translator = std::unique_ptr(new QTranslator()); +} + +const QString &OliveGlobal::get_project_file_filter() { + return project_file_filter; +} + +void OliveGlobal::update_project_filename(const QString &s) { + // set filename to s + olive::ActiveProjectFilename = s; + + // update main window title to reflect new project filename + olive::MainWindow->updateTitle(); +} + +void OliveGlobal::check_for_autorecovery_file() { + QString data_dir = get_data_path(); + if (!data_dir.isEmpty()) { + // detect auto-recovery file + autorecovery_filename = data_dir + "/autorecovery.ove"; + if (QFile::exists(autorecovery_filename)) { + if (QMessageBox::question(nullptr, + tr("Auto-recovery"), + tr("Olive didn't close properly and an autorecovery file " + "was detected. Would you like to open it?"), + QMessageBox::Yes, + QMessageBox::No) == QMessageBox::Yes) { + enable_load_project_on_init = false; + OpenProjectWorker(autorecovery_filename, true); + } + } + autorecovery_timer.setInterval(60000); + QObject::connect(&autorecovery_timer, SIGNAL(timeout()), this, SLOT(save_autorecovery_file())); + autorecovery_timer.start(); + } +} + +bool OliveGlobal::is_exporting() +{ + return rendering_; +} + +const olive::PixelFormat &OliveGlobal::effective_bit_depth() +{ + // FIXME uncomment this +// return olive::Global->is_exporting() ? olive::config.export_bit_depth : olive::config.playback_bit_depth; + return olive::config.export_bit_depth; +} + +void OliveGlobal::set_export_state(bool rendering) { + rendering_ = rendering; + if (rendering) { + autorecovery_timer.stop(); + } else { + autorecovery_timer.start(); + } +} + +void OliveGlobal::set_modified(bool modified) +{ + olive::MainWindow->setWindowModified(modified); + changed_since_last_autorecovery = modified; +} + +bool OliveGlobal::is_modified() +{ + return olive::MainWindow->isWindowModified(); +} + +void OliveGlobal::load_project_on_launch(const QString& s) { + olive::ActiveProjectFilename = s; + enable_load_project_on_init = true; +} + +QString OliveGlobal::get_recent_project_list_file() { + return get_data_dir().filePath("recents"); +} + +void OliveGlobal::load_translation_from_config() { + 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::runtime_config.external_translation_file.clear(); + + // remove current translation if there is one + QApplication::removeTranslator(translator.get()); + + if (!language_file.isEmpty()) { + + // translation files are stored relative to app path (see GitHub issue #454) + QString full_language_path = QDir(get_app_path()).filePath(language_file); + + // load translation file + if (QFileInfo::exists(full_language_path) + && translator->load(full_language_path)) { + QApplication::installTranslator(translator.get()); + } else { + qWarning() << "Failed to load translation file" << full_language_path << ". No language will be loaded."; + } + } +} + +void OliveGlobal::SetNativeStyling(QWidget *w) +{ +#ifdef Q_OS_WIN + 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. + + for (int i=0;iDisconnectFilterToModel(); + } + + LoadDialog ld(olive::MainWindow); + + ld.open(); + + LoadThread* lt = new LoadThread(fn, autorecovery); + connect(&ld, SIGNAL(cancel()), lt, SLOT(cancel())); + connect(lt, SIGNAL(success()), &ld, SLOT(accept())); + connect(lt, SIGNAL(error()), &ld, SLOT(reject())); + connect(lt, SIGNAL(error()), this, SLOT(new_project())); + connect(lt, SIGNAL(report_progress(int)), &ld, SLOT(setValue(int))); + lt->start(); + + for (int i=0;iConnectFilterToModel(); + } +} + +void OliveGlobal::ClearProject() +{ + // clear graph editor + panel_graph_editor->set_row(nullptr); + + // clear effects panel + panel_effect_controls->Clear(true); + + // clear existing project + 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.) + olive::project_model.clear(); + + // clear undo stack + olive::undo_stack.clear(); + + // empty current project filename + update_project_filename(""); + + // full update of all panels + update_ui(false); + + // set to unmodified + 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->TrackAt(c->track()->type(), 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()); + OldEffectNode* 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) +{ + LoadProject(fn, false); + set_modified(true); +} + +void OliveGlobal::new_project() { + if (can_close_project()) { + ClearProject(); + } +} + +void OliveGlobal::OpenProject() { + QString fn = QFileDialog::getOpenFileName(olive::MainWindow, tr("Open Project..."), "", project_file_filter); + if (!fn.isEmpty() && can_close_project()) { + OpenProjectWorker(fn, false); + } +} + +void OliveGlobal::open_recent(int index) { + QString recent_url = recent_projects.at(index); + if (!QFile::exists(recent_url)) { + if (QMessageBox::question( + olive::MainWindow, + tr("Missing recent project"), + 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); + save_recent_projects(); + } + } else if (can_close_project()) { + OpenProjectWorker(recent_url, false); + } +} + +bool OliveGlobal::save_project_as() { + QString fn = QFileDialog::getSaveFileName(olive::MainWindow, tr("Save Project As..."), "", project_file_filter); + if (!fn.isEmpty()) { + if (!fn.endsWith(".ove", Qt::CaseInsensitive)) { + fn += ".ove"; + } + update_project_filename(fn); + olive::Save(false); + return true; + } + return false; +} + +bool OliveGlobal::save_project() { + if (olive::ActiveProjectFilename.isEmpty()) { + return save_project_as(); + } else { + olive::Save(false); + return true; + } +} + +bool OliveGlobal::can_close_project() { + if (is_modified()) { + QMessageBox* m = new QMessageBox( + QMessageBox::Question, + tr("Unsaved Project"), + tr("This project has changed since it was last saved. Would you like to save it before closing?"), + QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, + olive::MainWindow + ); + m->setWindowModality(Qt::WindowModal); + int r = m->exec(); + delete m; + if (r == QMessageBox::Yes) { + return save_project(); + } else if (r == QMessageBox::Cancel) { + return false; + } + } + 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, Timeline::GetTopSequence().get()); + e.exec(); + } +} + +void OliveGlobal::finished_initialize() { + if (enable_load_project_on_init) { + + // if a project was set as a command line argument, we load it here + if (QFileInfo::exists(olive::ActiveProjectFilename)) { + OpenProjectWorker(olive::ActiveProjectFilename, false); + } else { + QMessageBox::critical(olive::MainWindow, + tr("Missing Project File"), + tr("Specified project '%1' does not exist.").arg(olive::ActiveProjectFilename), + QMessageBox::Ok); + update_project_filename(nullptr); + } + + enable_load_project_on_init = false; + + } else { + // if we are not loading a project on launch and are running a release build, open the demo notice dialog +#ifndef QT_DEBUG + DemoNotice* d = new DemoNotice(olive::MainWindow); + connect(d, SIGNAL(finished(int)), d, SLOT(deleteLater())); + d->open(); +#endif + } + + // Check for updates + olive::update_notifier.check(); +} + +void OliveGlobal::save_autorecovery_file() { + if (changed_since_last_autorecovery) { + olive::Save(true); + + changed_since_last_autorecovery = false; + + qInfo() << "Auto-recovery project saved"; + } +} + +void OliveGlobal::open_preferences() { + panel_sequence_viewer->pause(); + panel_footage_viewer->pause(); + + PreferencesDialog pd(olive::MainWindow); + pd.exec(); +} + +void OliveGlobal::PrimarySequenceChanged() +{ + panel_graph_editor->set_row(nullptr); + panel_effect_controls->Clear(true); +} + +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::undo_stack.clear(); +} + +bool OliveGlobal::CheckForActiveSequence(bool show_msg) +{ + if (Timeline::GetTopSequence() == nullptr) { + + if (show_msg) { + QMessageBox::information(olive::MainWindow, + tr("No active sequence"), + tr("Please open the sequence to perform this action."), + QMessageBox::Ok); + } + + return false; + } + 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 (!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 (!Timeline::IsImporting()) { + olive::undo_stack.redo(); + update_ui(true); + } +} + +void OliveGlobal::paste() { + PasteInternal(Timeline::GetTopSequence().get(), false); +} + +void OliveGlobal::paste_insert() { + PasteInternal(Timeline::GetTopSequence().get(), true); +} + +void OliveGlobal::open_about_dialog() { + AboutDialog a(olive::MainWindow); + a.exec(); +} + +void OliveGlobal::open_debug_log() { + olive::DebugDialog->show(); +} + +void OliveGlobal::open_speed_dialog() { + if (Timeline::GetTopSequence() != nullptr) { + + QVector selected_clips = Timeline::GetTopSequence()->SelectedClips(); + + if (!selected_clips.isEmpty()) { + SpeedDialog s(olive::MainWindow, selected_clips); + s.exec(); + } + } +} + +void OliveGlobal::open_autocut_silence_dialog() { + if (CheckForActiveSequence()) { + + QVector selected_clips = Timeline::GetTopSequence()->SelectedClips(); + + if (selected_clips.isEmpty()) { + QMessageBox::critical(olive::MainWindow, + tr("No clips selected"), + tr("Select the clips you wish to auto-cut"), + QMessageBox::Ok); + } else { + AutoCutSilenceDialog s(olive::MainWindow, selected_clips); + s.exec(); + } + + } +} + +void OliveGlobal::clear_undo_stack() { + olive::undo_stack.clear(); +} + +void OliveGlobal::open_action_search() { + ActionSearch as(olive::MainWindow); + as.exec(); +} diff --git a/global/global.h b/global/global.h index 5722088f7..d121fc417 100644 --- a/global/global.h +++ b/global/global.h @@ -1,573 +1,573 @@ -/*** - - 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 OLIVEGLOBAL_H -#define OLIVEGLOBAL_H - -#include - -#include -#include -#include - -#include "undo/undo.h" -#include "rendering/pixelformats.h" - -/** - * @brief The Olive Global class - * - * A resource for various global functions used throughout Olive. - */ -class OliveGlobal : public QObject { - Q_OBJECT -public: - /** - * @brief OliveGlobal Constructor - * - * Creates Olive Global object. Also sets some default runtime settings and the application name. - */ - 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(); - - /** - * @brief Change the current active project filename - * - * Triggered to change the current active project filename. Call this before calling any internal project - * saving or loading functions in order to set which file to work with (OliveGlobal::open_project() and - * OliveGlobal::save_project_as() do this automatically). Also updates the main window title to reflect the - * project filename. - * - * @param s - * - * 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); - - /** - * @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(); - - /** - * @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 Returns the "effective" bit depth for the composition pipeline - * - * Convenience function for using Config::playback_bit_depth or Config::export_bit_depth depending on the state of - * OliveGlobal::is_exporting() - */ - const olive::PixelFormat& effective_bit_depth(); - - /** - * @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 - * as necessary. - * - * The current functions are as follows: - * * Auto-recovery interval. Olive saves an auto-recovery just before exporting anyway and seeing as the user - * cannot make changes while rendering, there's no reason to continue saving auto-recovery files. - * * Audio device playback. Olive uses the same internal audio buffer for exporting as it does for playback, but - * this buffer does not need to be forwarded to the output device when exporting. - * - * @param rendering - * - * **TRUE** if Olive is about to export a video. **FALSE** if Olive has finished exporting. - */ - 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 - * technically controls whether to create autorecovery files as they'll only be generated if there are unsaved - * changes. - * - * @param modified - * - * TRUE if the project has been modified, FALSE if it has not. - */ - 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. - * This value is used to determine whether the currently open project has unsaved changes. - * - * @return - * - * TRUE if the project has been modified since the last save. - */ - 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 - * specified project once its finished initializing. - * - * @param s - * - * The URL of the project file to load. - */ - 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(); - - /** - * @brief (Re)load translation file from olive::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); - - /** - * @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(); - - /** - * @brief Redo user's last action - */ - 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(); - - /** - * @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 - * current playhead and ripple forward to make space for the clips in the clipboard. Can be considered - * 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(); - - /** - * @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(); - - /** - * @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(); - - /** - * @brief Import project from file - * - * Imports an Olive project into the current project, effectively merging them. - * - * @param fn - * - * The filename of the project to import. - */ - void ImportProject(const QString& fn); - - /** - * @brief Open recent project from list - * - * Triggers a project load from the internal recent projects list. - * - * @param index - * - * Index in the list of the project file to load - */ - 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 - * one. Also triggered by save_project() if the file hasn't been saved yet. - * - * @return **TRUE** if the user saved the project. **FALSE** if they cancelled out of the save file dialog. Useful - * 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(); - - /** - * @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 - * data. Calls save_project_as() if the file has not been saved before. - * - * @return **TRUE** if the project has been saved before and was successfully overwritten. Otherwise returns the - * 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(); - - /** - * @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, - * exiting Olive, etc.) If the project has unsaved changes, this function asks the user whether they want to save or - * not. If the user does, calls save_project() (which may in turn call save_project_as() if the project has never - * been saved). - * - * @return **TRUE** if the project can be closed. FALSE if not. If the project does NOT have unsaved changes, always - * 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(); - - /** - * @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(); - - /** - * @brief Open the About Olive dialog. - */ - void open_about_dialog(); - - /** - * @brief Open the Debug Log window. - */ - void open_debug_log(); - - /** - * @brief Open the Speed/Duration dialog. - */ - void open_speed_dialog(); - - /** - * @brief Open the auto-cut silence dialog. - */ - void open_autocut_silence_dialog(); - - /** - * @brief Open the Action Search overlay. - */ - 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(); - - /** - * @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(); - - /** - * @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(); - - /** - * @brief Opens the Preferences dialog - */ - void open_preferences(); - - /** - * @brief Clear the recent projects list - * - * Also saves the cleared recent projects to the config file making it permanent. - */ - 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. - * In most cases, you'll want open_project() to be end-user friendly. - * - * @param fn - * - * The URL to the project to load. - * - * @param autorecovery - * - * Whether this file is an autorecovery file. If it is, after the load Olive will set the project URL to a new file - * 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); - - /** - * @brief Create a LoadDialog and start a LoadThread to load data from a project - * - * Loads data from an Olive project file creating a LoadDialog to show visual information and a LoadThread to load - * outside of the main/GUI thread. - * - * All project loading functions eventually lead to this one and there's no reason to use it directly. Instead use - * one of the following functions: - * - * * OpenProject() - to check if the current project can be closed and prompt the user for the new project file - * * OpenProjectWorker() - if you already have the filename and wish to close the current project and open it - * * ImportProject() - to import a project file into this one, effectively merging them both - * - * @param fn - * - * The URL of the project file to open - * - * @param autorecovery - * - * TRUE if this file is an autorecovery file, in which case it's loaded slightly differently - * - * @param clear - * - * TRUE if the current project should be closed before opening, FALSE if the project should be imported into the - * currently open one. - */ - void LoadProject(const QString& fn, bool autorecovery); - - /** - * @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(); - - /** - * @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; - - /** - * @brief Regular interval to save an auto-recovery project. - */ - 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; - - /** - * @brief Internal translator object that interfaces with the currently loaded language file - */ - 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 - * an autorecovery file is made. Provides an extra layer of abstraction from the application "modified" state to - * 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; - - /** - * @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; - -/** - * @brief Currently active project filename - * - * Filename for the currently active project. Empty means the file has not - * been saved yet. - */ -extern QString ActiveProjectFilename; - -/** - * @brief Current application name - */ -extern QString AppName; - -/** - * Rational type for - */ -} - -#endif // OLIVEGLOBAL_H +/*** + + 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 OLIVEGLOBAL_H +#define OLIVEGLOBAL_H + +#include + +#include +#include +#include + +#include "undo/undo.h" +#include "rendering/pixelformats.h" + +/** + * @brief The Olive Global class + * + * A resource for various global functions used throughout Olive. + */ +class OliveGlobal : public QObject { + Q_OBJECT +public: + /** + * @brief OliveGlobal Constructor + * + * Creates Olive Global object. Also sets some default runtime settings and the application name. + */ + 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(); + + /** + * @brief Change the current active project filename + * + * Triggered to change the current active project filename. Call this before calling any internal project + * saving or loading functions in order to set which file to work with (OliveGlobal::open_project() and + * OliveGlobal::save_project_as() do this automatically). Also updates the main window title to reflect the + * project filename. + * + * @param s + * + * 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); + + /** + * @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(); + + /** + * @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 Returns the "effective" bit depth for the composition pipeline + * + * Convenience function for using Config::playback_bit_depth or Config::export_bit_depth depending on the state of + * OliveGlobal::is_exporting() + */ + const olive::PixelFormat& effective_bit_depth(); + + /** + * @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 + * as necessary. + * + * The current functions are as follows: + * * Auto-recovery interval. Olive saves an auto-recovery just before exporting anyway and seeing as the user + * cannot make changes while rendering, there's no reason to continue saving auto-recovery files. + * * Audio device playback. Olive uses the same internal audio buffer for exporting as it does for playback, but + * this buffer does not need to be forwarded to the output device when exporting. + * + * @param rendering + * + * **TRUE** if Olive is about to export a video. **FALSE** if Olive has finished exporting. + */ + 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 + * technically controls whether to create autorecovery files as they'll only be generated if there are unsaved + * changes. + * + * @param modified + * + * TRUE if the project has been modified, FALSE if it has not. + */ + 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. + * This value is used to determine whether the currently open project has unsaved changes. + * + * @return + * + * TRUE if the project has been modified since the last save. + */ + 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 + * specified project once its finished initializing. + * + * @param s + * + * The URL of the project file to load. + */ + 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(); + + /** + * @brief (Re)load translation file from olive::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); + + /** + * @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(); + + /** + * @brief Redo user's last action + */ + 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(); + + /** + * @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 + * current playhead and ripple forward to make space for the clips in the clipboard. Can be considered + * 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(); + + /** + * @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(); + + /** + * @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(); + + /** + * @brief Import project from file + * + * Imports an Olive project into the current project, effectively merging them. + * + * @param fn + * + * The filename of the project to import. + */ + void ImportProject(const QString& fn); + + /** + * @brief Open recent project from list + * + * Triggers a project load from the internal recent projects list. + * + * @param index + * + * Index in the list of the project file to load + */ + 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 + * one. Also triggered by save_project() if the file hasn't been saved yet. + * + * @return **TRUE** if the user saved the project. **FALSE** if they cancelled out of the save file dialog. Useful + * 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(); + + /** + * @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 + * data. Calls save_project_as() if the file has not been saved before. + * + * @return **TRUE** if the project has been saved before and was successfully overwritten. Otherwise returns the + * 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(); + + /** + * @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, + * exiting Olive, etc.) If the project has unsaved changes, this function asks the user whether they want to save or + * not. If the user does, calls save_project() (which may in turn call save_project_as() if the project has never + * been saved). + * + * @return **TRUE** if the project can be closed. FALSE if not. If the project does NOT have unsaved changes, always + * 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(); + + /** + * @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(); + + /** + * @brief Open the About Olive dialog. + */ + void open_about_dialog(); + + /** + * @brief Open the Debug Log window. + */ + void open_debug_log(); + + /** + * @brief Open the Speed/Duration dialog. + */ + void open_speed_dialog(); + + /** + * @brief Open the auto-cut silence dialog. + */ + void open_autocut_silence_dialog(); + + /** + * @brief Open the Action Search overlay. + */ + 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(); + + /** + * @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(); + + /** + * @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(); + + /** + * @brief Opens the Preferences dialog + */ + void open_preferences(); + + /** + * @brief Clear the recent projects list + * + * Also saves the cleared recent projects to the config file making it permanent. + */ + 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. + * In most cases, you'll want open_project() to be end-user friendly. + * + * @param fn + * + * The URL to the project to load. + * + * @param autorecovery + * + * Whether this file is an autorecovery file. If it is, after the load Olive will set the project URL to a new file + * 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); + + /** + * @brief Create a LoadDialog and start a LoadThread to load data from a project + * + * Loads data from an Olive project file creating a LoadDialog to show visual information and a LoadThread to load + * outside of the main/GUI thread. + * + * All project loading functions eventually lead to this one and there's no reason to use it directly. Instead use + * one of the following functions: + * + * * OpenProject() - to check if the current project can be closed and prompt the user for the new project file + * * OpenProjectWorker() - if you already have the filename and wish to close the current project and open it + * * ImportProject() - to import a project file into this one, effectively merging them both + * + * @param fn + * + * The URL of the project file to open + * + * @param autorecovery + * + * TRUE if this file is an autorecovery file, in which case it's loaded slightly differently + * + * @param clear + * + * TRUE if the current project should be closed before opening, FALSE if the project should be imported into the + * currently open one. + */ + void LoadProject(const QString& fn, bool autorecovery); + + /** + * @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(); + + /** + * @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; + + /** + * @brief Regular interval to save an auto-recovery project. + */ + 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; + + /** + * @brief Internal translator object that interfaces with the currently loaded language file + */ + 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 + * an autorecovery file is made. Provides an extra layer of abstraction from the application "modified" state to + * 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; + + /** + * @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; + +/** + * @brief Currently active project filename + * + * Filename for the currently active project. Empty means the file has not + * been saved yet. + */ +extern QString ActiveProjectFilename; + +/** + * @brief Current application name + */ +extern QString AppName; + +/** + * Rational type for + */ +} + +#endif // OLIVEGLOBAL_H diff --git a/global/math.cpp b/global/math.cpp index c11a79d4b..09ff22431 100644 --- a/global/math.cpp +++ b/global/math.cpp @@ -1,107 +1,107 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "math.h" - -#include -#include - -#include "debug.h" - -int lerp(int a, int b, double t) { - return qRound(((1.0 - t) * a) + (t * b)); -} - -float float_lerp(float a, float b, float t) { - return ((1.0F - t) * a) + (t * b); -} - -double double_lerp(double a, double b, double t) { - return ((1.0 - t) * a) + (t * b); -} - -double quad_from_t(double a, double b, double c, double t) { - return qPow(1.0 - t, 2)*a + 2*(1.0 - t)*t*b + qPow(t, 2)*c; -} - -double quad_t_from_x(double x, double a, double b, double c) { - return (a - b + qSqrt(a*x + c*x - 2*b*x + qPow(b, 2) - a*c))/(a - 2*b + c); - // alt: return (a - b - qSqrt(a*x + c*x - 2*b*x + qPow(b, 2) - a*c))/(a - 2*b + c); -} - -double cubic_from_t(double a, double b, double c, double d, double t) { - return qPow(1.0 - t, 3)*a + 3*qPow(1.0 - t, 2)*t*b + 3*(1.0 - t)*qPow(t, 2)*c + qPow(t, 3)*d; -} - -double cubic_t_from_x(double x_target, double a, double b, double c, double d) { - double tolerance = 0.0001; - - double lower = 0.0; - double upper = 1.0; - - double percent = 0.5; - double x = cubic_from_t(a, b, c, d, percent); - - while (qAbs(x_target - x) > tolerance) { - if (x_target > x) { - lower = percent; - } else { - upper = percent; - } - - percent = (upper + lower) / 2.0; - x = cubic_from_t(a, b, c, d, percent); - } - - return percent; -} - -double amplitude_to_db(double amplitude) { - return (20.0*(qLn(amplitude)/qLn(10.0))); -} - -double db_to_amplitude(double db) { - return qPow(M_E, (db*qLn(10.0))/20.0); -} - -QRect fit_size_into_rect(const QRect &r, int width, int height) -{ - // Get aspect ratio of object we're fitting - double inner_ar = double(width) / double(height); - - // Get aspect ratio of rectangle - double rect_ar = double(r.width()) / double(r.height()); - - if (rect_ar > inner_ar) { - // The rect is wider than the object, so we'll be limiting by height and scaling by width - int new_width = qRound(r.height() * inner_ar); - return QRect(r.x() + (r.width() / 2 - new_width / 2), r.y(), new_width, r.height()); - } else { - // The rect is taller than the object, so we'll be limiting by width and scaling by height - int new_height = qRound(r.width() / inner_ar); - 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); -} +/*** + + 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 "math.h" + +#include +#include + +#include "debug.h" + +int lerp(int a, int b, double t) { + return qRound(((1.0 - t) * a) + (t * b)); +} + +float float_lerp(float a, float b, float t) { + return ((1.0F - t) * a) + (t * b); +} + +double double_lerp(double a, double b, double t) { + return ((1.0 - t) * a) + (t * b); +} + +double quad_from_t(double a, double b, double c, double t) { + return qPow(1.0 - t, 2)*a + 2*(1.0 - t)*t*b + qPow(t, 2)*c; +} + +double quad_t_from_x(double x, double a, double b, double c) { + return (a - b + qSqrt(a*x + c*x - 2*b*x + qPow(b, 2) - a*c))/(a - 2*b + c); + // alt: return (a - b - qSqrt(a*x + c*x - 2*b*x + qPow(b, 2) - a*c))/(a - 2*b + c); +} + +double cubic_from_t(double a, double b, double c, double d, double t) { + return qPow(1.0 - t, 3)*a + 3*qPow(1.0 - t, 2)*t*b + 3*(1.0 - t)*qPow(t, 2)*c + qPow(t, 3)*d; +} + +double cubic_t_from_x(double x_target, double a, double b, double c, double d) { + double tolerance = 0.0001; + + double lower = 0.0; + double upper = 1.0; + + double percent = 0.5; + double x = cubic_from_t(a, b, c, d, percent); + + while (qAbs(x_target - x) > tolerance) { + if (x_target > x) { + lower = percent; + } else { + upper = percent; + } + + percent = (upper + lower) / 2.0; + x = cubic_from_t(a, b, c, d, percent); + } + + return percent; +} + +double amplitude_to_db(double amplitude) { + return (20.0*(qLn(amplitude)/qLn(10.0))); +} + +double db_to_amplitude(double db) { + return qPow(M_E, (db*qLn(10.0))/20.0); +} + +QRect fit_size_into_rect(const QRect &r, int width, int height) +{ + // Get aspect ratio of object we're fitting + double inner_ar = double(width) / double(height); + + // Get aspect ratio of rectangle + double rect_ar = double(r.width()) / double(r.height()); + + if (rect_ar > inner_ar) { + // The rect is wider than the object, so we'll be limiting by height and scaling by width + int new_width = qRound(r.height() * inner_ar); + return QRect(r.x() + (r.width() / 2 - new_width / 2), r.y(), new_width, r.height()); + } else { + // The rect is taller than the object, so we'll be limiting by width and scaling by height + int new_height = qRound(r.width() / inner_ar); + 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 721e4453e..04f2bbb9b 100644 --- a/global/math.h +++ b/global/math.h @@ -1,48 +1,48 @@ -/*** - - 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 MATH_H -#define MATH_H - -#include - -int lerp(int a, int b, double t); -float float_lerp(float a, float b, float t); -double double_lerp(double a, double b, double t); -double quad_from_t(double a, double b, double c, double t); -double quad_t_from_x(double x, double a, double b, double c); -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 +/*** + + 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 MATH_H +#define MATH_H + +#include + +int lerp(int a, int b, double t); +float float_lerp(float a, float b, float t); +double double_lerp(double a, double b, double t); +double quad_from_t(double a, double b, double c, double t); +double quad_t_from_x(double x, double a, double b, double c); +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 3c0eebb93..56c8ca148 100644 --- a/global/path.cpp +++ b/global/path.cpp @@ -1,122 +1,122 @@ -/*** - - 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 "path.h" - -#include -#include -#include -#include -#include - -#include "debug.h" - -QString get_app_path() { - return QCoreApplication::applicationDirPath(); -} - -QDir get_app_dir() { - return QDir(get_app_path()); -} - -QString get_data_path() { - QDir app_dir = get_app_dir(); - if (QFileInfo::exists(app_dir.filePath("portable"))) { - return app_dir.absolutePath(); - } else { - return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - } -} - -QDir get_data_dir() { - return QDir(get_data_path()); -} - -QString get_config_path() { - QDir app_dir = get_app_dir(); - if (QFileInfo::exists(app_dir.filePath("portable"))) { - return app_dir.absolutePath(); - } else { - return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - } -} - -QDir get_config_dir() { - return QDir(get_config_path()); -} - -QList get_effects_paths() { - // returns a list of the effects paths to search - - QList effects_paths; - - // get current app working directory - QDir app_dir = get_app_dir(); - - // "effects" subfolder in program folder - best for Windows - effects_paths.append(app_dir.filePath("effects")); - - // "Effects" folder one level above the program's directory - best for Mac - effects_paths.append(app_dir.filePath("../Effects")); - - // folder in share folder - best for Linux - effects_paths.append(app_dir.filePath("../share/olive-editor/effects")); - - // user path - best for linux - effects_paths.append(QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("effects")); - - // Olive will also accept a manually provided folder with an environment variable - QString env_path(qgetenv("OLIVE_EFFECTS_PATH")); - if (!env_path.isEmpty()) effects_paths.append(env_path); - - return effects_paths; -} - -QString get_file_hash(const QString& filename) { - QFileInfo file_info(filename); - - QString cache_file = filename.mid(filename.lastIndexOf('/')+1) - + QString::number(file_info.size()) - + QString::number(file_info.lastModified().toMSecsSinceEpoch()); - - return QCryptographicHash::hash(cache_file.toUtf8(), QCryptographicHash::Md5).toHex(); -} - -QList get_language_paths() { - QList language_paths; - - // get current app working directory - QDir app_dir = get_app_dir(); - - // subfolder in program folder - best for Windows (or compiling+running from source dir) - language_paths.append(app_dir.filePath("ts")); - - // folder one level above the program's directory - best for Mac - language_paths.append(app_dir.filePath("../Translations")); - - // folder in share folder - best for Linux - language_paths.append(app_dir.filePath("../share/olive-editor/ts")); - - // Olive will also accept a manually provided folder with an environment variable - QString env_path(qgetenv("OLIVE_LANG_PATH")); - if (!env_path.isEmpty()) language_paths.append(env_path); - - return language_paths; -} +/*** + + 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 "path.h" + +#include +#include +#include +#include +#include + +#include "debug.h" + +QString get_app_path() { + return QCoreApplication::applicationDirPath(); +} + +QDir get_app_dir() { + return QDir(get_app_path()); +} + +QString get_data_path() { + QDir app_dir = get_app_dir(); + if (QFileInfo::exists(app_dir.filePath("portable"))) { + return app_dir.absolutePath(); + } else { + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + } +} + +QDir get_data_dir() { + return QDir(get_data_path()); +} + +QString get_config_path() { + QDir app_dir = get_app_dir(); + if (QFileInfo::exists(app_dir.filePath("portable"))) { + return app_dir.absolutePath(); + } else { + return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + } +} + +QDir get_config_dir() { + return QDir(get_config_path()); +} + +QList get_effects_paths() { + // returns a list of the effects paths to search + + QList effects_paths; + + // get current app working directory + QDir app_dir = get_app_dir(); + + // "effects" subfolder in program folder - best for Windows + effects_paths.append(app_dir.filePath("effects")); + + // "Effects" folder one level above the program's directory - best for Mac + effects_paths.append(app_dir.filePath("../Effects")); + + // folder in share folder - best for Linux + effects_paths.append(app_dir.filePath("../share/olive-editor/effects")); + + // user path - best for linux + effects_paths.append(QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("effects")); + + // Olive will also accept a manually provided folder with an environment variable + QString env_path(qgetenv("OLIVE_EFFECTS_PATH")); + if (!env_path.isEmpty()) effects_paths.append(env_path); + + return effects_paths; +} + +QString get_file_hash(const QString& filename) { + QFileInfo file_info(filename); + + QString cache_file = filename.mid(filename.lastIndexOf('/')+1) + + QString::number(file_info.size()) + + QString::number(file_info.lastModified().toMSecsSinceEpoch()); + + return QCryptographicHash::hash(cache_file.toUtf8(), QCryptographicHash::Md5).toHex(); +} + +QList get_language_paths() { + QList language_paths; + + // get current app working directory + QDir app_dir = get_app_dir(); + + // subfolder in program folder - best for Windows (or compiling+running from source dir) + language_paths.append(app_dir.filePath("ts")); + + // folder one level above the program's directory - best for Mac + language_paths.append(app_dir.filePath("../Translations")); + + // folder in share folder - best for Linux + language_paths.append(app_dir.filePath("../share/olive-editor/ts")); + + // Olive will also accept a manually provided folder with an environment variable + QString env_path(qgetenv("OLIVE_LANG_PATH")); + if (!env_path.isEmpty()) language_paths.append(env_path); + + return language_paths; +} diff --git a/global/path.h b/global/path.h index f3ea5a659..764e5e150 100644 --- a/global/path.h +++ b/global/path.h @@ -1,38 +1,38 @@ -/*** - - 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 PATH_H -#define PATH_H - -#include -#include - -QString get_app_path(); -QString get_data_path(); -QDir get_data_dir(); -QString get_config_path(); -QDir get_config_dir(); -QList get_effects_paths(); -QList get_language_paths(); - -// generate hash algorithm used to uniquely identify files -QString get_file_hash(const QString& filename); - -#endif // PATH_H +/*** + + 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 PATH_H +#define PATH_H + +#include +#include + +QString get_app_path(); +QString get_data_path(); +QDir get_data_dir(); +QString get_config_path(); +QDir get_config_dir(); +QList get_effects_paths(); +QList get_language_paths(); + +// generate hash algorithm used to uniquely identify files +QString get_file_hash(const QString& filename); + +#endif // PATH_H diff --git a/global/rational.cpp b/global/rational.cpp index 37d923575..e6df7a871 100644 --- a/global/rational.cpp +++ b/global/rational.cpp @@ -1,377 +1,377 @@ -#include "rational.h" - -rational::rational(const int64_t &numerator) : - numerator_(numerator), - denominator_(1) -{ - if (numerator_ == 0) { - denominator_ = 0; - } -} - -rational::rational(const int64_t &numerator, const int64_t &denominator) : - numerator_(numerator), - denominator_(denominator) -{ - if (denominator_ != 0) { - - if (numerator_ != 0) { - FixSigns(); - Reduce(); - } else { - denominator_ = 0; - } - - } else { - numerator_ = 0; - } -} - -rational::rational(const AVRational &r) : - numerator_(r.num), - denominator_(r.den) -{ -} - -rational::rational(const rational &r) : - numerator_(r.numerator_), - denominator_(r.denominator_) -{ -} - -const rational &rational::operator=(const rational &r) -{ - if (this != &r) { - numerator_ = r.numerator_; - denominator_ = r.denominator_; - } - - return *this; -} - -const rational &rational::operator+=(const rational &r) -{ - if (numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { - numerator_ = 0; - denominator_ = 0; - } else { - if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { - // The other rational == 0, no addition needs to be done - } else { - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) - { - numerator_ = r.numerator_; - denominator_ = r.denominator_; - } - else - { - numerator_ = (numerator_ * r.denominator_) + (r.numerator_ * denominator_); - denominator_ = denominator_ * r.denominator_; - FixSigns(); - Reduce(); - } - } - } - return *this; -} - -const rational &rational::operator-=(const rational &r) -{ - if(numerator_ * denominator_ == 0 && r.numerator_ * r.numerator_ == 0) { - numerator_ = 0; - denominator_ = 0; - } else { - if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { - - } else { - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { - numerator_ = -(r.numerator_); - denominator_ = r.denominator_; - } else { - numerator_ = (numerator_ * r.denominator_) - (r.numerator_ * denominator_); - numerator_ = denominator_ * r.denominator_; - FixSigns(); - Reduce(); - } - } - } - return *this; -} - -const rational &rational::operator/=(const rational &r) -{ - numerator_ = numerator_ * r.denominator_; - denominator_ = denominator_ * r.numerator_; - FixSigns(); - Reduce(); - return *this; -} - -const rational &rational::operator*=(const rational &r) -{ - numerator_ = numerator_ * r.numerator_; - denominator_ = denominator_ * r.denominator_; - FixSigns(); - Reduce(); - return *this; -} - -rational rational::operator+(const rational &r) const -{ - rational result(*this); - result += r; - return result; -} - -rational rational::operator-(const rational &r) const -{ - rational result(*this); - result -= r; - return result; -} - -rational rational::operator/(const rational &r) const -{ - rational result(*this); - result /= r; - return result; -} - -rational rational::operator*(const rational &r) const -{ - rational result(*this); - result *= r; - return result; -} - -bool rational::operator<(const rational &r) const -{ - if (numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { - return false; - } else { - if (numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { - if(numerator_ * denominator_ < 0) - return true; - else - return false; - } - else { - if (numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { - if (r.numerator_ * r.denominator_ < 0) { - return false; - } else { - return true; - } - } else { - return ((numerator_ * r.denominator_) < (denominator_ * r.numerator_)); - } - } - } -} - -bool rational::operator<=(const rational &r) const -{ - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { - return true; - } else { - if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { - if(numerator_ * denominator_ < 0) { - return true; - } else { - return false; - } - } else { - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { - if(r.numerator_ * r.denominator_ < 0) { - return false; - } else { - return true; - } - } else { - return ((numerator_ * r.denominator_) <= (denominator_ * r.numerator_)); - } - } - } -} - -bool rational::operator>(const rational &r) const -{ - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { - return false; - } else { - if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { - if(numerator_ * denominator_ > 0) { - return true; - } else { - return false; - } - } else { - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { - if(r.numerator_ * r.denominator_ > 0) { - return false; - } else { - return true; - } - } else { - return ((numerator_ * r.denominator_) > (denominator_ * r.numerator_)); - } - } - } -} - -bool rational::operator>=(const rational &r) const -{ - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { - return true; - } else { - if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { - if(numerator_ * denominator_ > 0) { - return true; - } else { - return false; - } - } else { - if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { - if(r.numerator_ * r.denominator_ > 0) { - return false; - } else { - return true; - } - } else { - return ((numerator_ * r.denominator_) >= (denominator_ * r.numerator_)); - } - } - } -} - -bool rational::operator==(const rational &r) const -{ - return (numerator_ == r.numerator_ && denominator_ == r.denominator_); -} - -bool rational::operator!=(const rational &r) const -{ - return (numerator_ != r.numerator_) || (denominator_ != r.denominator_); -} - -const rational &rational::operator++() -{ - numerator_ += denominator_; - return *this; -} - -rational rational::operator++(int) -{ - rational tmp = *this; - numerator_ += denominator_; - return tmp; -} - -const rational &rational::operator--() -{ - numerator_ -= denominator_; - return *this; -} - -rational rational::operator--(int) -{ - rational tmp; - numerator_ -= denominator_; - return tmp; -} - -const rational &rational::operator+() const -{ - return *this; -} - -rational rational::operator-() const -{ - return rational(numerator_, -denominator_); -} - -bool rational::operator!() const -{ - return !numerator_; -} - -double rational::ToDouble() const -{ - if (denominator_ == 0) { - return 0; - } else { - return static_cast(numerator_)/static_cast(denominator_); - } -} - -void rational::FixSigns() -{ - // Ensures denominator is always positive (while numerator can be positive or negative) - if(denominator_ < 0) { - denominator_ = -denominator_; - numerator_ = -numerator_; - } - - // Ensures if either are zero, they're both zero - if(numerator_ == 0 || denominator_ == 0) { - numerator_ = 0; - denominator_ = 0; - } -} - -void rational::Reduce() -{ - int64_t d = 1; - - if(denominator_ != 0 && numerator_ != 0) { - d = GreatestCommonDenominator(numerator_, denominator_); - } - - if(d > 1) { - numerator_ /= d; - denominator_ /= d; - } -} - -int64_t rational::GreatestCommonDenominator(const int64_t& x, const int64_t& y) -{ - if (y == 0) { - return x; - } else { - int64_t tmp = x % y; - return GreatestCommonDenominator(y, tmp); - } -} - -std::ostream &operator<<(std::ostream &out, const rational &value) -{ - out << value.numerator_; - if(value.denominator_ != 1) - { - out << '/' << value.denominator_; - return out; - } - return out; -} - -std::istream &operator>>(std::istream &in, rational &value) -{ - in >> value.numerator_; - value.denominator_ = 1; - - char ch; - in.get(ch); - - if(!in.eof()) - { - if(ch == '/') - { - in >> value.denominator_; - value.FixSigns(); - value.Reduce(); - } - else - in.putback(ch); - } - return in; -} +#include "rational.h" + +rational::rational(const int64_t &numerator) : + numerator_(numerator), + denominator_(1) +{ + if (numerator_ == 0) { + denominator_ = 0; + } +} + +rational::rational(const int64_t &numerator, const int64_t &denominator) : + numerator_(numerator), + denominator_(denominator) +{ + if (denominator_ != 0) { + + if (numerator_ != 0) { + FixSigns(); + Reduce(); + } else { + denominator_ = 0; + } + + } else { + numerator_ = 0; + } +} + +rational::rational(const AVRational &r) : + numerator_(r.num), + denominator_(r.den) +{ +} + +rational::rational(const rational &r) : + numerator_(r.numerator_), + denominator_(r.denominator_) +{ +} + +const rational &rational::operator=(const rational &r) +{ + if (this != &r) { + numerator_ = r.numerator_; + denominator_ = r.denominator_; + } + + return *this; +} + +const rational &rational::operator+=(const rational &r) +{ + if (numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { + numerator_ = 0; + denominator_ = 0; + } else { + if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { + // The other rational == 0, no addition needs to be done + } else { + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) + { + numerator_ = r.numerator_; + denominator_ = r.denominator_; + } + else + { + numerator_ = (numerator_ * r.denominator_) + (r.numerator_ * denominator_); + denominator_ = denominator_ * r.denominator_; + FixSigns(); + Reduce(); + } + } + } + return *this; +} + +const rational &rational::operator-=(const rational &r) +{ + if(numerator_ * denominator_ == 0 && r.numerator_ * r.numerator_ == 0) { + numerator_ = 0; + denominator_ = 0; + } else { + if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { + + } else { + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { + numerator_ = -(r.numerator_); + denominator_ = r.denominator_; + } else { + numerator_ = (numerator_ * r.denominator_) - (r.numerator_ * denominator_); + numerator_ = denominator_ * r.denominator_; + FixSigns(); + Reduce(); + } + } + } + return *this; +} + +const rational &rational::operator/=(const rational &r) +{ + numerator_ = numerator_ * r.denominator_; + denominator_ = denominator_ * r.numerator_; + FixSigns(); + Reduce(); + return *this; +} + +const rational &rational::operator*=(const rational &r) +{ + numerator_ = numerator_ * r.numerator_; + denominator_ = denominator_ * r.denominator_; + FixSigns(); + Reduce(); + return *this; +} + +rational rational::operator+(const rational &r) const +{ + rational result(*this); + result += r; + return result; +} + +rational rational::operator-(const rational &r) const +{ + rational result(*this); + result -= r; + return result; +} + +rational rational::operator/(const rational &r) const +{ + rational result(*this); + result /= r; + return result; +} + +rational rational::operator*(const rational &r) const +{ + rational result(*this); + result *= r; + return result; +} + +bool rational::operator<(const rational &r) const +{ + if (numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { + return false; + } else { + if (numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { + if(numerator_ * denominator_ < 0) + return true; + else + return false; + } + else { + if (numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { + if (r.numerator_ * r.denominator_ < 0) { + return false; + } else { + return true; + } + } else { + return ((numerator_ * r.denominator_) < (denominator_ * r.numerator_)); + } + } + } +} + +bool rational::operator<=(const rational &r) const +{ + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { + return true; + } else { + if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { + if(numerator_ * denominator_ < 0) { + return true; + } else { + return false; + } + } else { + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { + if(r.numerator_ * r.denominator_ < 0) { + return false; + } else { + return true; + } + } else { + return ((numerator_ * r.denominator_) <= (denominator_ * r.numerator_)); + } + } + } +} + +bool rational::operator>(const rational &r) const +{ + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { + return false; + } else { + if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { + if(numerator_ * denominator_ > 0) { + return true; + } else { + return false; + } + } else { + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { + if(r.numerator_ * r.denominator_ > 0) { + return false; + } else { + return true; + } + } else { + return ((numerator_ * r.denominator_) > (denominator_ * r.numerator_)); + } + } + } +} + +bool rational::operator>=(const rational &r) const +{ + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ == 0) { + return true; + } else { + if(numerator_ * denominator_ != 0 && r.numerator_ * r.denominator_ == 0) { + if(numerator_ * denominator_ > 0) { + return true; + } else { + return false; + } + } else { + if(numerator_ * denominator_ == 0 && r.numerator_ * r.denominator_ != 0) { + if(r.numerator_ * r.denominator_ > 0) { + return false; + } else { + return true; + } + } else { + return ((numerator_ * r.denominator_) >= (denominator_ * r.numerator_)); + } + } + } +} + +bool rational::operator==(const rational &r) const +{ + return (numerator_ == r.numerator_ && denominator_ == r.denominator_); +} + +bool rational::operator!=(const rational &r) const +{ + return (numerator_ != r.numerator_) || (denominator_ != r.denominator_); +} + +const rational &rational::operator++() +{ + numerator_ += denominator_; + return *this; +} + +rational rational::operator++(int) +{ + rational tmp = *this; + numerator_ += denominator_; + return tmp; +} + +const rational &rational::operator--() +{ + numerator_ -= denominator_; + return *this; +} + +rational rational::operator--(int) +{ + rational tmp; + numerator_ -= denominator_; + return tmp; +} + +const rational &rational::operator+() const +{ + return *this; +} + +rational rational::operator-() const +{ + return rational(numerator_, -denominator_); +} + +bool rational::operator!() const +{ + return !numerator_; +} + +double rational::ToDouble() const +{ + if (denominator_ == 0) { + return 0; + } else { + return static_cast(numerator_)/static_cast(denominator_); + } +} + +void rational::FixSigns() +{ + // Ensures denominator is always positive (while numerator can be positive or negative) + if(denominator_ < 0) { + denominator_ = -denominator_; + numerator_ = -numerator_; + } + + // Ensures if either are zero, they're both zero + if(numerator_ == 0 || denominator_ == 0) { + numerator_ = 0; + denominator_ = 0; + } +} + +void rational::Reduce() +{ + int64_t d = 1; + + if(denominator_ != 0 && numerator_ != 0) { + d = GreatestCommonDenominator(numerator_, denominator_); + } + + if(d > 1) { + numerator_ /= d; + denominator_ /= d; + } +} + +int64_t rational::GreatestCommonDenominator(const int64_t& x, const int64_t& y) +{ + if (y == 0) { + return x; + } else { + int64_t tmp = x % y; + return GreatestCommonDenominator(y, tmp); + } +} + +std::ostream &operator<<(std::ostream &out, const rational &value) +{ + out << value.numerator_; + if(value.denominator_ != 1) + { + out << '/' << value.denominator_; + return out; + } + return out; +} + +std::istream &operator>>(std::istream &in, rational &value) +{ + in >> value.numerator_; + value.denominator_ = 1; + + char ch; + in.get(ch); + + if(!in.eof()) + { + if(ch == '/') + { + in >> value.denominator_; + value.FixSigns(); + value.Reduce(); + } + else + in.putback(ch); + } + return in; +} diff --git a/global/rational.h b/global/rational.h index 0a44ec08b..4a314757c 100644 --- a/global/rational.h +++ b/global/rational.h @@ -1,65 +1,65 @@ -#ifndef RATIONAL_H -#define RATIONAL_H - -// Adapted from https://github.com/angularadam/Qt-Class-rational used in compliance with the GNU General Public License - -#include - -extern "C" { - #include -} - -class rational { -public: - // Constructors - rational(const int64_t& numerator = 0); - rational(const int64_t& numerator, const int64_t& denominator); - rational(const AVRational& r); // Auto-convert from an FFmpeg AVRational - rational(const rational& r); - - // Assignment Operators - const rational& operator=(const rational& r); - const rational& operator+=(const rational& r); - const rational& operator-=(const rational& r); - const rational& operator/=(const rational& r); - const rational& operator*=(const rational& r); - - // Math Operators - rational operator+(const rational& r) const; - rational operator-(const rational& r) const; - rational operator/(const rational& r) const; - rational operator*(const rational& r) const; - - // Relational and Equality Operators - bool operator<(const rational &r) const; - bool operator<=(const rational &r) const; - bool operator>(const rational &r) const; - bool operator>=(const rational &r) const; - bool operator==(const rational &r) const; - bool operator!=(const rational &r) const; - - //Unary operators - const rational& operator++(); //prefix - rational operator++(int); //postfix - const rational& operator--(); //prefix - rational operator--(int); //postfix - const rational& operator+() const; - rational operator-() const; - bool operator!() const; - - // IO - friend std::ostream& operator<<(std::ostream &out, const rational& value); - friend std::istream& operator>>(std::istream &in, rational& value); - - // Convert to double - double ToDouble() const; -private: - int64_t numerator_; - int64_t denominator_; - - void FixSigns(); - void Reduce(); - int64_t GreatestCommonDenominator(const int64_t &x, const int64_t &y); -}; - -#endif // RATIONAL_H +#ifndef RATIONAL_H +#define RATIONAL_H + +// Adapted from https://github.com/angularadam/Qt-Class-rational used in compliance with the GNU General Public License + +#include + +extern "C" { + #include +} + +class rational { +public: + // Constructors + rational(const int64_t& numerator = 0); + rational(const int64_t& numerator, const int64_t& denominator); + rational(const AVRational& r); // Auto-convert from an FFmpeg AVRational + rational(const rational& r); + + // Assignment Operators + const rational& operator=(const rational& r); + const rational& operator+=(const rational& r); + const rational& operator-=(const rational& r); + const rational& operator/=(const rational& r); + const rational& operator*=(const rational& r); + + // Math Operators + rational operator+(const rational& r) const; + rational operator-(const rational& r) const; + rational operator/(const rational& r) const; + rational operator*(const rational& r) const; + + // Relational and Equality Operators + bool operator<(const rational &r) const; + bool operator<=(const rational &r) const; + bool operator>(const rational &r) const; + bool operator>=(const rational &r) const; + bool operator==(const rational &r) const; + bool operator!=(const rational &r) const; + + //Unary operators + const rational& operator++(); //prefix + rational operator++(int); //postfix + const rational& operator--(); //prefix + rational operator--(int); //postfix + const rational& operator+() const; + rational operator-() const; + bool operator!() const; + + // IO + friend std::ostream& operator<<(std::ostream &out, const rational& value); + friend std::istream& operator>>(std::istream &in, rational& value); + + // Convert to double + double ToDouble() const; +private: + int64_t numerator_; + int64_t denominator_; + + void FixSigns(); + void Reduce(); + int64_t GreatestCommonDenominator(const int64_t &x, const int64_t &y); +}; + +#endif // RATIONAL_H diff --git a/global/timing.cpp b/global/timing.cpp index dbf876b72..9d0ae84db 100644 --- a/global/timing.cpp +++ b/global/timing.cpp @@ -1,169 +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') - ); -} +#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 index 1744b8c92..c42a45b5c 100644 --- a/global/timing.h +++ b/global/timing.h @@ -1,137 +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 +#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 6397b8413..5c397b7e0 100644 --- a/main.cpp +++ b/main.cpp @@ -1,167 +1,167 @@ -/*** - - 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 -#include - -#include "decoders/pixelformatconverter.h" -#include "dialogs/crashdialog.h" -#include "global/crashhandler.h" -#include "global/debug.h" -#include "global/config.h" -#include "global/global.h" -#include "panels/timeline.h" -#include "rendering/pixelformats.h" -#include "ui/mediaiconservice.h" -#include "ui/mainwindow.h" - -extern "C" { -#include -#include -} - -int main(int argc, char *argv[]) { -#ifdef __GNUC__ - signal(SIGSEGV, handler); -#endif - - olive::Global = std::unique_ptr(new OliveGlobal); - - bool launch_fullscreen = false; - QString load_proj; - - bool use_internal_logger = true; - - if (argc > 1) { - for (int i=1;i\tSet an external language file to use\n" - "\n" - "Environment Variables:\n" - "\tOLIVE_EFFECTS_PATH\tSpecify a path to search for GLSL shader effects\n" - "\tFREI0R_PATH\t\tSpecify a path to search for Frei0r effects\n" - "\tOLIVE_LANG_PATH\t\tSpecify a path to search for translation files\n" - "\n", argv[0]); - return 0; - } else if (!strcmp(argv[i], "--fullscreen") || !strcmp(argv[i], "-f")) { - launch_fullscreen = true; - } else if (!strcmp(argv[i], "--disable-shaders")) { - olive::runtime_config.shaders_are_enabled = false; - } else if (!strcmp(argv[i], "--no-debug")) { - use_internal_logger = false; - } else if (!strcmp(argv[i], "--translation")) { - if (i + 1 < argc && argv[i + 1][0] != '-') { - // load translation file - olive::runtime_config.external_translation_file = argv[i + 1]; - - i++; - } else { - printf("[ERROR] No translation file specified\n"); - return 1; - } - } else { - printf("[ERROR] Unknown argument '%s'\n", argv[1]); - return 1; - } - } else if (load_proj.isEmpty()) { - load_proj = argv[i]; - } - } - } - - if (use_internal_logger) { - qInstallMessageHandler(debug_message_handler); - } - - // Initialize ffmpeg subsystem - // (these have been deprecated in FFmpeg 4, but are still necessary for FFmpeg 3) -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) - av_register_all(); -#endif - -#if LIBAVFILTER_VERSION_INT < AV_VERSION_INT(7, 14, 100) - avfilter_register_all(); -#endif - - 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); - a.setWindowIcon(QIcon(":/icons/olive64.png")); - - // start media icon service (uses QPixmaps which require a QGuiApplication to have been created) - olive::media_icon_service = std::unique_ptr(new MediaIconService()); - - // set app name data - QCoreApplication::setOrganizationName("olivevideoeditor.org"); - QCoreApplication::setOrganizationDomain("olivevideoeditor.org"); - QCoreApplication::setApplicationName("Olive"); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) - QGuiApplication::setDesktopFileName("org.olivevideoeditor.Olive"); -#endif - - olive::crash_dialog = new CrashDialog(); - - MainWindow w(nullptr); - - // multiply track height constants by the current DPI scale - olive::timeline::MultiplyTrackSizesByDPI(); - - // set up rendering bit depths - olive::InitializePixelFormats(); - - // initialize pixel format converter - olive::pix_fmt_conv = new PixelFormatConverter(); - - // 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); - - if (!load_proj.isEmpty()) { - olive::Global->load_project_on_launch(load_proj); - } - if (launch_fullscreen) { - w.showFullScreen(); - } else { - w.showMaximized(); - } - - return a.exec(); -} +/*** + + 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 +#include + +#include "decoders/pixelformatconverter.h" +#include "dialogs/crashdialog.h" +#include "global/crashhandler.h" +#include "global/debug.h" +#include "global/config.h" +#include "global/global.h" +#include "panels/timeline.h" +#include "rendering/pixelformats.h" +#include "ui/mediaiconservice.h" +#include "ui/mainwindow.h" + +extern "C" { +#include +#include +} + +int main(int argc, char *argv[]) { +#ifdef __GNUC__ + signal(SIGSEGV, handler); +#endif + + olive::Global = std::unique_ptr(new OliveGlobal); + + bool launch_fullscreen = false; + QString load_proj; + + bool use_internal_logger = true; + + if (argc > 1) { + for (int i=1;i\tSet an external language file to use\n" + "\n" + "Environment Variables:\n" + "\tOLIVE_EFFECTS_PATH\tSpecify a path to search for GLSL shader effects\n" + "\tFREI0R_PATH\t\tSpecify a path to search for Frei0r effects\n" + "\tOLIVE_LANG_PATH\t\tSpecify a path to search for translation files\n" + "\n", argv[0]); + return 0; + } else if (!strcmp(argv[i], "--fullscreen") || !strcmp(argv[i], "-f")) { + launch_fullscreen = true; + } else if (!strcmp(argv[i], "--disable-shaders")) { + olive::runtime_config.shaders_are_enabled = false; + } else if (!strcmp(argv[i], "--no-debug")) { + use_internal_logger = false; + } else if (!strcmp(argv[i], "--translation")) { + if (i + 1 < argc && argv[i + 1][0] != '-') { + // load translation file + olive::runtime_config.external_translation_file = argv[i + 1]; + + i++; + } else { + printf("[ERROR] No translation file specified\n"); + return 1; + } + } else { + printf("[ERROR] Unknown argument '%s'\n", argv[1]); + return 1; + } + } else if (load_proj.isEmpty()) { + load_proj = argv[i]; + } + } + } + + if (use_internal_logger) { + qInstallMessageHandler(debug_message_handler); + } + + // Initialize ffmpeg subsystem + // (these have been deprecated in FFmpeg 4, but are still necessary for FFmpeg 3) +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) + av_register_all(); +#endif + +#if LIBAVFILTER_VERSION_INT < AV_VERSION_INT(7, 14, 100) + avfilter_register_all(); +#endif + + 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); + a.setWindowIcon(QIcon(":/icons/olive64.png")); + + // start media icon service (uses QPixmaps which require a QGuiApplication to have been created) + olive::media_icon_service = std::unique_ptr(new MediaIconService()); + + // set app name data + QCoreApplication::setOrganizationName("olivevideoeditor.org"); + QCoreApplication::setOrganizationDomain("olivevideoeditor.org"); + QCoreApplication::setApplicationName("Olive"); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) + QGuiApplication::setDesktopFileName("org.olivevideoeditor.Olive"); +#endif + + olive::crash_dialog = new CrashDialog(); + + MainWindow w(nullptr); + + // multiply track height constants by the current DPI scale + olive::timeline::MultiplyTrackSizesByDPI(); + + // set up rendering bit depths + olive::InitializePixelFormats(); + + // initialize pixel format converter + olive::pix_fmt_conv = new PixelFormatConverter(); + + // 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); + + if (!load_proj.isEmpty()) { + olive::Global->load_project_on_launch(load_proj); + } + if (launch_fullscreen) { + w.showFullScreen(); + } else { + w.showMaximized(); + } + + return a.exec(); +} diff --git a/nodes/nodeio.cpp b/nodes/nodeio.cpp index 6f31ee53e..7bfd63083 100644 --- a/nodes/nodeio.cpp +++ b/nodes/nodeio.cpp @@ -1,414 +1,414 @@ -/*** - - 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 "nodeio.h" - -#include -#include -#include - -#include "undo/undo.h" -#include "undo/undostack.h" -#include "timeline/clip.h" -#include "timeline/sequence.h" -#include "panels/panels.h" -#include "panels/effectcontrols.h" -#include "panels/viewer.h" -#include "panels/grapheditor.h" -#include "nodes/oldeffectnode.h" -#include "ui/viewerwidget.h" -#include "ui/keyframenavigator.h" -#include "ui/clickablelabel.h" - -NodeIO::NodeIO(Node *parent, - const QString &id, - const QString &name, - bool savable, - bool keyframable) : - QObject(parent), - id_(id), - name_(name), - keyframable_(keyframable), - keyframing_(false), - savable_(savable), - output_type_(olive::nodes::kInvalid) -{ - Q_ASSERT(parent != nullptr); - - parent->AddParameter(this); -} - -void NodeIO::AddField(EffectField *field) -{ - field->setParent(this); - - connect(field, SIGNAL(Clicked()), this, SIGNAL(Clicked())); - connect(field, SIGNAL(Changed()), this, SIGNAL(Changed())); - - fields_.append(field); -} - -void NodeIO::AddAcceptedNodeInput(olive::nodes::DataType type) -{ - Q_ASSERT(output_type_ == olive::nodes::kInvalid); - - accepted_inputs_.append(type); -} - -void NodeIO::ConnectEdge(NodeIO *output, NodeIO *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()) { - NodeIO* 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 NodeIO::DisconnectEdge(NodeEdgePtr edge) -{ - NodeIO* output = edge->output(); - NodeIO* input = edge->input(); - - output->node_edges_.removeAll(edge); - input->node_edges_.removeAll(edge); - - emit output->EdgesChanged(); -} - -QVector NodeIO::edges() -{ - return node_edges_; -} - -bool NodeIO::IsKeyframing() { - return keyframing_; -} - -void NodeIO::SetKeyframingInternal(bool b) { - /* FIXME - if (GetParentEffect()->type() != EFFECT_TYPE_TRANSITION) { - keyframing_ = b; - emit KeyframingSetChanged(keyframing_); - } - */ -} - -bool NodeIO::IsSavable() -{ - return savable_; -} - -bool NodeIO::IsKeyframable() -{ - return keyframable_; -} - -QVariant NodeIO::GetValue() -{ - return GetValueAt(0); -} - -QVariant NodeIO::GetValueAt(double timecode) -{ - if (FieldCount() == 0) { - return data_; - } - - Q_ASSERT(FieldCount() == 1); - - return Field(0)->GetValueAt(timecode); -} - -void NodeIO::SetValue(const QVariant &value) -{ - SetValueAt(0, value); -} - -void NodeIO::SetValueAt(double timecode, const QVariant &value) -{ - if (FieldCount() == 0) { - data_ = value; - return; - } - - Q_ASSERT(FieldCount() == 1); - - Field(0)->SetValueAt(timecode, value); -} - -void NodeIO::SetEnabled(bool enabled) -{ - for (int i=0;iSetEnabled(enabled); - } -} - -void NodeIO::SetOutputDataType(olive::nodes::DataType type) -{ - Q_ASSERT(accepted_inputs_.isEmpty()); - - output_type_ = type; -} - -bool NodeIO::CanAcceptDataType(olive::nodes::DataType type) -{ - if (!IsNodeInput()) { - return false; - } - - return accepted_inputs_.contains(type); -} - -olive::nodes::DataType NodeIO::OutputDataType() -{ - return output_type_; -} - -bool NodeIO::IsNodeInput() -{ - return !accepted_inputs_.isEmpty(); -} - -bool NodeIO::IsNodeOutput() -{ - return output_type_ != olive::nodes::kInvalid; -} - -void NodeIO::SetKeyframingEnabled(bool enabled) { - if (enabled == keyframing_) { - return; - } - - if (enabled) { - - ComboAction* ca = new ComboAction(); - - // Enable keyframing setting on this row - ca->append(new SetIsKeyframing(this, true)); - - // Prepare each field's data to start keyframing - for (int i=0;iPrepareDataForKeyframing(true, ca); - } - - olive::undo_stack.push(ca); - - update_ui(false); - - } else { - - // Confirm with the user whether they really want to disable keyframing - if (QMessageBox::question(panel_effect_controls, - tr("Disable Keyframes"), - tr("Disabling keyframes will delete all current keyframes. " - "Are you sure you want to do this?"), - QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - - ComboAction* ca = new ComboAction(); - - // Prepare each field's data to stop keyframing - for (int i=0;iPrepareDataForKeyframing(false, ca); - } - - // Disable keyframing setting on this row - ca->append(new SetIsKeyframing(this, false)); - - olive::undo_stack.push(ca); - - update_ui(false); - - } else { - - SetKeyframingInternal(true); - - } - } -} - -void NodeIO::GoToPreviousKeyframe() { - /* FIXME - long key = LONG_MIN; - Clip* c = GetParentEffect()->parent_clip; - 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(); - - // Loop through all of this row's fields - for (int i=0;ikeyframes.size();j++) { - long comp = f->keyframes.at(j).time + time_adjustment; - - // Get the closest keyframe - if (comp < sequence_playhead) { - key = qMax(comp, key); - } - } - } - - // If we found a keyframe less than the playhead, jump to it - if (key != LONG_MIN) panel_sequence_viewer->seek(key); - */ -} - -void NodeIO::ToggleKeyframe() { - /* FIXME - Clip* c = GetParentEffect()->parent_clip; - 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(); - - QVector key_fields; - QVector key_field_index; - - // See if any keyframes on any fields are at the current time - for (int j=0;jkeyframes.size();i++) { - long comp = f->keyframes.at(i).time + time_adjustment; - - if (comp == sequence_playhead) { - - // Cache the keyframes if they are at the current time - key_fields.append(f); - key_field_index.append(i); - - } - } - } - - ComboAction* ca = new ComboAction(); - - if (key_fields.isEmpty()) { - - // If we didn't find any current keyframes, create one for each field - SetKeyframeOnAllFields(ca); - - } else { - - // If we DID find keyframes at this time, delete them - - QVector sorted_key_fields; - QVector sorted_key_field_index; - - // Since QVectors shift themselves when removing items, we need to sort these in reverse order - for (int i=0;iappend(new KeyframeDelete(sorted_key_fields.at(i), sorted_key_field_index.at(i))); - } - - } - - olive::undo_stack.push(ca); - update_ui(false); - */ -} - -void NodeIO::GoToNextKeyframe() { - /* FIXME - long key = LONG_MAX; - Clip* c = GetParentEffect()->parent_clip; - for (int i=0;ikeyframes.size();j++) { - long comp = f->keyframes.at(j).time - c->clip_in() + c->timeline_in(); - if (comp > c->track()->sequence()->playhead) { - key = qMin(comp, key); - } - } - } - if (key != LONG_MAX) panel_sequence_viewer->seek(key); - */ -} - -void NodeIO::FocusRow() { - panel_graph_editor->set_row(this); -} - -Node* NodeIO::ParentNode() { - return static_cast(parent()); -} - -void NodeIO::SetKeyframeOnAllFields(ComboAction* ca) { - for (int i=0;iSetValueAt(ParentNode()->Time(), field->GetValueAt(ParentNode()->Time())); - - kdc->SetNewKeyframes(); - ca->append(kdc); - } - - panel_effect_controls->update_keyframes(); -} - -const QString &NodeIO::name() { - return name_; -} - -EffectField* NodeIO::Field(int i) { - return fields_.at(i); -} - -int NodeIO::FieldCount() { - return fields_.size(); -} +/*** + + 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 "nodeio.h" + +#include +#include +#include + +#include "undo/undo.h" +#include "undo/undostack.h" +#include "timeline/clip.h" +#include "timeline/sequence.h" +#include "panels/panels.h" +#include "panels/effectcontrols.h" +#include "panels/viewer.h" +#include "panels/grapheditor.h" +#include "nodes/oldeffectnode.h" +#include "ui/viewerwidget.h" +#include "ui/keyframenavigator.h" +#include "ui/clickablelabel.h" + +NodeIO::NodeIO(Node *parent, + const QString &id, + const QString &name, + bool savable, + bool keyframable) : + QObject(parent), + id_(id), + name_(name), + keyframable_(keyframable), + keyframing_(false), + savable_(savable), + output_type_(olive::nodes::kInvalid) +{ + Q_ASSERT(parent != nullptr); + + parent->AddParameter(this); +} + +void NodeIO::AddField(EffectField *field) +{ + field->setParent(this); + + connect(field, SIGNAL(Clicked()), this, SIGNAL(Clicked())); + connect(field, SIGNAL(Changed()), this, SIGNAL(Changed())); + + fields_.append(field); +} + +void NodeIO::AddAcceptedNodeInput(olive::nodes::DataType type) +{ + Q_ASSERT(output_type_ == olive::nodes::kInvalid); + + accepted_inputs_.append(type); +} + +void NodeIO::ConnectEdge(NodeIO *output, NodeIO *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()) { + NodeIO* 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 NodeIO::DisconnectEdge(NodeEdgePtr edge) +{ + NodeIO* output = edge->output(); + NodeIO* input = edge->input(); + + output->node_edges_.removeAll(edge); + input->node_edges_.removeAll(edge); + + emit output->EdgesChanged(); +} + +QVector NodeIO::edges() +{ + return node_edges_; +} + +bool NodeIO::IsKeyframing() { + return keyframing_; +} + +void NodeIO::SetKeyframingInternal(bool b) { + /* FIXME + if (GetParentEffect()->type() != EFFECT_TYPE_TRANSITION) { + keyframing_ = b; + emit KeyframingSetChanged(keyframing_); + } + */ +} + +bool NodeIO::IsSavable() +{ + return savable_; +} + +bool NodeIO::IsKeyframable() +{ + return keyframable_; +} + +QVariant NodeIO::GetValue() +{ + return GetValueAt(0); +} + +QVariant NodeIO::GetValueAt(double timecode) +{ + if (FieldCount() == 0) { + return data_; + } + + Q_ASSERT(FieldCount() == 1); + + return Field(0)->GetValueAt(timecode); +} + +void NodeIO::SetValue(const QVariant &value) +{ + SetValueAt(0, value); +} + +void NodeIO::SetValueAt(double timecode, const QVariant &value) +{ + if (FieldCount() == 0) { + data_ = value; + return; + } + + Q_ASSERT(FieldCount() == 1); + + Field(0)->SetValueAt(timecode, value); +} + +void NodeIO::SetEnabled(bool enabled) +{ + for (int i=0;iSetEnabled(enabled); + } +} + +void NodeIO::SetOutputDataType(olive::nodes::DataType type) +{ + Q_ASSERT(accepted_inputs_.isEmpty()); + + output_type_ = type; +} + +bool NodeIO::CanAcceptDataType(olive::nodes::DataType type) +{ + if (!IsNodeInput()) { + return false; + } + + return accepted_inputs_.contains(type); +} + +olive::nodes::DataType NodeIO::OutputDataType() +{ + return output_type_; +} + +bool NodeIO::IsNodeInput() +{ + return !accepted_inputs_.isEmpty(); +} + +bool NodeIO::IsNodeOutput() +{ + return output_type_ != olive::nodes::kInvalid; +} + +void NodeIO::SetKeyframingEnabled(bool enabled) { + if (enabled == keyframing_) { + return; + } + + if (enabled) { + + ComboAction* ca = new ComboAction(); + + // Enable keyframing setting on this row + ca->append(new SetIsKeyframing(this, true)); + + // Prepare each field's data to start keyframing + for (int i=0;iPrepareDataForKeyframing(true, ca); + } + + olive::undo_stack.push(ca); + + update_ui(false); + + } else { + + // Confirm with the user whether they really want to disable keyframing + if (QMessageBox::question(panel_effect_controls, + tr("Disable Keyframes"), + tr("Disabling keyframes will delete all current keyframes. " + "Are you sure you want to do this?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + + ComboAction* ca = new ComboAction(); + + // Prepare each field's data to stop keyframing + for (int i=0;iPrepareDataForKeyframing(false, ca); + } + + // Disable keyframing setting on this row + ca->append(new SetIsKeyframing(this, false)); + + olive::undo_stack.push(ca); + + update_ui(false); + + } else { + + SetKeyframingInternal(true); + + } + } +} + +void NodeIO::GoToPreviousKeyframe() { + /* FIXME + long key = LONG_MIN; + Clip* c = GetParentEffect()->parent_clip; + 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(); + + // Loop through all of this row's fields + for (int i=0;ikeyframes.size();j++) { + long comp = f->keyframes.at(j).time + time_adjustment; + + // Get the closest keyframe + if (comp < sequence_playhead) { + key = qMax(comp, key); + } + } + } + + // If we found a keyframe less than the playhead, jump to it + if (key != LONG_MIN) panel_sequence_viewer->seek(key); + */ +} + +void NodeIO::ToggleKeyframe() { + /* FIXME + Clip* c = GetParentEffect()->parent_clip; + 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(); + + QVector key_fields; + QVector key_field_index; + + // See if any keyframes on any fields are at the current time + for (int j=0;jkeyframes.size();i++) { + long comp = f->keyframes.at(i).time + time_adjustment; + + if (comp == sequence_playhead) { + + // Cache the keyframes if they are at the current time + key_fields.append(f); + key_field_index.append(i); + + } + } + } + + ComboAction* ca = new ComboAction(); + + if (key_fields.isEmpty()) { + + // If we didn't find any current keyframes, create one for each field + SetKeyframeOnAllFields(ca); + + } else { + + // If we DID find keyframes at this time, delete them + + QVector sorted_key_fields; + QVector sorted_key_field_index; + + // Since QVectors shift themselves when removing items, we need to sort these in reverse order + for (int i=0;iappend(new KeyframeDelete(sorted_key_fields.at(i), sorted_key_field_index.at(i))); + } + + } + + olive::undo_stack.push(ca); + update_ui(false); + */ +} + +void NodeIO::GoToNextKeyframe() { + /* FIXME + long key = LONG_MAX; + Clip* c = GetParentEffect()->parent_clip; + for (int i=0;ikeyframes.size();j++) { + long comp = f->keyframes.at(j).time - c->clip_in() + c->timeline_in(); + if (comp > c->track()->sequence()->playhead) { + key = qMin(comp, key); + } + } + } + if (key != LONG_MAX) panel_sequence_viewer->seek(key); + */ +} + +void NodeIO::FocusRow() { + panel_graph_editor->set_row(this); +} + +Node* NodeIO::ParentNode() { + return static_cast(parent()); +} + +void NodeIO::SetKeyframeOnAllFields(ComboAction* ca) { + for (int i=0;iSetValueAt(ParentNode()->Time(), field->GetValueAt(ParentNode()->Time())); + + kdc->SetNewKeyframes(); + ca->append(kdc); + } + + panel_effect_controls->update_keyframes(); +} + +const QString &NodeIO::name() { + return name_; +} + +EffectField* NodeIO::Field(int i) { + return fields_.at(i); +} + +int NodeIO::FieldCount() { + return fields_.size(); +} diff --git a/nodes/nodeio.h b/nodes/nodeio.h index 7e99c1076..68240a1f8 100644 --- a/nodes/nodeio.h +++ b/nodes/nodeio.h @@ -1,491 +1,491 @@ -/*** - - 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 EFFECTROW_H -#define EFFECTROW_H - -#include -#include - -class Node; -class QGridLayout; -class EffectField; -class QLabel; -class QPushButton; -class ComboAction; -class QHBoxLayout; -class KeyframeNavigator; -class ClickableLabel; - -#include "effects/effectfields.h" -#include "nodes/nodeedge.h" - -/** - * @brief The EffectRow class - * - * Primarily a way of grouping EffectField objects together. As UI objects, Effects are largely formatted as a table - * and you can think of EffectRows as the rows of the table and EffectFields as the columns. - * - * Within Olive, keyframing is enabled on the EffectRow (rather than individual EffectFields) so all attached fields - * will be keyframed together. - * - * Unlike EffectField, there's no reason to derive from EffectRow as it's simply a container of fields and a few - * keyframe functions. - */ -class NodeIO : public QObject { - Q_OBJECT -public: - - /** - * @brief EffectRow Constructor - * - * @param parent - * - * Every EffectRow object must be attached to a valid Effect object. The Effect object takes ownership of the - * EffectRow and automatically frees it through the QObject parent/child system. EffectRows are never intended to - * change parents throughout their lifetimes. - * - * @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. - * - * @param savable - * - * Whether the fields in this row should be saved to the project file. This is true by default. If the row contains - * non-value UI widgets, setting this to false is recommended. - * - * @param keyframable - * - * 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. - */ - NodeIO(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(). - * - * @param i - * - * Index to retrieve the EffectField at. - * - * @return - * - * EffectField at the provided index. - */ - EffectField* Field(int i); - - /** - * @brief Number of fields currently contained in this row. - * - * @return The number of fields currently contained in this row as an integer. Any field index (retrieved with - * Field()) is guaranteed to be valid >= 0 and < FieldCount(). - */ - int FieldCount(); - - /** - * @brief Set a keyframe at the current playhead on all fields contained within this row - * - * @param ca - * - * The ComboAction to add this action to. This may not be nullptr. - */ - void SetKeyframeOnAllFields(ComboAction *ca); - - /** - * @brief Get parent Effect - * - * Equivalent to `static_cast(parent())`. - * - * @return The parent Effect object that this row is attached to. - */ - Node* ParentNode(); - - /** - * @brief Return the row's name - * - * @return The name specified in the constructor as a QString - */ - 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 - * - * @return True if this row is keyframing. - */ - bool IsKeyframing(); - - /** - * @brief Set whether this row is keyframing or not. - * - * It's not recommended to use this function for any user-initiated keyframe setting change as it doesn't create any - * undoable actions. Use SetKeyframingEnabled() instead for a user-friendly variant. - */ - void SetKeyframingInternal(bool); - - /** - * @brief Get whether this row should be saved into a project file or not - * - * @return True if this row should be saved. This value is set in the constructor. - */ - bool IsSavable(); - - /** - * @brief Get whether this row can be keyframed or not. - * - * @return True if this row can be keyframed. This value is set in the constructor. - */ - bool IsKeyframable(); - - /** - * @brief Get value - * - * A convenience value for a parameters that doesn't keyframe. Equivalent to GetValueAt(0) where the parameter - * doesn't keyframe so the 0 is ignored. - * - * @param value - */ - QVariant GetValue(); - - /** - * @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 Set value - * - * A convenience value for a parameters that doesn't keyframe. Equivalent to SetValueAt(0, value) where the parameter - * doesn't keyframe so the 0 is ignored. - * - * @param value - */ - void SetValue(const QVariant& value); - - /** - * @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(NodeIO* output, NodeIO* 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 - * - * Gets the closest keyframe prior to the current playhead and seeks to it. - * - * Attach to KeyframeNavigator::goto_previous_key() signal. - */ - void GoToPreviousKeyframe(); - - /** - * @brief Toggle a keyframe at this point in time - * - * Either deletes (if any child EffectFields have any keyframes here) or creates (if none do) a keyframe on all - * EffectField children at the current time. - * - * Attach to KeyframeNavigator::toggle_key() signal. - */ - void ToggleKeyframe(); - - /** - * @brief Go to next keyframe - * - * Gets the closest keyframe after the current playhead and seeks to it. - * - * Attach to KeyframeNavigator::goto_next_key() signal. - */ - void GoToNextKeyframe(); - - /** - * @brief Slot for whenever this EffectRow is focused. - * - * Connect UI objects gaining focus to this slot. Automatically updates the Graph Editor to attach to this row. - */ - void FocusRow(); -signals: - - /** - * @brief Keyframing setting changed signal - * - * Emitted whenever keyframing is enabled or disabled. - * - * @param - * - * 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 - * - * A user-friendly function for enabling or disabling keyframes on this row. Preferred to SetKeyframingInternal() - * for any user-initiated change. Automatically creates an undoable action so users can undo the enabling/disabling. - * Also confirms with the user when disabling keyframing whether they wish to continue and remove all the current - * keyframes. - * - * Attach to KeyframeNavigator::keyframe_enabled_changed() signal. - */ - 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 - * - * Set in the constructor, retrieved with name(). - */ - QString name_; - - /** - * @brief Internal variable for whether this row can be keyframed. - * - * Set in the constructor, retrieved with IsKeyframable(). - */ - bool keyframable_; - - /** - * @brief Internal variable for whether this row is currently keyframing. - * - * Set by SetKeyframingInternal() and retrieved with IsKeyframing(). - */ - bool keyframing_; - - /** - * @brief Internal variable for whether this row should be saved. - * - * Set in the constructor, retrieved with IsSavable(). - */ - bool savable_; - - /** - * @brief Internal array of EffectField objects. - * - * It is not necessary to delete the elements in this array as they're already children of this QObject, so they'll - * 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_; - - /** - * @brief Internal data object for rows without fields - */ - QVariant data_; -}; - -#endif // EFFECTROW_H +/*** + + 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 EFFECTROW_H +#define EFFECTROW_H + +#include +#include + +class Node; +class QGridLayout; +class EffectField; +class QLabel; +class QPushButton; +class ComboAction; +class QHBoxLayout; +class KeyframeNavigator; +class ClickableLabel; + +#include "effects/effectfields.h" +#include "nodes/nodeedge.h" + +/** + * @brief The EffectRow class + * + * Primarily a way of grouping EffectField objects together. As UI objects, Effects are largely formatted as a table + * and you can think of EffectRows as the rows of the table and EffectFields as the columns. + * + * Within Olive, keyframing is enabled on the EffectRow (rather than individual EffectFields) so all attached fields + * will be keyframed together. + * + * Unlike EffectField, there's no reason to derive from EffectRow as it's simply a container of fields and a few + * keyframe functions. + */ +class NodeIO : public QObject { + Q_OBJECT +public: + + /** + * @brief EffectRow Constructor + * + * @param parent + * + * Every EffectRow object must be attached to a valid Effect object. The Effect object takes ownership of the + * EffectRow and automatically frees it through the QObject parent/child system. EffectRows are never intended to + * change parents throughout their lifetimes. + * + * @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. + * + * @param savable + * + * Whether the fields in this row should be saved to the project file. This is true by default. If the row contains + * non-value UI widgets, setting this to false is recommended. + * + * @param keyframable + * + * 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. + */ + NodeIO(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(). + * + * @param i + * + * Index to retrieve the EffectField at. + * + * @return + * + * EffectField at the provided index. + */ + EffectField* Field(int i); + + /** + * @brief Number of fields currently contained in this row. + * + * @return The number of fields currently contained in this row as an integer. Any field index (retrieved with + * Field()) is guaranteed to be valid >= 0 and < FieldCount(). + */ + int FieldCount(); + + /** + * @brief Set a keyframe at the current playhead on all fields contained within this row + * + * @param ca + * + * The ComboAction to add this action to. This may not be nullptr. + */ + void SetKeyframeOnAllFields(ComboAction *ca); + + /** + * @brief Get parent Effect + * + * Equivalent to `static_cast(parent())`. + * + * @return The parent Effect object that this row is attached to. + */ + Node* ParentNode(); + + /** + * @brief Return the row's name + * + * @return The name specified in the constructor as a QString + */ + 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 + * + * @return True if this row is keyframing. + */ + bool IsKeyframing(); + + /** + * @brief Set whether this row is keyframing or not. + * + * It's not recommended to use this function for any user-initiated keyframe setting change as it doesn't create any + * undoable actions. Use SetKeyframingEnabled() instead for a user-friendly variant. + */ + void SetKeyframingInternal(bool); + + /** + * @brief Get whether this row should be saved into a project file or not + * + * @return True if this row should be saved. This value is set in the constructor. + */ + bool IsSavable(); + + /** + * @brief Get whether this row can be keyframed or not. + * + * @return True if this row can be keyframed. This value is set in the constructor. + */ + bool IsKeyframable(); + + /** + * @brief Get value + * + * A convenience value for a parameters that doesn't keyframe. Equivalent to GetValueAt(0) where the parameter + * doesn't keyframe so the 0 is ignored. + * + * @param value + */ + QVariant GetValue(); + + /** + * @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 Set value + * + * A convenience value for a parameters that doesn't keyframe. Equivalent to SetValueAt(0, value) where the parameter + * doesn't keyframe so the 0 is ignored. + * + * @param value + */ + void SetValue(const QVariant& value); + + /** + * @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(NodeIO* output, NodeIO* 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 + * + * Gets the closest keyframe prior to the current playhead and seeks to it. + * + * Attach to KeyframeNavigator::goto_previous_key() signal. + */ + void GoToPreviousKeyframe(); + + /** + * @brief Toggle a keyframe at this point in time + * + * Either deletes (if any child EffectFields have any keyframes here) or creates (if none do) a keyframe on all + * EffectField children at the current time. + * + * Attach to KeyframeNavigator::toggle_key() signal. + */ + void ToggleKeyframe(); + + /** + * @brief Go to next keyframe + * + * Gets the closest keyframe after the current playhead and seeks to it. + * + * Attach to KeyframeNavigator::goto_next_key() signal. + */ + void GoToNextKeyframe(); + + /** + * @brief Slot for whenever this EffectRow is focused. + * + * Connect UI objects gaining focus to this slot. Automatically updates the Graph Editor to attach to this row. + */ + void FocusRow(); +signals: + + /** + * @brief Keyframing setting changed signal + * + * Emitted whenever keyframing is enabled or disabled. + * + * @param + * + * 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 + * + * A user-friendly function for enabling or disabling keyframes on this row. Preferred to SetKeyframingInternal() + * for any user-initiated change. Automatically creates an undoable action so users can undo the enabling/disabling. + * Also confirms with the user when disabling keyframing whether they wish to continue and remove all the current + * keyframes. + * + * Attach to KeyframeNavigator::keyframe_enabled_changed() signal. + */ + 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 + * + * Set in the constructor, retrieved with name(). + */ + QString name_; + + /** + * @brief Internal variable for whether this row can be keyframed. + * + * Set in the constructor, retrieved with IsKeyframable(). + */ + bool keyframable_; + + /** + * @brief Internal variable for whether this row is currently keyframing. + * + * Set by SetKeyframingInternal() and retrieved with IsKeyframing(). + */ + bool keyframing_; + + /** + * @brief Internal variable for whether this row should be saved. + * + * Set in the constructor, retrieved with IsSavable(). + */ + bool savable_; + + /** + * @brief Internal array of EffectField objects. + * + * It is not necessary to delete the elements in this array as they're already children of this QObject, so they'll + * 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_; + + /** + * @brief Internal data object for rows without fields + */ + QVariant data_; +}; + +#endif // EFFECTROW_H diff --git a/nodes/nodes/nodeblock.cpp b/nodes/nodes/nodeblock.cpp index 063d70cf0..d22875463 100644 --- a/nodes/nodes/nodeblock.cpp +++ b/nodes/nodes/nodeblock.cpp @@ -1,72 +1,72 @@ -#include "nodeblock.h" - -NodeBlock::NodeBlock(NodeGraph *graph) : - Node(graph) -{ - previous_block_ = new NodeIO(this, "prev_block", tr("Previous"), true, false); - previous_block_->AddAcceptedNodeInput(olive::nodes::kBlock); - previous_block_->SetValue(0); - AddParameter(previous_block_); - - next_block_ = new NodeIO(this, "next_block", tr("Next"), true, false); - next_block_->SetOutputDataType(olive::nodes::kBlock); - next_block_->SetValue(0); - AddParameter(next_block_); -} - -rational NodeBlock::track_in() -{ - rational in_point; - - NodeBlock* previous = previous_block(); - while (previous != nullptr) { - in_point += previous->length(); - } - - return in_point; -} - -rational NodeBlock::track_out() -{ - return track_in() + length(); -} - -const rational &NodeBlock::length() -{ - return length_; -} - -void NodeBlock::set_length(const rational &l) -{ - length_ = l; -} - -QString NodeBlock::name() -{ - return tr("Block"); -} - -QString NodeBlock::id() -{ - return "block"; -} - -NodeBlock *NodeBlock::previous_block() -{ - return reinterpret_cast(previous_block_->GetValue().value()); -} - -void NodeBlock::set_previous_block(NodeBlock *p) -{ - previous_block_->SetValue(reinterpret_cast(p)); -} - -NodeBlock *NodeBlock::next_block() -{ - return reinterpret_cast(next_block_->GetValue().value()); -} - -void NodeBlock::set_next_block(NodeBlock *p) -{ - next_block_->SetValue(reinterpret_cast(p)); -} +#include "nodeblock.h" + +NodeBlock::NodeBlock(NodeGraph *graph) : + Node(graph) +{ + previous_block_ = new NodeIO(this, "prev_block", tr("Previous"), true, false); + previous_block_->AddAcceptedNodeInput(olive::nodes::kBlock); + previous_block_->SetValue(0); + AddParameter(previous_block_); + + next_block_ = new NodeIO(this, "next_block", tr("Next"), true, false); + next_block_->SetOutputDataType(olive::nodes::kBlock); + next_block_->SetValue(0); + AddParameter(next_block_); +} + +rational NodeBlock::track_in() +{ + rational in_point; + + NodeBlock* previous = previous_block(); + while (previous != nullptr) { + in_point += previous->length(); + } + + return in_point; +} + +rational NodeBlock::track_out() +{ + return track_in() + length(); +} + +const rational &NodeBlock::length() +{ + return length_; +} + +void NodeBlock::set_length(const rational &l) +{ + length_ = l; +} + +QString NodeBlock::name() +{ + return tr("Block"); +} + +QString NodeBlock::id() +{ + return "block"; +} + +NodeBlock *NodeBlock::previous_block() +{ + return reinterpret_cast(previous_block_->GetValue().value()); +} + +void NodeBlock::set_previous_block(NodeBlock *p) +{ + previous_block_->SetValue(reinterpret_cast(p)); +} + +NodeBlock *NodeBlock::next_block() +{ + return reinterpret_cast(next_block_->GetValue().value()); +} + +void NodeBlock::set_next_block(NodeBlock *p) +{ + next_block_->SetValue(reinterpret_cast(p)); +} diff --git a/nodes/nodes/nodeblock.h b/nodes/nodes/nodeblock.h index f57e452e9..db67861f1 100644 --- a/nodes/nodes/nodeblock.h +++ b/nodes/nodes/nodeblock.h @@ -1,48 +1,48 @@ -#ifndef NODEBLOCK_H -#define NODEBLOCK_H - -#include "global/rational.h" -#include "nodes/node.h" - -/** - * @brief The NodeBlock class - * - * A "Block" node is a node with a timed in-point and out-point that can appear on the Timeline. - */ -class NodeBlock : public Node -{ - Q_OBJECT -public: - NodeBlock(NodeGraph* graph); - - rational track_in(); - rational track_out(); - - const rational& length(); - void set_length(const rational& l); - - virtual QString name() override; - virtual QString id() override; - - /** - * @brief Retrieves the NodeBlock connected to the "Previous" parameter - */ - NodeBlock* previous_block(); - void set_previous_block(NodeBlock* p); - - /** - * @brief Retrieves the NodeBlock connected to the "Next" parameter - */ - NodeBlock* next_block(); - void set_next_block(NodeBlock* p); - - - -private: - rational length_; - - NodeIO* previous_block_; - NodeIO* next_block_; -}; - -#endif // NODEBLOCK_H +#ifndef NODEBLOCK_H +#define NODEBLOCK_H + +#include "global/rational.h" +#include "nodes/node.h" + +/** + * @brief The NodeBlock class + * + * A "Block" node is a node with a timed in-point and out-point that can appear on the Timeline. + */ +class NodeBlock : public Node +{ + Q_OBJECT +public: + NodeBlock(NodeGraph* graph); + + rational track_in(); + rational track_out(); + + const rational& length(); + void set_length(const rational& l); + + virtual QString name() override; + virtual QString id() override; + + /** + * @brief Retrieves the NodeBlock connected to the "Previous" parameter + */ + NodeBlock* previous_block(); + void set_previous_block(NodeBlock* p); + + /** + * @brief Retrieves the NodeBlock connected to the "Next" parameter + */ + NodeBlock* next_block(); + void set_next_block(NodeBlock* p); + + + +private: + rational length_; + + NodeIO* previous_block_; + NodeIO* next_block_; +}; + +#endif // NODEBLOCK_H diff --git a/nodes/nodes/nodevideoclip.cpp b/nodes/nodes/nodevideoclip.cpp index b537a09d9..1de6a659d 100644 --- a/nodes/nodes/nodevideoclip.cpp +++ b/nodes/nodes/nodevideoclip.cpp @@ -1,16 +1,16 @@ -#include "nodevideoclip.h" - -NodeVideoClip::NodeVideoClip(NodeGraph *parent) : - NodeBlock(parent) -{ - texture_input_ = new NodeIO(this, "texture", tr("Texture"), true, false); - texture_input_->AddAcceptedNodeInput(olive::nodes::kTexture); - - texture_output_ = new NodeIO(this, "texture", tr("Texture"), true, false); - texture_output_->SetOutputDataType(olive::nodes::kTexture); -} - -NodeIO *NodeVideoClip::texture_output() -{ - return texture_output_; -} +#include "nodevideoclip.h" + +NodeVideoClip::NodeVideoClip(NodeGraph *parent) : + NodeBlock(parent) +{ + texture_input_ = new NodeIO(this, "texture", tr("Texture"), true, false); + texture_input_->AddAcceptedNodeInput(olive::nodes::kTexture); + + texture_output_ = new NodeIO(this, "texture", tr("Texture"), true, false); + texture_output_->SetOutputDataType(olive::nodes::kTexture); +} + +NodeIO *NodeVideoClip::texture_output() +{ + return texture_output_; +} diff --git a/nodes/nodes/nodevideoclip.h b/nodes/nodes/nodevideoclip.h index 59cccadb5..874922581 100644 --- a/nodes/nodes/nodevideoclip.h +++ b/nodes/nodes/nodevideoclip.h @@ -1,19 +1,19 @@ -#ifndef NODEVIDEOCLIP_H -#define NODEVIDEOCLIP_H - -#include "nodeblock.h" - -class NodeVideoClip : public NodeBlock -{ -public: - NodeVideoClip(NodeGraph* parent); - - NodeIO* texture_input(); - NodeIO* texture_output(); - -private: - NodeIO* texture_input_; - NodeIO* texture_output_; -}; - -#endif // NODEVIDEOCLIP_H +#ifndef NODEVIDEOCLIP_H +#define NODEVIDEOCLIP_H + +#include "nodeblock.h" + +class NodeVideoClip : public NodeBlock +{ +public: + NodeVideoClip(NodeGraph* parent); + + NodeIO* texture_input(); + NodeIO* texture_output(); + +private: + NodeIO* texture_input_; + NodeIO* texture_output_; +}; + +#endif // NODEVIDEOCLIP_H diff --git a/nodes/oldeffectnode.cpp b/nodes/oldeffectnode.cpp index 067622861..e561e3381 100644 --- a/nodes/oldeffectnode.cpp +++ b/nodes/oldeffectnode.cpp @@ -1,920 +1,920 @@ -/*** - - 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 "oldeffectnode.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; - -OldEffectNode::OldEffectNode(Clip *c) : - Node(nullptr), - 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) -{ -} - -OldEffectNode::~OldEffectNode() { - 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 OldEffectNode::category() -{ - return QString(); -} - -QString OldEffectNode::description() -{ - return QString(); -} - -bool OldEffectNode::IsCreatable() -{ - return true; -} - -void OldEffectNode::copy_field_keyframes(OldEffectNodePtr e) { - for (int i=0;iParameter(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_; - } - } -} - -EffectGizmo *OldEffectNode::add_gizmo(int type) { - EffectGizmo* gizmo = new EffectGizmo(this, type); - gizmos.append(gizmo); - return gizmo; -} - -EffectGizmo *OldEffectNode::gizmo(int i) { - return gizmos.at(i); -} - -int OldEffectNode::gizmo_count() { - return gizmos.size(); -} - -QVector OldEffectNode::GetAllEdges() -{ - QVector edges; - - for (int i=0;iedges()); - } - - return edges; -} - -void OldEffectNode::refresh() {} - -void OldEffectNode::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 OldEffectNode::delete_self() { - olive::undo_stack.push(new EffectDeleteCommand(this)); - update_ui(true); -} - -void OldEffectNode::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 OldEffectNode::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 OldEffectNode::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 OldEffectNode::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 OldEffectNode::AlwaysUpdate() -{ - return false; -} - -bool OldEffectNode::IsEnabled() { - return enabled_; -} - -bool OldEffectNode::IsExpanded() -{ - return expanded_; -} - -void OldEffectNode::SetExpanded(bool e) -{ - expanded_ = e; -} - -void OldEffectNode::SetEnabled(bool b) { - enabled_ = b; - emit EnabledChanged(b); -} - -void OldEffectNode::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 OldEffectNode::custom_load(QXmlStreamReader &) {} - -void OldEffectNode::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 OldEffectNode::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 *OldEffectNode::GetShaderPipeline() -{ - return shader_program_.get(); -} - -int OldEffectNode::Flags() -{ - return flags_; -} - -void OldEffectNode::SetFlags(int flags) -{ - flags_ = flags; -} - -int OldEffectNode::getIterations() { - return iterations; -} - -void OldEffectNode::setIterations(int i) { - iterations = i; -} - -void OldEffectNode::process_image(double, uint8_t *, uint8_t *, int){} - -OldEffectNodePtr OldEffectNode::copy(Clip *c) { - OldEffectNodePtr copy = Create(c); - copy->SetEnabled(IsEnabled()); - copy_field_keyframes(copy); - return copy; -} - -void OldEffectNode::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 OldEffectNode::process_coords(double, GLTextureCoords&, int) {} - -GLuint OldEffectNode::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 OldEffectNode::process_audio(double, double, float **, int, int, int) {} - -void OldEffectNode::gizmo_draw(double, GLTextureCoords &) {} - -void OldEffectNode::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 OldEffectNode::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 OldEffectNode::are_gizmos_enabled() { - return (gizmos.size() > 0); -} - -double OldEffectNode::Now() -{ - return playhead_to_clip_seconds(parent_clip, parent_clip->track()->sequence()->playhead); -} - -long OldEffectNode::NowInFrames() -{ - return playhead_to_clip_frame(parent_clip, parent_clip->track()->sequence()->playhead); -} - -void OldEffectNode::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 OldEffectNode::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 OldEffectNode::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. + +***/ + +#include "oldeffectnode.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; + +OldEffectNode::OldEffectNode(Clip *c) : + Node(nullptr), + 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) +{ +} + +OldEffectNode::~OldEffectNode() { + 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 OldEffectNode::category() +{ + return QString(); +} + +QString OldEffectNode::description() +{ + return QString(); +} + +bool OldEffectNode::IsCreatable() +{ + return true; +} + +void OldEffectNode::copy_field_keyframes(OldEffectNodePtr e) { + for (int i=0;iParameter(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_; + } + } +} + +EffectGizmo *OldEffectNode::add_gizmo(int type) { + EffectGizmo* gizmo = new EffectGizmo(this, type); + gizmos.append(gizmo); + return gizmo; +} + +EffectGizmo *OldEffectNode::gizmo(int i) { + return gizmos.at(i); +} + +int OldEffectNode::gizmo_count() { + return gizmos.size(); +} + +QVector OldEffectNode::GetAllEdges() +{ + QVector edges; + + for (int i=0;iedges()); + } + + return edges; +} + +void OldEffectNode::refresh() {} + +void OldEffectNode::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 OldEffectNode::delete_self() { + olive::undo_stack.push(new EffectDeleteCommand(this)); + update_ui(true); +} + +void OldEffectNode::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 OldEffectNode::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 OldEffectNode::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 OldEffectNode::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 OldEffectNode::AlwaysUpdate() +{ + return false; +} + +bool OldEffectNode::IsEnabled() { + return enabled_; +} + +bool OldEffectNode::IsExpanded() +{ + return expanded_; +} + +void OldEffectNode::SetExpanded(bool e) +{ + expanded_ = e; +} + +void OldEffectNode::SetEnabled(bool b) { + enabled_ = b; + emit EnabledChanged(b); +} + +void OldEffectNode::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 OldEffectNode::custom_load(QXmlStreamReader &) {} + +void OldEffectNode::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 OldEffectNode::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 *OldEffectNode::GetShaderPipeline() +{ + return shader_program_.get(); +} + +int OldEffectNode::Flags() +{ + return flags_; +} + +void OldEffectNode::SetFlags(int flags) +{ + flags_ = flags; +} + +int OldEffectNode::getIterations() { + return iterations; +} + +void OldEffectNode::setIterations(int i) { + iterations = i; +} + +void OldEffectNode::process_image(double, uint8_t *, uint8_t *, int){} + +OldEffectNodePtr OldEffectNode::copy(Clip *c) { + OldEffectNodePtr copy = Create(c); + copy->SetEnabled(IsEnabled()); + copy_field_keyframes(copy); + return copy; +} + +void OldEffectNode::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 OldEffectNode::process_coords(double, GLTextureCoords&, int) {} + +GLuint OldEffectNode::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 OldEffectNode::process_audio(double, double, float **, int, int, int) {} + +void OldEffectNode::gizmo_draw(double, GLTextureCoords &) {} + +void OldEffectNode::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 OldEffectNode::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 OldEffectNode::are_gizmos_enabled() { + return (gizmos.size() > 0); +} + +double OldEffectNode::Now() +{ + return playhead_to_clip_seconds(parent_clip, parent_clip->track()->sequence()->playhead); +} + +long OldEffectNode::NowInFrames() +{ + return playhead_to_clip_frame(parent_clip, parent_clip->track()->sequence()->playhead); +} + +void OldEffectNode::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 OldEffectNode::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 OldEffectNode::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" -#include "node.h" - -class EffectGizmo; -class KeyframeDataChange; - -class Clip; -using ClipPtr = std::shared_ptr; - -class OldEffectNode; -using OldEffectNodePtr = 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 OldEffectNode : public Node { - Q_OBJECT -public: - OldEffectNode(Clip *c); - ~OldEffectNode(); - - Clip* parent_clip; - - virtual QString name() = 0; - virtual QString id() = 0; - virtual QString category(); - virtual QString description(); - virtual EffectType type() = 0; - virtual olive::TrackType subtype() = 0; - virtual bool IsCreatable(); - virtual OldEffectNodePtr Create(Clip *c) = 0; - - EffectGizmo* add_gizmo(int type); - EffectGizmo* gizmo(int i); - int gizmo_count(); - - QVector GetAllEdges(); - - bool IsEnabled(); - bool IsExpanded(); - - virtual void refresh(); - - virtual OldEffectNodePtr copy(Clip* c); - void copy_field_keyframes(OldEffectNodePtr 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); - - - - 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); - -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 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(); -}; - -namespace olive { - extern QVector node_library; -} - -#endif // EFFECT_H +/*** + + 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 "timeline/tracktypes.h" +#include "rendering/qopenglshaderprogramptr.h" +#include "inputs.h" +#include "effects/effectgizmo.h" +#include "node.h" + +class EffectGizmo; +class KeyframeDataChange; + +class Clip; +using ClipPtr = std::shared_ptr; + +class OldEffectNode; +using OldEffectNodePtr = 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 OldEffectNode : public Node { + Q_OBJECT +public: + OldEffectNode(Clip *c); + ~OldEffectNode(); + + Clip* parent_clip; + + virtual QString name() = 0; + virtual QString id() = 0; + virtual QString category(); + virtual QString description(); + virtual EffectType type() = 0; + virtual olive::TrackType subtype() = 0; + virtual bool IsCreatable(); + virtual OldEffectNodePtr Create(Clip *c) = 0; + + EffectGizmo* add_gizmo(int type); + EffectGizmo* gizmo(int i); + int gizmo_count(); + + QVector GetAllEdges(); + + bool IsEnabled(); + bool IsExpanded(); + + virtual void refresh(); + + virtual OldEffectNodePtr copy(Clip* c); + void copy_field_keyframes(OldEffectNodePtr 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); + + + + 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); + +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 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(); +}; + +namespace olive { + extern QVector node_library; +} + +#endif // EFFECT_H diff --git a/packaging/windows/nsis/olive.nsi b/packaging/windows/nsis/olive.nsi index ba367c69b..bc638adfb 100644 --- a/packaging/windows/nsis/olive.nsi +++ b/packaging/windows/nsis/olive.nsi @@ -1,85 +1,85 @@ -!include "MUI.nsh" - -!define MUI_ICON "install icon.ico" -!define MUI_UNICON "uninstall icon.ico" - -!define APP_NAME "Olive" -!define APP_TARGET "olive-editor" - -!define MUI_FINISHPAGE_RUN "$INSTDIR\olive-editor.exe" - -SetCompressor lzma - -Name ${APP_NAME} - - -!ifdef X64 -InstallDir "$PROGRAMFILES64\${APP_NAME}" -!else -InstallDir "$PROGRAMFILES32\${APP_NAME}" -!endif - -!insertmacro MUI_PAGE_WELCOME -!insertmacro MUI_PAGE_LICENSE LICENSE -!insertmacro MUI_PAGE_DIRECTORY -!insertmacro MUI_PAGE_COMPONENTS -!insertmacro MUI_PAGE_INSTFILES - -!define MUI_FINISHPAGE_NOAUTOCLOSE -!define MUI_FINISHPAGE_RUN_TEXT "Run ${APP_NAME}" -!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchOlive" -!insertmacro MUI_PAGE_FINISH - -!insertmacro MUI_LANGUAGE "English" - -Section "Olive (required)" - - SectionIn RO - - SetOutPath $INSTDIR - - File /r olive\* - - WriteUninstaller "$INSTDIR\uninstall.exe" - -SectionEnd - -Section "Create Desktop shortcut" - CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_TARGET}.exe" -SectionEnd - -Section "Create Start Menu shortcut" - CreateDirectory "$SMPROGRAMS\${APP_NAME}" - CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${APP_TARGET}.exe" - CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" -SectionEnd - -Section "Associate *.ove files with Olive" - WriteRegStr HKCR ".ove" "" "OliveEditor.OVEFile" - WriteRegStr HKCR ".ove" "Content Type" "application/vnd.olive-project" - WriteRegStr HKCR "OliveEditor.OVEFile" "" "Olive project file" - WriteRegStr HKCR "OliveEditor.OVEFile\DefaultIcon" "" "$INSTDIR\olive-editor.exe,1" - WriteRegStr HKCR "OliveEditor.OVEFile\shell\open\command" "" "$\"$INSTDIR\olive-editor.exe$\" $\"%1$\"" - System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' -SectionEnd - -UninstPage uninstConfirm -UninstPage instfiles - -Section "uninstall" - - rmdir /r "$INSTDIR" - - Delete "$DESKTOP\${APP_NAME}.lnk" - rmdir /r "$SMPROGRAMS\${APP_NAME}" - - DeleteRegKey HKCR ".ove" - DeleteRegKey HKCR "OliveEditor.OVEFile" - DeleteRegKey HKCR "OliveEditor.OVEFile\DefaultIcon" "" - DeleteRegKey HKCR "OliveEditor.OVEFile\shell\open\command" "" - System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' -SectionEnd - -Function LaunchOlive - ExecShell "" "$INSTDIR\${APP_TARGET}.exe" +!include "MUI.nsh" + +!define MUI_ICON "install icon.ico" +!define MUI_UNICON "uninstall icon.ico" + +!define APP_NAME "Olive" +!define APP_TARGET "olive-editor" + +!define MUI_FINISHPAGE_RUN "$INSTDIR\olive-editor.exe" + +SetCompressor lzma + +Name ${APP_NAME} + + +!ifdef X64 +InstallDir "$PROGRAMFILES64\${APP_NAME}" +!else +InstallDir "$PROGRAMFILES32\${APP_NAME}" +!endif + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE LICENSE +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_NOAUTOCLOSE +!define MUI_FINISHPAGE_RUN_TEXT "Run ${APP_NAME}" +!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchOlive" +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + +Section "Olive (required)" + + SectionIn RO + + SetOutPath $INSTDIR + + File /r olive\* + + WriteUninstaller "$INSTDIR\uninstall.exe" + +SectionEnd + +Section "Create Desktop shortcut" + CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_TARGET}.exe" +SectionEnd + +Section "Create Start Menu shortcut" + CreateDirectory "$SMPROGRAMS\${APP_NAME}" + CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${APP_TARGET}.exe" + CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" +SectionEnd + +Section "Associate *.ove files with Olive" + WriteRegStr HKCR ".ove" "" "OliveEditor.OVEFile" + WriteRegStr HKCR ".ove" "Content Type" "application/vnd.olive-project" + WriteRegStr HKCR "OliveEditor.OVEFile" "" "Olive project file" + WriteRegStr HKCR "OliveEditor.OVEFile\DefaultIcon" "" "$INSTDIR\olive-editor.exe,1" + WriteRegStr HKCR "OliveEditor.OVEFile\shell\open\command" "" "$\"$INSTDIR\olive-editor.exe$\" $\"%1$\"" + System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' +SectionEnd + +UninstPage uninstConfirm +UninstPage instfiles + +Section "uninstall" + + rmdir /r "$INSTDIR" + + Delete "$DESKTOP\${APP_NAME}.lnk" + rmdir /r "$SMPROGRAMS\${APP_NAME}" + + DeleteRegKey HKCR ".ove" + DeleteRegKey HKCR "OliveEditor.OVEFile" + DeleteRegKey HKCR "OliveEditor.OVEFile\DefaultIcon" "" + DeleteRegKey HKCR "OliveEditor.OVEFile\shell\open\command" "" + System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' +SectionEnd + +Function LaunchOlive + ExecShell "" "$INSTDIR\${APP_TARGET}.exe" FunctionEnd \ No newline at end of file diff --git a/packaging/windows/resources.rc b/packaging/windows/resources.rc index 16f1262dc..034de5076 100644 --- a/packaging/windows/resources.rc +++ b/packaging/windows/resources.rc @@ -1,32 +1,32 @@ -IDI_ICON1 ICON DISCARDABLE "olive.ico" -IDI_ICON2 ICON DISCARDABLE "olive_ove.ico" - -#include -#include "version.h" - -VS_VERSION_INFO VERSIONINFO -FILEVERSION VER_FILEVERSION -PRODUCTVERSION VER_PRODUCTVERSION -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - BEGIN - VALUE "CompanyName", VER_COMPANYNAME_STR - VALUE "FileDescription", VER_FILEDESCRIPTION_STR - VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "InternalName", VER_INTERNALNAME_STR - VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR - VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR - VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR - VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR - VALUE "ProductName", VER_PRODUCTNAME_STR - VALUE "ProductVersion", VER_PRODUCTVERSION_STR - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END +IDI_ICON1 ICON DISCARDABLE "olive.ico" +IDI_ICON2 ICON DISCARDABLE "olive_ove.ico" + +#include +#include "version.h" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", VER_FILEDESCRIPTION_STR + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", VER_INTERNALNAME_STR + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR + VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR + VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR + VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/packaging/windows/version.h b/packaging/windows/version.h index 615e67dfc..5f2bef70d 100644 --- a/packaging/windows/version.h +++ b/packaging/windows/version.h @@ -1,21 +1,21 @@ -#ifndef VERSION_H -#define VERSION_H - -#define VER_FILEVERSION 1,0,0,0 -#define VER_FILEVERSION_STR "1.0.0.0\0" - -#define VER_PRODUCTVERSION 1,0,0,0 -#define VER_PRODUCTVERSION_STR "1.0\0" - -#define VER_COMPANYNAME_STR "Olive Team" -#define VER_FILEDESCRIPTION_STR "Olive" -#define VER_INTERNALNAME_STR "Olive" -#define VER_LEGALCOPYRIGHT_STR "Copyright © 2018 Olive Team" -#define VER_LEGALTRADEMARKS1_STR "All Rights Reserved" -#define VER_LEGALTRADEMARKS2_STR VER_LEGALTRADEMARKS1_STR -#define VER_ORIGINALFILENAME_STR "Olive.exe" -#define VER_PRODUCTNAME_STR "Olive" - -#define VER_COMPANYDOMAIN_STR "www.olivevideoeditor.org" - -#endif // VERSION_H +#ifndef VERSION_H +#define VERSION_H + +#define VER_FILEVERSION 1,0,0,0 +#define VER_FILEVERSION_STR "1.0.0.0\0" + +#define VER_PRODUCTVERSION 1,0,0,0 +#define VER_PRODUCTVERSION_STR "1.0\0" + +#define VER_COMPANYNAME_STR "Olive Team" +#define VER_FILEDESCRIPTION_STR "Olive" +#define VER_INTERNALNAME_STR "Olive" +#define VER_LEGALCOPYRIGHT_STR "Copyright © 2018 Olive Team" +#define VER_LEGALTRADEMARKS1_STR "All Rights Reserved" +#define VER_LEGALTRADEMARKS2_STR VER_LEGALTRADEMARKS1_STR +#define VER_ORIGINALFILENAME_STR "Olive.exe" +#define VER_PRODUCTNAME_STR "Olive" + +#define VER_COMPANYDOMAIN_STR "www.olivevideoeditor.org" + +#endif // VERSION_H diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 71a1a099e..92c19a9f1 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -1,425 +1,425 @@ -/*** - - 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 "effectcontrols.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "panels/panels.h" -#include "nodes/oldeffectnode.h" -#include "effects/effectloaders.h" -#include "effects/transition.h" -#include "timeline/clip.h" -#include "ui/collapsiblewidget.h" -#include "timeline/sequence.h" -#include "undo/undo.h" -#include "undo/undostack.h" -#include "panels/project.h" -#include "panels/timeline.h" -#include "panels/viewer.h" -#include "panels/grapheditor.h" -#include "ui/viewerwidget.h" -#include "ui/menuhelper.h" -#include "ui/icons.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" -#include "global/debug.h" -#include "ui/menu.h" - -EffectControls::EffectControls(QWidget *parent) : - EffectsPanel(parent), - zoom(1) -{ - setup_ui(); - Retranslate(); - - Clear(false); - - headers->viewer = panel_sequence_viewer; - headers->snapping = false; - - effects_area->parent_widget = scrollArea; - effects_area->keyframe_area = keyframeView; - effects_area->header = headers; - keyframeView->header = headers; - - connect(keyframeView, SIGNAL(wheel_event_signal(QWheelEvent*)), effects_area, SLOT(receive_wheel_event(QWheelEvent*))); - connect(horizontalScrollBar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int))); - connect(horizontalScrollBar, SIGNAL(resize_move(double)), keyframeView, SLOT(resize_move(double))); - connect(horizontalScrollBar, SIGNAL(valueChanged(int)), keyframeView, SLOT(set_x_scroll(int))); - connect(verticalScrollBar, SIGNAL(valueChanged(int)), keyframeView, SLOT(set_y_scroll(int))); - connect(verticalScrollBar, SIGNAL(valueChanged(int)), scrollArea->verticalScrollBar(), SLOT(setValue(int))); - connect(scrollArea->verticalScrollBar(), SIGNAL(valueChanged(int)), verticalScrollBar, SLOT(setValue(int))); -} - -void EffectControls::set_zoom(bool in) { - zoom *= (in) ? 2 : 0.5; - update_keyframes(); - - if (!selected_clips_.isEmpty()) { - scroll_to_frame(selected_clips_.first()->track()->sequence()->playhead); - } -} - -void EffectControls::update_keyframes() { - for (int i=0;iUpdateFromEffect(); - } - - headers->update_zoom(zoom); - keyframeView->update(); -} - -void EffectControls::delete_selected_keyframes() { - keyframeView->delete_selected_keyframes(); -} - -void EffectControls::scroll_to_frame(long frame) { - scroll_to_frame_internal(horizontalScrollBar, frame - keyframeView->visible_in, zoom, keyframeView->width()); -} - -void EffectControls::UpdateTitle() { - if (selected_clips_.isEmpty()) { - setWindowTitle(panel_name + tr("(none)")); - } else { - setWindowTitle(panel_name + selected_clips_.first()->name()); - } -} - -void EffectControls::setup_ui() { - QWidget* contents = new QWidget(this); - - QHBoxLayout* hlayout = new QHBoxLayout(contents); - hlayout->setSpacing(0); - hlayout->setMargin(0); - - splitter = new QSplitter(); - splitter->setOrientation(Qt::Horizontal); - splitter->setChildrenCollapsible(false); - - scrollArea = new QScrollArea(); - scrollArea->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); - scrollArea->setFrameShape(QFrame::NoFrame); - scrollArea->setFrameShadow(QFrame::Plain); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - scrollArea->setWidgetResizable(true); - - QWidget* scrollAreaWidgetContents = new QWidget(); - - QHBoxLayout* scrollAreaLayout = new QHBoxLayout(scrollAreaWidgetContents); - scrollAreaLayout->setSpacing(0); - scrollAreaLayout->setMargin(0); - - effects_area = new EffectsArea(); - effects_area->setContextMenuPolicy(Qt::CustomContextMenu); - connect(effects_area, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(effects_area_context_menu())); - - QVBoxLayout* effects_area_layout = new QVBoxLayout(effects_area); - effects_area_layout->setSpacing(0); - effects_area_layout->setMargin(0); - - vcontainer = new QWidget(); - QVBoxLayout* vcontainerLayout = new QVBoxLayout(vcontainer); - vcontainerLayout->setSpacing(0); - vcontainerLayout->setMargin(0); - - QWidget* veHeader = new QWidget(); - veHeader->setObjectName(QStringLiteral("veHeader")); - veHeader->setStyleSheet(QLatin1String("#veHeader { background: rgba(0, 0, 0, 0.25); }")); - - QHBoxLayout* veHeaderLayout = new QHBoxLayout(veHeader); - veHeaderLayout->setSpacing(0); - veHeaderLayout->setMargin(0); - - QIcon add_effect_icon = olive::icon::CreateIconFromSVG(":/icons/add-effect.svg", false); - QIcon add_transition_icon = olive::icon::CreateIconFromSVG(":/icons/add-transition.svg", false); - - btnAddVideoEffect = new QPushButton(); - btnAddVideoEffect->setIcon(add_effect_icon); - veHeaderLayout->addWidget(btnAddVideoEffect); - connect(btnAddVideoEffect, SIGNAL(clicked(bool)), this, SLOT(video_effect_click())); - - veHeaderLayout->addStretch(); - - lblVideoEffects = new QLabel(); - QFont font; - font.setPointSize(9); - lblVideoEffects->setFont(font); - lblVideoEffects->setAlignment(Qt::AlignCenter); - veHeaderLayout->addWidget(lblVideoEffects); - - veHeaderLayout->addStretch(); - - btnAddVideoTransition = new QPushButton(); - btnAddVideoTransition->setIcon(add_transition_icon); - connect(btnAddVideoTransition, SIGNAL(clicked(bool)), this, SLOT(video_transition_click())); - veHeaderLayout->addWidget(btnAddVideoTransition); - - vcontainerLayout->addWidget(veHeader); - - video_effect_area = new QWidget(); - video_effect_layout = new QVBoxLayout(video_effect_area); - video_effect_layout->setSpacing(0); - video_effect_layout->setMargin(0); - - vcontainerLayout->addWidget(video_effect_area); - - effects_area_layout->addWidget(vcontainer); - - acontainer = new QWidget(); - QVBoxLayout* acontainerLayout = new QVBoxLayout(acontainer); - acontainerLayout->setSpacing(0); - acontainerLayout->setMargin(0); - QWidget* aeHeader = new QWidget(); - aeHeader->setObjectName(QStringLiteral("aeHeader")); - aeHeader->setStyleSheet(QLatin1String("#aeHeader { background: rgba(0, 0, 0, 0.25); }")); - - QHBoxLayout* aeHeaderLayout = new QHBoxLayout(aeHeader); - aeHeaderLayout->setSpacing(0); - aeHeaderLayout->setMargin(0); - - btnAddAudioEffect = new QPushButton(); - btnAddAudioEffect->setIcon(add_effect_icon); - connect(btnAddAudioEffect, SIGNAL(clicked(bool)), this, SLOT(audio_effect_click())); - aeHeaderLayout->addWidget(btnAddAudioEffect); - - aeHeaderLayout->addStretch(); - - lblAudioEffects = new QLabel(); - lblAudioEffects->setFont(font); - lblAudioEffects->setAlignment(Qt::AlignCenter); - aeHeaderLayout->addWidget(lblAudioEffects); - - aeHeaderLayout->addStretch(); - - btnAddAudioTransition = new QPushButton(); - btnAddAudioTransition->setIcon(add_transition_icon); - connect(btnAddAudioTransition, SIGNAL(clicked(bool)), this, SLOT(audio_transition_click())); - aeHeaderLayout->addWidget(btnAddAudioTransition); - - acontainerLayout->addWidget(aeHeader); - - audio_effect_area = new QWidget(); - audio_effect_layout = new QVBoxLayout(audio_effect_area); - audio_effect_layout->setSpacing(0); - audio_effect_layout->setMargin(0); - - acontainerLayout->addWidget(audio_effect_area); - - effects_area_layout->addWidget(acontainer); - - effects_area_layout->addStretch(); - - scrollAreaLayout->addWidget(effects_area); - - scrollArea->setWidget(scrollAreaWidgetContents); - splitter->addWidget(scrollArea); - - QWidget* keyframeArea = new QWidget(); - - QSizePolicy keyframe_sp; - keyframe_sp.setHorizontalPolicy(QSizePolicy::Minimum); - keyframe_sp.setVerticalPolicy(QSizePolicy::Preferred); - keyframe_sp.setHorizontalStretch(1); - keyframeArea->setSizePolicy(keyframe_sp); - - QVBoxLayout* keyframeAreaLayout = new QVBoxLayout(keyframeArea); - keyframeAreaLayout->setSpacing(0); - keyframeAreaLayout->setMargin(0); - - headers = new TimelineHeader(); - keyframeAreaLayout->addWidget(headers); - - QWidget* keyframeCenterWidget = new QWidget(); - - QHBoxLayout* keyframeCenterLayout = new QHBoxLayout(keyframeCenterWidget); - keyframeCenterLayout->setSpacing(0); - keyframeCenterLayout->setMargin(0); - - keyframeView = new KeyframeView(); - - keyframeCenterLayout->addWidget(keyframeView); - - verticalScrollBar = new QScrollBar(); - verticalScrollBar->setOrientation(Qt::Vertical); - - keyframeCenterLayout->addWidget(verticalScrollBar); - - - keyframeAreaLayout->addWidget(keyframeCenterWidget); - - horizontalScrollBar = new ResizableScrollBar(); - horizontalScrollBar->setOrientation(Qt::Horizontal); - - keyframeAreaLayout->addWidget(horizontalScrollBar); - - splitter->addWidget(keyframeArea); - - hlayout->addWidget(splitter); - - setWidget(contents); -} - -void EffectControls::Retranslate() { - panel_name = tr("Effects: "); - - btnAddVideoEffect->setToolTip(tr("Add Video Effect")); - lblVideoEffects->setText(tr("VIDEO EFFECTS")); - btnAddVideoTransition->setToolTip(tr("Add Video Transition")); - btnAddAudioEffect->setToolTip(tr("Add Audio Effect")); - lblAudioEffects->setText(tr("AUDIO EFFECTS")); - btnAddAudioTransition->setToolTip(tr("Add Audio Transition")); - - UpdateTitle(); -} - -void EffectControls::LoadLayoutState(const QByteArray &data) -{ - splitter->restoreState(data); -} - -QByteArray EffectControls::SaveLayoutState() -{ - return splitter->saveState(); -} - -void EffectControls::update_scrollbar() { - verticalScrollBar->setMaximum(qMax(0, effects_area->height() - keyframeView->height() - headers->height())); - verticalScrollBar->setPageStep(verticalScrollBar->height()); -} - -void EffectControls::queue_post_update() { - keyframeView->update(); - update_scrollbar(); -} - -void EffectControls::effects_area_context_menu() { - Menu menu(this); - - olive::MenuHelper.create_effect_paste_action(&menu); - - menu.exec(QCursor::pos()); -} - -bool EffectControls::focused() -{ - if (this->hasFocus() - || headers->hasFocus() - || keyframeView->hasFocus()) { - return true; - } - - return EffectsPanel::focused(); -} - -void EffectControls::video_effect_click() { - olive::Global->ShowEffectMenu(EFFECT_TYPE_EFFECT, olive::kTypeVideo, selected_clips_); -} - -void EffectControls::audio_effect_click() { - olive::Global->ShowEffectMenu(EFFECT_TYPE_EFFECT, olive::kTypeAudio, selected_clips_); -} - -void EffectControls::video_transition_click() { - olive::Global->ShowEffectMenu(EFFECT_TYPE_TRANSITION, olive::kTypeVideo, selected_clips_); -} - -void EffectControls::audio_transition_click() { - olive::Global->ShowEffectMenu(EFFECT_TYPE_TRANSITION, olive::kTypeAudio, selected_clips_); -} - -void EffectControls::resizeEvent(QResizeEvent*) { - update_scrollbar(); -} - -void EffectControls::ClearEvent() -{ - keyframeView->SetEffects(open_effects_); - - vcontainer->setVisible(false); - acontainer->setVisible(false); - headers->setVisible(false); - keyframeView->setEnabled(false); - - UpdateTitle(); -} - -void EffectControls::LoadEvent() -{ - keyframeView->SetEffects(open_effects_); - - if (selected_clips_.size() > 0) { - keyframeView->setEnabled(true); - - headers->setVisible(true); - - QTimer::singleShot(50, this, SLOT(queue_post_update())); - } - - 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(); -} - -EffectsArea::EffectsArea(QWidget* parent) : - QWidget(parent) -{} - -void EffectsArea::resizeEvent(QResizeEvent *) -{ - parent_widget->setMinimumWidth(sizeHint().width()); -} - -void EffectsArea::receive_wheel_event(QWheelEvent *e) { - QApplication::sendEvent(this, e); -} +/*** + + 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 "effectcontrols.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "panels/panels.h" +#include "nodes/oldeffectnode.h" +#include "effects/effectloaders.h" +#include "effects/transition.h" +#include "timeline/clip.h" +#include "ui/collapsiblewidget.h" +#include "timeline/sequence.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "panels/project.h" +#include "panels/timeline.h" +#include "panels/viewer.h" +#include "panels/grapheditor.h" +#include "ui/viewerwidget.h" +#include "ui/menuhelper.h" +#include "ui/icons.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" +#include "global/debug.h" +#include "ui/menu.h" + +EffectControls::EffectControls(QWidget *parent) : + EffectsPanel(parent), + zoom(1) +{ + setup_ui(); + Retranslate(); + + Clear(false); + + headers->viewer = panel_sequence_viewer; + headers->snapping = false; + + effects_area->parent_widget = scrollArea; + effects_area->keyframe_area = keyframeView; + effects_area->header = headers; + keyframeView->header = headers; + + connect(keyframeView, SIGNAL(wheel_event_signal(QWheelEvent*)), effects_area, SLOT(receive_wheel_event(QWheelEvent*))); + connect(horizontalScrollBar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int))); + connect(horizontalScrollBar, SIGNAL(resize_move(double)), keyframeView, SLOT(resize_move(double))); + connect(horizontalScrollBar, SIGNAL(valueChanged(int)), keyframeView, SLOT(set_x_scroll(int))); + connect(verticalScrollBar, SIGNAL(valueChanged(int)), keyframeView, SLOT(set_y_scroll(int))); + connect(verticalScrollBar, SIGNAL(valueChanged(int)), scrollArea->verticalScrollBar(), SLOT(setValue(int))); + connect(scrollArea->verticalScrollBar(), SIGNAL(valueChanged(int)), verticalScrollBar, SLOT(setValue(int))); +} + +void EffectControls::set_zoom(bool in) { + zoom *= (in) ? 2 : 0.5; + update_keyframes(); + + if (!selected_clips_.isEmpty()) { + scroll_to_frame(selected_clips_.first()->track()->sequence()->playhead); + } +} + +void EffectControls::update_keyframes() { + for (int i=0;iUpdateFromEffect(); + } + + headers->update_zoom(zoom); + keyframeView->update(); +} + +void EffectControls::delete_selected_keyframes() { + keyframeView->delete_selected_keyframes(); +} + +void EffectControls::scroll_to_frame(long frame) { + scroll_to_frame_internal(horizontalScrollBar, frame - keyframeView->visible_in, zoom, keyframeView->width()); +} + +void EffectControls::UpdateTitle() { + if (selected_clips_.isEmpty()) { + setWindowTitle(panel_name + tr("(none)")); + } else { + setWindowTitle(panel_name + selected_clips_.first()->name()); + } +} + +void EffectControls::setup_ui() { + QWidget* contents = new QWidget(this); + + QHBoxLayout* hlayout = new QHBoxLayout(contents); + hlayout->setSpacing(0); + hlayout->setMargin(0); + + splitter = new QSplitter(); + splitter->setOrientation(Qt::Horizontal); + splitter->setChildrenCollapsible(false); + + scrollArea = new QScrollArea(); + scrollArea->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setFrameShadow(QFrame::Plain); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + scrollArea->setWidgetResizable(true); + + QWidget* scrollAreaWidgetContents = new QWidget(); + + QHBoxLayout* scrollAreaLayout = new QHBoxLayout(scrollAreaWidgetContents); + scrollAreaLayout->setSpacing(0); + scrollAreaLayout->setMargin(0); + + effects_area = new EffectsArea(); + effects_area->setContextMenuPolicy(Qt::CustomContextMenu); + connect(effects_area, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(effects_area_context_menu())); + + QVBoxLayout* effects_area_layout = new QVBoxLayout(effects_area); + effects_area_layout->setSpacing(0); + effects_area_layout->setMargin(0); + + vcontainer = new QWidget(); + QVBoxLayout* vcontainerLayout = new QVBoxLayout(vcontainer); + vcontainerLayout->setSpacing(0); + vcontainerLayout->setMargin(0); + + QWidget* veHeader = new QWidget(); + veHeader->setObjectName(QStringLiteral("veHeader")); + veHeader->setStyleSheet(QLatin1String("#veHeader { background: rgba(0, 0, 0, 0.25); }")); + + QHBoxLayout* veHeaderLayout = new QHBoxLayout(veHeader); + veHeaderLayout->setSpacing(0); + veHeaderLayout->setMargin(0); + + QIcon add_effect_icon = olive::icon::CreateIconFromSVG(":/icons/add-effect.svg", false); + QIcon add_transition_icon = olive::icon::CreateIconFromSVG(":/icons/add-transition.svg", false); + + btnAddVideoEffect = new QPushButton(); + btnAddVideoEffect->setIcon(add_effect_icon); + veHeaderLayout->addWidget(btnAddVideoEffect); + connect(btnAddVideoEffect, SIGNAL(clicked(bool)), this, SLOT(video_effect_click())); + + veHeaderLayout->addStretch(); + + lblVideoEffects = new QLabel(); + QFont font; + font.setPointSize(9); + lblVideoEffects->setFont(font); + lblVideoEffects->setAlignment(Qt::AlignCenter); + veHeaderLayout->addWidget(lblVideoEffects); + + veHeaderLayout->addStretch(); + + btnAddVideoTransition = new QPushButton(); + btnAddVideoTransition->setIcon(add_transition_icon); + connect(btnAddVideoTransition, SIGNAL(clicked(bool)), this, SLOT(video_transition_click())); + veHeaderLayout->addWidget(btnAddVideoTransition); + + vcontainerLayout->addWidget(veHeader); + + video_effect_area = new QWidget(); + video_effect_layout = new QVBoxLayout(video_effect_area); + video_effect_layout->setSpacing(0); + video_effect_layout->setMargin(0); + + vcontainerLayout->addWidget(video_effect_area); + + effects_area_layout->addWidget(vcontainer); + + acontainer = new QWidget(); + QVBoxLayout* acontainerLayout = new QVBoxLayout(acontainer); + acontainerLayout->setSpacing(0); + acontainerLayout->setMargin(0); + QWidget* aeHeader = new QWidget(); + aeHeader->setObjectName(QStringLiteral("aeHeader")); + aeHeader->setStyleSheet(QLatin1String("#aeHeader { background: rgba(0, 0, 0, 0.25); }")); + + QHBoxLayout* aeHeaderLayout = new QHBoxLayout(aeHeader); + aeHeaderLayout->setSpacing(0); + aeHeaderLayout->setMargin(0); + + btnAddAudioEffect = new QPushButton(); + btnAddAudioEffect->setIcon(add_effect_icon); + connect(btnAddAudioEffect, SIGNAL(clicked(bool)), this, SLOT(audio_effect_click())); + aeHeaderLayout->addWidget(btnAddAudioEffect); + + aeHeaderLayout->addStretch(); + + lblAudioEffects = new QLabel(); + lblAudioEffects->setFont(font); + lblAudioEffects->setAlignment(Qt::AlignCenter); + aeHeaderLayout->addWidget(lblAudioEffects); + + aeHeaderLayout->addStretch(); + + btnAddAudioTransition = new QPushButton(); + btnAddAudioTransition->setIcon(add_transition_icon); + connect(btnAddAudioTransition, SIGNAL(clicked(bool)), this, SLOT(audio_transition_click())); + aeHeaderLayout->addWidget(btnAddAudioTransition); + + acontainerLayout->addWidget(aeHeader); + + audio_effect_area = new QWidget(); + audio_effect_layout = new QVBoxLayout(audio_effect_area); + audio_effect_layout->setSpacing(0); + audio_effect_layout->setMargin(0); + + acontainerLayout->addWidget(audio_effect_area); + + effects_area_layout->addWidget(acontainer); + + effects_area_layout->addStretch(); + + scrollAreaLayout->addWidget(effects_area); + + scrollArea->setWidget(scrollAreaWidgetContents); + splitter->addWidget(scrollArea); + + QWidget* keyframeArea = new QWidget(); + + QSizePolicy keyframe_sp; + keyframe_sp.setHorizontalPolicy(QSizePolicy::Minimum); + keyframe_sp.setVerticalPolicy(QSizePolicy::Preferred); + keyframe_sp.setHorizontalStretch(1); + keyframeArea->setSizePolicy(keyframe_sp); + + QVBoxLayout* keyframeAreaLayout = new QVBoxLayout(keyframeArea); + keyframeAreaLayout->setSpacing(0); + keyframeAreaLayout->setMargin(0); + + headers = new TimelineHeader(); + keyframeAreaLayout->addWidget(headers); + + QWidget* keyframeCenterWidget = new QWidget(); + + QHBoxLayout* keyframeCenterLayout = new QHBoxLayout(keyframeCenterWidget); + keyframeCenterLayout->setSpacing(0); + keyframeCenterLayout->setMargin(0); + + keyframeView = new KeyframeView(); + + keyframeCenterLayout->addWidget(keyframeView); + + verticalScrollBar = new QScrollBar(); + verticalScrollBar->setOrientation(Qt::Vertical); + + keyframeCenterLayout->addWidget(verticalScrollBar); + + + keyframeAreaLayout->addWidget(keyframeCenterWidget); + + horizontalScrollBar = new ResizableScrollBar(); + horizontalScrollBar->setOrientation(Qt::Horizontal); + + keyframeAreaLayout->addWidget(horizontalScrollBar); + + splitter->addWidget(keyframeArea); + + hlayout->addWidget(splitter); + + setWidget(contents); +} + +void EffectControls::Retranslate() { + panel_name = tr("Effects: "); + + btnAddVideoEffect->setToolTip(tr("Add Video Effect")); + lblVideoEffects->setText(tr("VIDEO EFFECTS")); + btnAddVideoTransition->setToolTip(tr("Add Video Transition")); + btnAddAudioEffect->setToolTip(tr("Add Audio Effect")); + lblAudioEffects->setText(tr("AUDIO EFFECTS")); + btnAddAudioTransition->setToolTip(tr("Add Audio Transition")); + + UpdateTitle(); +} + +void EffectControls::LoadLayoutState(const QByteArray &data) +{ + splitter->restoreState(data); +} + +QByteArray EffectControls::SaveLayoutState() +{ + return splitter->saveState(); +} + +void EffectControls::update_scrollbar() { + verticalScrollBar->setMaximum(qMax(0, effects_area->height() - keyframeView->height() - headers->height())); + verticalScrollBar->setPageStep(verticalScrollBar->height()); +} + +void EffectControls::queue_post_update() { + keyframeView->update(); + update_scrollbar(); +} + +void EffectControls::effects_area_context_menu() { + Menu menu(this); + + olive::MenuHelper.create_effect_paste_action(&menu); + + menu.exec(QCursor::pos()); +} + +bool EffectControls::focused() +{ + if (this->hasFocus() + || headers->hasFocus() + || keyframeView->hasFocus()) { + return true; + } + + return EffectsPanel::focused(); +} + +void EffectControls::video_effect_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_EFFECT, olive::kTypeVideo, selected_clips_); +} + +void EffectControls::audio_effect_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_EFFECT, olive::kTypeAudio, selected_clips_); +} + +void EffectControls::video_transition_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_TRANSITION, olive::kTypeVideo, selected_clips_); +} + +void EffectControls::audio_transition_click() { + olive::Global->ShowEffectMenu(EFFECT_TYPE_TRANSITION, olive::kTypeAudio, selected_clips_); +} + +void EffectControls::resizeEvent(QResizeEvent*) { + update_scrollbar(); +} + +void EffectControls::ClearEvent() +{ + keyframeView->SetEffects(open_effects_); + + vcontainer->setVisible(false); + acontainer->setVisible(false); + headers->setVisible(false); + keyframeView->setEnabled(false); + + UpdateTitle(); +} + +void EffectControls::LoadEvent() +{ + keyframeView->SetEffects(open_effects_); + + if (selected_clips_.size() > 0) { + keyframeView->setEnabled(true); + + headers->setVisible(true); + + QTimer::singleShot(50, this, SLOT(queue_post_update())); + } + + 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(); +} + +EffectsArea::EffectsArea(QWidget* parent) : + QWidget(parent) +{} + +void EffectsArea::resizeEvent(QResizeEvent *) +{ + parent_widget->setMinimumWidth(sizeHint().width()); +} + +void EffectsArea::receive_wheel_event(QWheelEvent *e) { + QApplication::sendEvent(this, e); +} diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h index 94f41eb72..78ac1c3f3 100644 --- a/panels/effectcontrols.h +++ b/panels/effectcontrols.h @@ -1,122 +1,122 @@ -/*** - - 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 EFFECTCONTROLS_H -#define EFFECTCONTROLS_H - -#include -#include -#include -#include -#include -#include -#include -#include -#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 "effectspanel.h" -#include "ui/effectui.h" - -class EffectsArea : public QWidget { - Q_OBJECT -public: - EffectsArea(QWidget* parent = nullptr); - QScrollArea* parent_widget; - KeyframeView* keyframe_area; - TimelineHeader* header; -protected: - void resizeEvent(QResizeEvent*); -public slots: - void receive_wheel_event(QWheelEvent* e); -}; - -class EffectControls : public EffectsPanel -{ - Q_OBJECT -public: - explicit EffectControls(QWidget *parent = nullptr); - - virtual bool focused() override; - void set_zoom(bool in); - void delete_selected_keyframes(); - void scroll_to_frame(long frame); - - double zoom; - - ResizableScrollBar* horizontalScrollBar; - QScrollBar* verticalScrollBar; - - virtual void Retranslate() override; - - virtual void LoadLayoutState(const QByteArray& data) override; - virtual QByteArray SaveLayoutState() override; -public slots: - void update_keyframes(); -private slots: - void video_effect_click(); - void audio_effect_click(); - void video_transition_click(); - void audio_transition_click(); - - - - void update_scrollbar(); - void queue_post_update(); - - void effects_area_context_menu(); -protected: - virtual void resizeEvent(QResizeEvent *event) override; - virtual void ClearEvent() override; - virtual void LoadEvent() override; -private: - void load_keyframes(); - void UpdateTitle(); - - void setup_ui(); - - QString panel_name; - - QWidget* video_effect_area; - QWidget* audio_effect_area; - QVBoxLayout* video_effect_layout; - QVBoxLayout* audio_effect_layout; - - QSplitter* splitter; - QPushButton* btnAddVideoEffect; - QLabel* lblVideoEffects; - QLabel* lblAudioEffects; - QPushButton* btnAddVideoTransition; - QPushButton* btnAddAudioEffect; - QPushButton* btnAddAudioTransition; - TimelineHeader* headers; - EffectsArea* effects_area; - QScrollArea* scrollArea; - KeyframeView* keyframeView; - QWidget* vcontainer; - QWidget* acontainer; -}; - -#endif // EFFECTCONTROLS_H +/*** + + 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 EFFECTCONTROLS_H +#define EFFECTCONTROLS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#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 "effectspanel.h" +#include "ui/effectui.h" + +class EffectsArea : public QWidget { + Q_OBJECT +public: + EffectsArea(QWidget* parent = nullptr); + QScrollArea* parent_widget; + KeyframeView* keyframe_area; + TimelineHeader* header; +protected: + void resizeEvent(QResizeEvent*); +public slots: + void receive_wheel_event(QWheelEvent* e); +}; + +class EffectControls : public EffectsPanel +{ + Q_OBJECT +public: + explicit EffectControls(QWidget *parent = nullptr); + + virtual bool focused() override; + void set_zoom(bool in); + void delete_selected_keyframes(); + void scroll_to_frame(long frame); + + double zoom; + + ResizableScrollBar* horizontalScrollBar; + QScrollBar* verticalScrollBar; + + virtual void Retranslate() override; + + virtual void LoadLayoutState(const QByteArray& data) override; + virtual QByteArray SaveLayoutState() override; +public slots: + void update_keyframes(); +private slots: + void video_effect_click(); + void audio_effect_click(); + void video_transition_click(); + void audio_transition_click(); + + + + void update_scrollbar(); + void queue_post_update(); + + void effects_area_context_menu(); +protected: + virtual void resizeEvent(QResizeEvent *event) override; + virtual void ClearEvent() override; + virtual void LoadEvent() override; +private: + void load_keyframes(); + void UpdateTitle(); + + void setup_ui(); + + QString panel_name; + + QWidget* video_effect_area; + QWidget* audio_effect_area; + QVBoxLayout* video_effect_layout; + QVBoxLayout* audio_effect_layout; + + QSplitter* splitter; + QPushButton* btnAddVideoEffect; + QLabel* lblVideoEffects; + QLabel* lblAudioEffects; + QPushButton* btnAddVideoTransition; + QPushButton* btnAddAudioEffect; + QPushButton* btnAddAudioTransition; + TimelineHeader* headers; + EffectsArea* effects_area; + QScrollArea* scrollArea; + KeyframeView* keyframeView; + QWidget* vcontainer; + QWidget* acontainer; +}; + +#endif // EFFECTCONTROLS_H diff --git a/panels/grapheditor.cpp b/panels/grapheditor.cpp index fb5d3d33c..644999cd9 100644 --- a/panels/grapheditor.cpp +++ b/panels/grapheditor.cpp @@ -1,256 +1,256 @@ -/*** - - 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 "grapheditor.h" - -#include -#include -#include -#include - -#include "ui/keyframenavigator.h" -#include "ui/timelineheader.h" -#include "timeline/timelinetools.h" -#include "ui/labelslider.h" -#include "ui/graphview.h" -#include "nodes/oldeffectnode.h" -#include "effects/effectfields.h" -#include "nodes/nodeio.h" -#include "timeline/clip.h" -#include "rendering/renderfunctions.h" -#include "panels.h" -#include "ui/icons.h" -#include "global/debug.h" - -GraphEditor::GraphEditor(QWidget* parent) : Panel(parent), row(nullptr) { - resize(720, 480); - - QWidget* main_widget = new QWidget(this); - QVBoxLayout* layout = new QVBoxLayout(main_widget); - setWidget(main_widget); - - QWidget* tool_widget = new QWidget(); - tool_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - QHBoxLayout* tools = new QHBoxLayout(tool_widget); - - QWidget* left_tool_widget = new QWidget(); - QHBoxLayout* left_tool_layout = new QHBoxLayout(left_tool_widget); - left_tool_layout->setSpacing(0); - left_tool_layout->setMargin(0); - tools->addWidget(left_tool_widget); - QWidget* center_tool_widget = new QWidget(); - QHBoxLayout* center_tool_layout = new QHBoxLayout(center_tool_widget); - center_tool_layout->setSpacing(0); - center_tool_layout->setMargin(0); - tools->addWidget(center_tool_widget); - QWidget* right_tool_widget = new QWidget(); - QHBoxLayout* right_tool_layout = new QHBoxLayout(right_tool_widget); - right_tool_layout->setSpacing(0); - right_tool_layout->setMargin(0); - tools->addWidget(right_tool_widget); - - keyframe_nav = new KeyframeNavigator(nullptr, false); - keyframe_nav->enable_keyframes(true); - keyframe_nav->enable_keyframe_toggle(false); - left_tool_layout->addWidget(keyframe_nav); - left_tool_layout->addStretch(); - - linear_button = new QPushButton(); - linear_button->setProperty("type", EFFECT_KEYFRAME_LINEAR); - linear_button->setCheckable(true); - bezier_button = new QPushButton(); - bezier_button->setProperty("type", EFFECT_KEYFRAME_BEZIER); - bezier_button->setCheckable(true); - hold_button = new QPushButton(); - hold_button->setProperty("type", EFFECT_KEYFRAME_HOLD); - hold_button->setCheckable(true); - - center_tool_layout->addStretch(); - center_tool_layout->addWidget(linear_button); - center_tool_layout->addWidget(bezier_button); - center_tool_layout->addWidget(hold_button); - - layout->addWidget(tool_widget); - - QWidget* central_widget = new QWidget(); - QVBoxLayout* central_layout = new QVBoxLayout(central_widget); - central_layout->setSpacing(0); - central_layout->setMargin(0); - header = new TimelineHeader(); - header->viewer = panel_sequence_viewer; - central_layout->addWidget(header); - view = new GraphView(); - central_layout->addWidget(view); - - layout->addWidget(central_widget); - - QWidget* value_widget = new QWidget(); - value_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - QHBoxLayout* values = new QHBoxLayout(value_widget); - values->addStretch(); - - QWidget* central_value_widget = new QWidget(); - value_layout = new QHBoxLayout(central_value_widget); - value_layout->setMargin(0); - value_layout->addWidget(new QLabel("")); // a spacer so the layout doesn't jump - values->addWidget(central_value_widget); - - values->addStretch(); - layout->addWidget(value_widget); - - current_row_desc = new QLabel(); - current_row_desc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - current_row_desc->setAlignment(Qt::AlignCenter); - layout->addWidget(current_row_desc); - - connect(view, SIGNAL(zoom_changed(double, double)), header, SLOT(update_zoom(double))); - connect(view, SIGNAL(x_scroll_changed(int)), header, SLOT(set_scroll(int))); - connect(view, SIGNAL(selection_changed(bool, int)), this, SLOT(set_key_button_enabled(bool, int))); - - connect(linear_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); - connect(bezier_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); - connect(hold_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); - - Retranslate(); -} - -NodeIO *GraphEditor::get_row() -{ - return row; -} - -void GraphEditor::Retranslate() { - setWindowTitle(tr("Graph Editor")); - linear_button->setText(tr("Linear")); - bezier_button->setText(tr("Bezier")); - hold_button->setText(tr("Hold")); -} - -void GraphEditor::update_panel() { - if (isVisible()) { - if (row != nullptr) { - int slider_index = 0; - 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), row->ParentNode()->Time()); - slider_index++; - } - } - } - - header->update(); - view->update(); - } -} - -bool GraphEditor::focused() -{ - return hasFocus() || view->hasFocus() || header->hasFocus(); -} - -void GraphEditor::set_row(NodeIO *r) { - for (int i=0;iIsKeyframing()) { - for (int i=0;iFieldCount();i++) { - EffectField* field = r->Field(i); - if (field->type() == EffectField::EFFECT_FIELD_DOUBLE) { - QPushButton* slider_button = new QPushButton(); - slider_button->setCheckable(true); - slider_button->setChecked(field->IsEnabled()); - slider_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/record.svg", false)); - slider_button->setProperty("field", i); - slider_button->setIconSize(slider_button->iconSize()*0.5); - connect(slider_button, SIGNAL(toggled(bool)), this, SLOT(set_field_visibility(bool))); - field_enable_buttons.append(slider_button); - value_layout->addWidget(slider_button); - - LabelSlider* slider = static_cast(field->CreateWidget()); - slider->SetColor(get_curve_color(i, r->FieldCount()).name()); - field_sliders_.append(slider); - value_layout->addWidget(slider); - - found_vals = true; - } - } - } - - if (found_vals) { - row = r; - /* FIXME - current_row_desc->setText(row->ParentNode()->parent_clip->name() - + " :: " + row->GetParentEffect()->name() - + " :: " + row->name()); - header->set_visible_in(r->ParentNode()->parent_clip->timeline_in()); - */ - - connect(keyframe_nav, SIGNAL(goto_previous_key()), row, SLOT(GoToPreviousKeyframe())); - connect(keyframe_nav, SIGNAL(toggle_key()), row, SLOT(ToggleKeyframe())); - connect(keyframe_nav, SIGNAL(goto_next_key()), row, SLOT(GoToNextKeyframe())); - } else { - row = nullptr; - current_row_desc->setText(nullptr); - } - view->set_row(row); - update_panel(); -} - -void GraphEditor::delete_selected_keys() { - view->delete_selected_keys(); -} - -void GraphEditor::select_all() { - view->select_all(); -} - -void GraphEditor::set_key_button_enabled(bool e, int type) { - linear_button->setEnabled(e); - linear_button->setChecked(type == EFFECT_KEYFRAME_LINEAR); - bezier_button->setEnabled(e); - bezier_button->setChecked(type == EFFECT_KEYFRAME_BEZIER); - hold_button->setEnabled(e); - hold_button->setChecked(type == EFFECT_KEYFRAME_HOLD); -} - -void GraphEditor::set_keyframe_type() { - linear_button->setChecked(linear_button == sender()); - bezier_button->setChecked(bezier_button == sender()); - hold_button->setChecked(hold_button == sender()); - view->set_selected_keyframe_type(sender()->property("type").toInt()); -} - -void GraphEditor::set_field_visibility(bool b) { - view->set_field_visibility(sender()->property("field").toInt(), b); -} +/*** + + 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 "grapheditor.h" + +#include +#include +#include +#include + +#include "ui/keyframenavigator.h" +#include "ui/timelineheader.h" +#include "timeline/timelinetools.h" +#include "ui/labelslider.h" +#include "ui/graphview.h" +#include "nodes/oldeffectnode.h" +#include "effects/effectfields.h" +#include "nodes/nodeio.h" +#include "timeline/clip.h" +#include "rendering/renderfunctions.h" +#include "panels.h" +#include "ui/icons.h" +#include "global/debug.h" + +GraphEditor::GraphEditor(QWidget* parent) : Panel(parent), row(nullptr) { + resize(720, 480); + + QWidget* main_widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(main_widget); + setWidget(main_widget); + + QWidget* tool_widget = new QWidget(); + tool_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + QHBoxLayout* tools = new QHBoxLayout(tool_widget); + + QWidget* left_tool_widget = new QWidget(); + QHBoxLayout* left_tool_layout = new QHBoxLayout(left_tool_widget); + left_tool_layout->setSpacing(0); + left_tool_layout->setMargin(0); + tools->addWidget(left_tool_widget); + QWidget* center_tool_widget = new QWidget(); + QHBoxLayout* center_tool_layout = new QHBoxLayout(center_tool_widget); + center_tool_layout->setSpacing(0); + center_tool_layout->setMargin(0); + tools->addWidget(center_tool_widget); + QWidget* right_tool_widget = new QWidget(); + QHBoxLayout* right_tool_layout = new QHBoxLayout(right_tool_widget); + right_tool_layout->setSpacing(0); + right_tool_layout->setMargin(0); + tools->addWidget(right_tool_widget); + + keyframe_nav = new KeyframeNavigator(nullptr, false); + keyframe_nav->enable_keyframes(true); + keyframe_nav->enable_keyframe_toggle(false); + left_tool_layout->addWidget(keyframe_nav); + left_tool_layout->addStretch(); + + linear_button = new QPushButton(); + linear_button->setProperty("type", EFFECT_KEYFRAME_LINEAR); + linear_button->setCheckable(true); + bezier_button = new QPushButton(); + bezier_button->setProperty("type", EFFECT_KEYFRAME_BEZIER); + bezier_button->setCheckable(true); + hold_button = new QPushButton(); + hold_button->setProperty("type", EFFECT_KEYFRAME_HOLD); + hold_button->setCheckable(true); + + center_tool_layout->addStretch(); + center_tool_layout->addWidget(linear_button); + center_tool_layout->addWidget(bezier_button); + center_tool_layout->addWidget(hold_button); + + layout->addWidget(tool_widget); + + QWidget* central_widget = new QWidget(); + QVBoxLayout* central_layout = new QVBoxLayout(central_widget); + central_layout->setSpacing(0); + central_layout->setMargin(0); + header = new TimelineHeader(); + header->viewer = panel_sequence_viewer; + central_layout->addWidget(header); + view = new GraphView(); + central_layout->addWidget(view); + + layout->addWidget(central_widget); + + QWidget* value_widget = new QWidget(); + value_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + QHBoxLayout* values = new QHBoxLayout(value_widget); + values->addStretch(); + + QWidget* central_value_widget = new QWidget(); + value_layout = new QHBoxLayout(central_value_widget); + value_layout->setMargin(0); + value_layout->addWidget(new QLabel("")); // a spacer so the layout doesn't jump + values->addWidget(central_value_widget); + + values->addStretch(); + layout->addWidget(value_widget); + + current_row_desc = new QLabel(); + current_row_desc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + current_row_desc->setAlignment(Qt::AlignCenter); + layout->addWidget(current_row_desc); + + connect(view, SIGNAL(zoom_changed(double, double)), header, SLOT(update_zoom(double))); + connect(view, SIGNAL(x_scroll_changed(int)), header, SLOT(set_scroll(int))); + connect(view, SIGNAL(selection_changed(bool, int)), this, SLOT(set_key_button_enabled(bool, int))); + + connect(linear_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); + connect(bezier_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); + connect(hold_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); + + Retranslate(); +} + +NodeIO *GraphEditor::get_row() +{ + return row; +} + +void GraphEditor::Retranslate() { + setWindowTitle(tr("Graph Editor")); + linear_button->setText(tr("Linear")); + bezier_button->setText(tr("Bezier")); + hold_button->setText(tr("Hold")); +} + +void GraphEditor::update_panel() { + if (isVisible()) { + if (row != nullptr) { + int slider_index = 0; + 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), row->ParentNode()->Time()); + slider_index++; + } + } + } + + header->update(); + view->update(); + } +} + +bool GraphEditor::focused() +{ + return hasFocus() || view->hasFocus() || header->hasFocus(); +} + +void GraphEditor::set_row(NodeIO *r) { + for (int i=0;iIsKeyframing()) { + for (int i=0;iFieldCount();i++) { + EffectField* field = r->Field(i); + if (field->type() == EffectField::EFFECT_FIELD_DOUBLE) { + QPushButton* slider_button = new QPushButton(); + slider_button->setCheckable(true); + slider_button->setChecked(field->IsEnabled()); + slider_button->setIcon(olive::icon::CreateIconFromSVG(":/icons/record.svg", false)); + slider_button->setProperty("field", i); + slider_button->setIconSize(slider_button->iconSize()*0.5); + connect(slider_button, SIGNAL(toggled(bool)), this, SLOT(set_field_visibility(bool))); + field_enable_buttons.append(slider_button); + value_layout->addWidget(slider_button); + + LabelSlider* slider = static_cast(field->CreateWidget()); + slider->SetColor(get_curve_color(i, r->FieldCount()).name()); + field_sliders_.append(slider); + value_layout->addWidget(slider); + + found_vals = true; + } + } + } + + if (found_vals) { + row = r; + /* FIXME + current_row_desc->setText(row->ParentNode()->parent_clip->name() + + " :: " + row->GetParentEffect()->name() + + " :: " + row->name()); + header->set_visible_in(r->ParentNode()->parent_clip->timeline_in()); + */ + + connect(keyframe_nav, SIGNAL(goto_previous_key()), row, SLOT(GoToPreviousKeyframe())); + connect(keyframe_nav, SIGNAL(toggle_key()), row, SLOT(ToggleKeyframe())); + connect(keyframe_nav, SIGNAL(goto_next_key()), row, SLOT(GoToNextKeyframe())); + } else { + row = nullptr; + current_row_desc->setText(nullptr); + } + view->set_row(row); + update_panel(); +} + +void GraphEditor::delete_selected_keys() { + view->delete_selected_keys(); +} + +void GraphEditor::select_all() { + view->select_all(); +} + +void GraphEditor::set_key_button_enabled(bool e, int type) { + linear_button->setEnabled(e); + linear_button->setChecked(type == EFFECT_KEYFRAME_LINEAR); + bezier_button->setEnabled(e); + bezier_button->setChecked(type == EFFECT_KEYFRAME_BEZIER); + hold_button->setEnabled(e); + hold_button->setChecked(type == EFFECT_KEYFRAME_HOLD); +} + +void GraphEditor::set_keyframe_type() { + linear_button->setChecked(linear_button == sender()); + bezier_button->setChecked(bezier_button == sender()); + hold_button->setChecked(hold_button == sender()); + view->set_selected_keyframe_type(sender()->property("type").toInt()); +} + +void GraphEditor::set_field_visibility(bool b) { + view->set_field_visibility(sender()->property("field").toInt(), b); +} diff --git a/panels/grapheditor.h b/panels/grapheditor.h index b5dc35922..b1f392a9a 100644 --- a/panels/grapheditor.h +++ b/panels/grapheditor.h @@ -1,68 +1,68 @@ -/*** - - 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 GRAPHEDITOR_H -#define GRAPHEDITOR_H - -#include -#include -#include - -#include "ui/panel.h" -#include "ui/graphview.h" -#include "ui/timelineheader.h" -#include "ui/labelslider.h" -#include "ui/keyframenavigator.h" -#include "nodes/nodeio.h" - -class GraphEditor : public Panel { - Q_OBJECT -public: - GraphEditor(QWidget* parent = nullptr); - - NodeIO* get_row(); - void set_row(NodeIO* r); - - void update_panel(); - virtual bool focused() override; - void delete_selected_keys(); - void select_all(); - - virtual void Retranslate() override; -protected: -private: - GraphView* view; - TimelineHeader* header; - QHBoxLayout* value_layout; - QVector field_sliders_; - QVector field_enable_buttons; - QLabel* current_row_desc; - NodeIO* row; - KeyframeNavigator* keyframe_nav; - QPushButton* linear_button; - QPushButton* bezier_button; - QPushButton* hold_button; -private slots: - void set_key_button_enabled(bool e, int type); - void set_keyframe_type(); - void set_field_visibility(bool b); -}; - -#endif // GRAPHEDITOR_H +/*** + + 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 GRAPHEDITOR_H +#define GRAPHEDITOR_H + +#include +#include +#include + +#include "ui/panel.h" +#include "ui/graphview.h" +#include "ui/timelineheader.h" +#include "ui/labelslider.h" +#include "ui/keyframenavigator.h" +#include "nodes/nodeio.h" + +class GraphEditor : public Panel { + Q_OBJECT +public: + GraphEditor(QWidget* parent = nullptr); + + NodeIO* get_row(); + void set_row(NodeIO* r); + + void update_panel(); + virtual bool focused() override; + void delete_selected_keys(); + void select_all(); + + virtual void Retranslate() override; +protected: +private: + GraphView* view; + TimelineHeader* header; + QHBoxLayout* value_layout; + QVector field_sliders_; + QVector field_enable_buttons; + QLabel* current_row_desc; + NodeIO* row; + KeyframeNavigator* keyframe_nav; + QPushButton* linear_button; + QPushButton* bezier_button; + QPushButton* hold_button; +private slots: + void set_key_button_enabled(bool e, int type); + void set_keyframe_type(); + void set_field_visibility(bool b); +}; + +#endif // GRAPHEDITOR_H diff --git a/panels/panels.cpp b/panels/panels.cpp index 2145e7478..06af4b80b 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -1,128 +1,128 @@ -/*** - - 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 "panels.h" - -#include "timeline/sequence.h" -#include "timeline/clip.h" -#include "effects/transition.h" -#include "global/config.h" -#include "global/debug.h" -#include "global/math.h" - -#include -#include - -QVector panel_project; -EffectControls* panel_effect_controls = nullptr; -Viewer* panel_sequence_viewer = nullptr; -Viewer* panel_footage_viewer = 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(); - 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::config.hover_focus || force_hover) { - for (int i=0;iunderMouse()) { - w = olive::panels.at(i); - break; - } - } - } - if (w == nullptr) { - for (int i=0;ifocused()) { - w = olive::panels.at(i); - break; - } - } - } - return w; -} - -void alloc_panels(QWidget* parent) { - panel_sequence_viewer = new Viewer(parent); - panel_sequence_viewer->setObjectName("seq_viewer"); - panel_footage_viewer = new Viewer(parent); - panel_footage_viewer->setObjectName("footage_viewer"); - panel_footage_viewer->show_videoaudio_buttons(true); - 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); - panel_effect_controls->setObjectName("fx_controls"); - 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() { - delete panel_sequence_viewer; - panel_sequence_viewer = nullptr; - delete panel_footage_viewer; - panel_footage_viewer = nullptr; - - for (int i=0;ivalue() == bar->minimum() || bar->value() == bar->maximum()) { - return; - } - - int screen_point = getScreenPointFromFrame(zoom, frame) - bar->value(); - int min_x = area_width*0.1; - int max_x = area_width-min_x; - if (screen_point < min_x) { - bar->setValue(getScreenPointFromFrame(zoom, frame) - min_x); - } else if (screen_point > max_x) { - bar->setValue(getScreenPointFromFrame(zoom, frame) - max_x); - } -} +/*** + + 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 "panels.h" + +#include "timeline/sequence.h" +#include "timeline/clip.h" +#include "effects/transition.h" +#include "global/config.h" +#include "global/debug.h" +#include "global/math.h" + +#include +#include + +QVector panel_project; +EffectControls* panel_effect_controls = nullptr; +Viewer* panel_sequence_viewer = nullptr; +Viewer* panel_footage_viewer = 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(); + 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::config.hover_focus || force_hover) { + for (int i=0;iunderMouse()) { + w = olive::panels.at(i); + break; + } + } + } + if (w == nullptr) { + for (int i=0;ifocused()) { + w = olive::panels.at(i); + break; + } + } + } + return w; +} + +void alloc_panels(QWidget* parent) { + panel_sequence_viewer = new Viewer(parent); + panel_sequence_viewer->setObjectName("seq_viewer"); + panel_footage_viewer = new Viewer(parent); + panel_footage_viewer->setObjectName("footage_viewer"); + panel_footage_viewer->show_videoaudio_buttons(true); + 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); + panel_effect_controls->setObjectName("fx_controls"); + 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() { + delete panel_sequence_viewer; + panel_sequence_viewer = nullptr; + delete panel_footage_viewer; + panel_footage_viewer = nullptr; + + for (int i=0;ivalue() == bar->minimum() || bar->value() == bar->maximum()) { + return; + } + + int screen_point = getScreenPointFromFrame(zoom, frame) - bar->value(); + int min_x = area_width*0.1; + int max_x = area_width-min_x; + if (screen_point < min_x) { + bar->setValue(getScreenPointFromFrame(zoom, frame) - min_x); + } else if (screen_point > max_x) { + bar->setValue(getScreenPointFromFrame(zoom, frame) - max_x); + } +} diff --git a/panels/panels.h b/panels/panels.h index 70af99b75..cbce80272 100644 --- a/panels/panels.h +++ b/panels/panels.h @@ -1,45 +1,45 @@ -/*** - - 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 PANELS_H -#define PANELS_H - -#include "timeline.h" -#include "effectcontrols.h" -#include "viewer.h" -#include "grapheditor.h" -#include "project.h" -#include "nodeeditor.h" - -extern QVector panel_project; -extern EffectControls* panel_effect_controls; -extern Viewer* panel_sequence_viewer; -extern Viewer* panel_footage_viewer; -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); -void alloc_panels(QWidget *parent); -void free_panels(); -void scroll_to_frame_internal(QScrollBar* bar, long frame, double zoom, int area_width); - -#endif // PANELS_H +/*** + + 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 PANELS_H +#define PANELS_H + +#include "timeline.h" +#include "effectcontrols.h" +#include "viewer.h" +#include "grapheditor.h" +#include "project.h" +#include "nodeeditor.h" + +extern QVector panel_project; +extern EffectControls* panel_effect_controls; +extern Viewer* panel_sequence_viewer; +extern Viewer* panel_footage_viewer; +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); +void alloc_panels(QWidget *parent); +void free_panels(); +void scroll_to_frame_internal(QScrollBar* bar, long frame, double zoom, int area_width); + +#endif // PANELS_H diff --git a/panels/project.h b/panels/project.h index 2cc8c826c..973c902b2 100644 --- a/panels/project.h +++ b/panels/project.h @@ -1,97 +1,97 @@ -/*** - - 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 PROJECT_H -#define PROJECT_H - -#include -#include -#include -#include -#include -#include -#include - -#include "project/projectmodel.h" -#include "project/projectfilter.h" -#include "project/projectelements.h" -#include "project/sourcescommon.h" -#include "ui/panel.h" -#include "ui/sourceiconview.h" -#include "timeline/mediaimportdata.h" -#include "undo/undo.h" -#include "ui/sourcetable.h" - -class Project : public Panel { - Q_OBJECT -public: - explicit Project(QWidget *parent = nullptr); - - void ConnectFilterToModel(); - void DisconnectFilterToModel(); - - virtual bool focused() override; - - Media* get_selected_folder(); - bool reveal_media(Media *media, QModelIndex parent = QModelIndex()); - - Media* item_to_media(const QModelIndex& index); - MediaPtr item_to_media_ptr(const QModelIndex &index); - - QModelIndexList get_current_selected(); - - bool IsToolbarVisible(); - bool IsProjectWidget(QObject *child); - - virtual void Retranslate() override; -protected: -public slots: - void delete_selected_media(); - void duplicate_selected(); - void delete_clips_using_selected_media(); - void replace_selected_file(); - void replace_clip_media(); - void open_properties(); - void new_folder(); - void SetToolbarVisible(bool visible); -private: - QWidget* icon_view_container; - QSlider* icon_size_slider; - QPushButton* directory_up; - QLineEdit* toolbar_search; - - QWidget* toolbar_widget; - SourceTable* tree_view; - SourceIconView* icon_view; - - ProjectFilter sorter; - SourcesCommon sources_common; -private slots: - void update_view_type(); - void set_icon_view(); - void set_list_view(); - void set_tree_view(); - void set_icon_view_size(int); - void set_up_dir_enabled(); - void go_up_dir(); - void make_new_menu(); -}; - -#endif // PROJECT_H +/*** + + 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 PROJECT_H +#define PROJECT_H + +#include +#include +#include +#include +#include +#include +#include + +#include "project/projectmodel.h" +#include "project/projectfilter.h" +#include "project/projectelements.h" +#include "project/sourcescommon.h" +#include "ui/panel.h" +#include "ui/sourceiconview.h" +#include "timeline/mediaimportdata.h" +#include "undo/undo.h" +#include "ui/sourcetable.h" + +class Project : public Panel { + Q_OBJECT +public: + explicit Project(QWidget *parent = nullptr); + + void ConnectFilterToModel(); + void DisconnectFilterToModel(); + + virtual bool focused() override; + + Media* get_selected_folder(); + bool reveal_media(Media *media, QModelIndex parent = QModelIndex()); + + Media* item_to_media(const QModelIndex& index); + MediaPtr item_to_media_ptr(const QModelIndex &index); + + QModelIndexList get_current_selected(); + + bool IsToolbarVisible(); + bool IsProjectWidget(QObject *child); + + virtual void Retranslate() override; +protected: +public slots: + void delete_selected_media(); + void duplicate_selected(); + void delete_clips_using_selected_media(); + void replace_selected_file(); + void replace_clip_media(); + void open_properties(); + void new_folder(); + void SetToolbarVisible(bool visible); +private: + QWidget* icon_view_container; + QSlider* icon_size_slider; + QPushButton* directory_up; + QLineEdit* toolbar_search; + + QWidget* toolbar_widget; + SourceTable* tree_view; + SourceIconView* icon_view; + + ProjectFilter sorter; + SourcesCommon sources_common; +private slots: + void update_view_type(); + void set_icon_view(); + void set_list_view(); + void set_tree_view(); + void set_icon_view_size(int); + void set_up_dir_enabled(); + void go_up_dir(); + void make_new_menu(); +}; + +#endif // PROJECT_H diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 5656fb9be..d13e78f1d 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -1,1126 +1,1126 @@ -/*** - - 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 "timeline.h" - -#include -#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 "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 "global/clipboard.h" -#include "global/math.h" -#include "ui/timelineheader.h" -#include "ui/resizablescrollbar.h" -#include "ui/audiomonitor.h" -#include "ui/flowlayout.h" -#include "ui/cursors.h" -#include "ui/mainwindow.h" -#include "undo/undostack.h" -#include "global/debug.h" -#include "global/timing.h" -#include "ui/menu.h" - -Timeline::Timeline(QWidget *parent) : - Panel(parent), - cursor_frame(0), - cursor_track(nullptr), - zoom(1.0), - zoom_just_changed(false), - showing_all(false), - selecting(false), - rect_select_init(false), - rect_select_proc(false), - moving_init(false), - moving_proc(false), - move_insert(false), - 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(nullptr), - transition_tool_close_clip(nullptr), - hand_moving(false), - block_repaints(false), - scroll(0), - sequence_(nullptr) -{ - setup_ui(); - - headers->viewer = panel_sequence_viewer; - - video_area->SetAlignment(olive::timeline::kAlignmentBottom); - - tool_buttons.append(toolArrowButton); - tool_buttons.append(toolEditButton); - tool_buttons.append(toolRippleButton); - tool_buttons.append(toolRazorButton); - tool_buttons.append(toolSlipButton); - tool_buttons.append(toolSlideButton); - 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(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::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; - } - - if (sequence_ != nullptr) { - sequence_->SetGLContext(nullptr); - } - - sequence_ = sequence; - - if (sequence_ != nullptr) { - sequence_->SetGLContext(QOpenGLContext::globalShareContext()); - } - - update_sequence(); - video_area->SetTrackType(sequence_.get(), olive::kTypeVideo); - audio_area->SetTrackType(sequence_.get(), olive::kTypeAudio); - repaint_timeline(); - - emit SequenceChanged(sequence_); -} - -void Timeline::Retranslate() { - toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)"); - toolEditButton->setToolTip(tr("Edit Tool") + " (X)"); - toolRippleButton->setToolTip(tr("Ripple Tool") + " (B)"); - toolRazorButton->setToolTip(tr("Razor Tool") + " (C)"); - toolSlipButton->setToolTip(tr("Slip Tool") + " (Y)"); - toolSlideButton->setToolTip(tr("Slide Tool") + " (U)"); - toolHandButton->setToolTip(tr("Hand Tool") + " (H)"); - toolTransitionButton->setToolTip(tr("Transition Tool") + " (T)"); - snappingButton->setToolTip(tr("Snapping") + " (S)"); - zoomInButton->setToolTip(tr("Zoom In") + " (=)"); - zoomOutButton->setToolTip(tr("Zoom Out") + " (-)"); - recordButton->setToolTip(tr("Record audio")); - addButton->setToolTip(tr("Add title, solid, bars, etc.")); - - UpdateTitle(); -} - -void Timeline::toggle_show_all() { - if (sequence_ != nullptr) { - showing_all = !showing_all; - if (showing_all) { - old_zoom = zoom; - set_zoom_value(double(timeline_area->width() - 200) / double(sequence_->GetEndFrame())); - } else { - set_zoom_value(old_zoom); - } - } -} - -void Timeline::toggle_links() -{ - if (sequence_ != nullptr) { - sequence_->ToggleLinksOnSelected(); - } -} - -void Timeline::add_transition() { - ComboAction* ca = new ComboAction(); - bool adding = false; - - 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::undo_stack.push(ca); - } else { - delete ca; - } - - update_ui(true); -} - -void Timeline::nest() { - if (sequence_ != nullptr) { - // get selected clips - QVector selected_clips = sequence_->SelectedClips(); - - // nest them - if (!selected_clips.isEmpty()) { - - // get earliest point in selected clips - long earliest_point = LONG_MAX; - for (int i=0;itimeline_in(), earliest_point); - } - - ComboAction* ca = new ComboAction(); - - // create "nest" sequence with the same attributes as the current sequence - SequencePtr s = std::make_shared(); - - s->set_name(olive::project_model.GetNextSequenceName(tr("Nested Sequence"))); - s->set_width(sequence_->width()); - s->set_height(sequence_->height()); - s->set_frame_rate(sequence_->frame_rate()); - s->set_audio_frequency(sequence_->audio_frequency()); - s->set_audio_layout(sequence_->audio_layout()); - - QVector new_clips; - - // copy all selected clips to the nest - for (int i=0;iappend(new DeleteClipAction(c)); - - // copy to new - Track* track = s->TrackAt(c->type(), 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); - track->AddClip(copy); - - new_clips.append(copy); - } - - // relink clips in new nested sequences - olive::timeline::RelinkClips(selected_clips, new_clips); - - // add sequence to project - MediaPtr m = olive::project_model.CreateSequence(ca, s, false, nullptr); - - // add nested sequence to active sequence - QVector media_list; - media_list.append(m.get()); - olive::timeline::CreateGhostsFromMedia(sequence_.get(), earliest_point, media_list); - - // ensure ghosts won't overlap anything - 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 a track, and seek again. - g.track = g.track->Next(); - - // Restart entire loop again - j = -1; - break; - - } - } - } - } - - - sequence_->AddClipsFromGhosts(ca, ghosts); - - panel_graph_editor->set_row(nullptr); - panel_effect_controls->Clear(true); - sequence_->ClearSelections(); - - olive::undo_stack.push(ca); - - update_ui(true); - } - } -} - -void Timeline::update_sequence() { - bool null_sequence = (sequence_ == nullptr); - - for (int i=0;isetEnabled(!null_sequence); - } - snappingButton->setEnabled(!null_sequence); - zoomInButton->setEnabled(!null_sequence); - zoomOutButton->setEnabled(!null_sequence); - recordButton->setEnabled(!null_sequence); - addButton->setEnabled(!null_sequence); - headers->setEnabled(!null_sequence); - - UpdateTitle(); -} - -bool Timeline::focused() { - return (sequence_ != nullptr && (headers->hasFocus() || video_area->hasFocus() || audio_area->hasFocus())); -} - -void Timeline::repaint_timeline() { - if (!block_repaints) { - bool draw = true; - - if (sequence_ != nullptr - && !horizontalScrollBar->isSliderDown() - && !horizontalScrollBar->is_resizing() - && panel_sequence_viewer->playing - && !zoom_just_changed) { - // auto scroll - 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::config.autoscroll == olive::AUTOSCROLL_SMOOTH_SCROLL) { - if (center_scroll_to_playhead(horizontalScrollBar, zoom, sequence_->playhead)) { - draw = false; - } - } - } - - if (draw) { - headers->update(); - video_area->update(); - audio_area->update(); - - if (sequence_ != nullptr - && !zoom_just_changed) { - set_sb_max(); - } - } - - zoom_just_changed = false; - } -} - -void Timeline::select_all() { - if (sequence_ != nullptr) { - sequence_->SelectAll(); - repaint_timeline(); - } -} - -void Timeline::scroll_to_frame(long frame) { - scroll_to_frame_internal(horizontalScrollBar, frame, zoom, timeline_area->width()); -} - -void Timeline::resizeEvent(QResizeEvent *) { - // adjust maximum scrollbar - if (sequence_ != nullptr) set_sb_max(); - - - // resize tool button widget to its contents - QList tool_button_children = tool_button_widget->findChildren(); - - int horizontal_spacing = static_cast(tool_button_widget->layout())->horizontalSpacing(); - int vertical_spacing = static_cast(tool_button_widget->layout())->verticalSpacing(); - int total_area = tool_button_widget->height(); - - int button_count = tool_button_children.size(); - int button_height = tool_button_children.at(0)->sizeHint().height() + vertical_spacing; - - int cols = 0; - - int col_height; - - if (button_height < total_area) { - do { - cols++; - col_height = (qCeil(double(button_count)/double(cols))*button_height)-vertical_spacing; - } while (col_height > total_area); - } else { - cols = button_count; - } - - tool_button_widget->setFixedWidth((tool_button_children.at(0)->sizeHint().width())*cols + horizontal_spacing*(cols-1) + 1); -} - -void Timeline::toggle_enable_on_selected_clips() { - if (sequence_ != nullptr) { - - // get currently selected clips - QVector selected_clips = sequence_->SelectedClips(); - - if (!selected_clips.isEmpty()) { - // if clips are selected, create an undoable action - SetClipProperty* set_action = new SetClipProperty(kSetClipPropertyEnabled); - - // add each selected clip to the action - for (int i=0;iAddSetting(c, !c->enabled()); - } - - // push the action - olive::undo_stack.push(set_action); - update_ui(false); - } - } -} - -void Timeline::set_zoom_value(double v) { - // set zoom value - zoom = v; - - // update header zoom to match - headers->update_zoom(zoom); - - // set flag that zoom has just changed to prevent auto-scrolling since we change the scroll below - zoom_just_changed = true; - - // set scrollbar to center the playhead - if (sequence_ != nullptr) { - // update scrollbar maximum value for new zoom - set_sb_max(); - - if (!horizontalScrollBar->is_resizing()) { - center_scroll_to_playhead(horizontalScrollBar, zoom, sequence_->playhead); - } - } - - // repaint the timeline for the new zoom/location - repaint_timeline(); -} - -void Timeline::multiply_zoom(double m) { - showing_all = false; - set_zoom_value(zoom * m); -} - -void Timeline::zoom_in() { - multiply_zoom(2.0); -} - -void Timeline::zoom_out() { - multiply_zoom(0.5); -} - -void Timeline::ChangeTrackHeightUniformly(int diff) { - if (sequence_ != nullptr) { - sequence_->ChangeTrackHeightsRelatively(diff); - } - - // update the timeline - repaint_timeline(); -} - -void Timeline::IncreaseTrackHeight() { - ChangeTrackHeightUniformly(olive::timeline::kTrackHeightIncrement); -} - -void Timeline::DecreaseTrackHeight() { - ChangeTrackHeightUniformly(-olive::timeline::kTrackHeightIncrement); -} - -void Timeline::snapping_clicked(bool checked) { - olive::timeline::snapping = checked; -} - -/* -bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool relink) { - Clip* c = sequence_->clips.at(clip).get(); - if (c != nullptr) { - QVector pre_clips; - QVector post_clips; - - ClipPtr post = split_clip(ca, true, clip, frame); - - if (post == nullptr) { - return false; - } else { - post_clips.append(post); - - // if alt is not down, split clips links too - if (relink) { - pre_clips.append(clip); - - bool original_clip_is_selected = c->IsSelected(); - - // find linked clips of old clip - for (int i=0;ilinked.size();i++) { - int l = c->linked.at(i); - 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(sequence_.get(), post_clips)); - return true; - } - } - return false; -} -*/ - - - -void Timeline::copy(bool del) { - if (sequence_ != nullptr) { - sequence_->AddSelectionsToClipboard(del); - } -} - -void Timeline::ripple_delete() { - - 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::ripple_delete_empty_space() -{ - if (sequence_ != nullptr) { - ComboAction* ca = new ComboAction(); - sequence_->RippleDeleteEmptySpace(ca, cursor_track, cursor_frame); - olive::undo_stack.push(ca); - - repaint_timeline(); - } -} - -void Timeline::set_marker() { - // determine if any clips are selected, and if so add markers to clips rather than the sequence - - QVector selected_clips = sequence_->SelectedClips(); - - 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--; - } - } - - // Check if we removed them all - if (selected_clips.isEmpty()) { - return; - } - - // If not, let's create markers on them - Marker::SetOnClips(selected_clips); - - } -} - -void Timeline::delete_inout() { - if (sequence_ != nullptr) { - sequence_->DeleteInToOut(false); - } -} - -void Timeline::ripple_delete_inout() { - if (sequence_ != nullptr) { - sequence_->DeleteInToOut(true); - } -} - -void Timeline::ripple_to_in_point() { - if (sequence_ != nullptr) { - sequence_->EditToPoint(true, true); - } -} - -void Timeline::ripple_to_out_point() { - if (sequence_ != nullptr) { - sequence_->EditToPoint(false, true); - } -} - -void Timeline::edit_to_in_point() { - if (sequence_ != nullptr) { - sequence_->EditToPoint(true, false); - } -} - -void Timeline::edit_to_out_point() { - if (sequence_ != nullptr) { - sequence_->EditToPoint(false, false); - } -} - -void Timeline::deselect() { - 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 = qFloor(double(x) / zoom); - if (f < 0) { - return 0; - } - return f; -} - -int getScreenPointFromFrame(double zoom, long frame) { - return qRound(double(frame)*zoom); -} - -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 - QVector area_tracks = sequence_->GetTrackList(area->track_type()); - - for (int j=0;jview()->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; -} - -void Timeline::add_btn_click() { - Menu add_menu(this); - - QAction* titleMenuItem = new QAction(&add_menu); - titleMenuItem->setText(tr("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(olive::timeline::ADD_OBJ_SOLID); - add_menu.addAction(solidMenuItem); - - QAction* barsMenuItem = new QAction(&add_menu); - barsMenuItem->setText(tr("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(olive::timeline::ADD_OBJ_TONE); - add_menu.addAction(toneMenuItem); - - QAction* noiseMenuItem = new QAction(&add_menu); - noiseMenuItem->setText(tr("Noise...")); - noiseMenuItem->setData(olive::timeline::ADD_OBJ_NOISE); - add_menu.addAction(noiseMenuItem); - - connect(&add_menu, SIGNAL(triggered(QAction*)), this, SLOT(add_menu_item(QAction*))); - - add_menu.exec(QCursor::pos()); -} - -void Timeline::add_menu_item(QAction* action) { - creating = true; - creating_object = static_cast(action->data().toInt()); -} - -void Timeline::setScroll(int s) { - scroll = s; - headers->set_scroll(s); - repaint_timeline(); -} - -void Timeline::record_btn_click() { - if (olive::ActiveProjectFilename.isEmpty()) { - QMessageBox::critical(this, - tr("Unsaved Project"), - tr("You must save this project before you can record audio in it."), - QMessageBox::Ok); - } else { - creating = true; - 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); - } -} - -void Timeline::transition_tool_click() { - creating = false; - - Menu transition_menu(this); - - 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(); - - 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); - } - } - - connect(&transition_menu, SIGNAL(triggered(QAction*)), this, SLOT(transition_menu_select(QAction*))); - - toolTransitionButton->setChecked(false); - - transition_menu.exec(QCursor::pos()); -} - -void Timeline::transition_menu_select(QAction* a) { - transition_tool_meta = static_cast(a->data().toInt()); - timeline_area->setCursor(Qt::CrossCursor); - olive::timeline::current_tool = olive::timeline::TIMELINE_TOOL_TRANSITION; - toolTransitionButton->setChecked(true); -} - -void Timeline::resize_move(double z) { - set_zoom_value(zoom * z); -} - -void Timeline::set_sb_max() { - headers->set_scrollbar_max(horizontalScrollBar, sequence_->GetEndFrame(), editAreas->width() - getScreenPointFromFrame(zoom, 200)); -} - -void Timeline::UpdateTitle() { - setWindowTitle( - tr("Timeline: %1").arg( - (sequence_ == nullptr) ? tr("(none)") : sequence_->name() - ) - ); -} - -void Timeline::setup_ui() { - QWidget* dockWidgetContents = new QWidget(); - - QHBoxLayout* horizontalLayout = new QHBoxLayout(dockWidgetContents); - horizontalLayout->setSpacing(0); - horizontalLayout->setMargin(0); - - setWidget(dockWidgetContents); - - tool_button_widget = new QWidget(); - tool_button_widget->setObjectName("timeline_toolbar"); - tool_button_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - FlowLayout* tool_buttons_layout = new FlowLayout(tool_button_widget); - tool_buttons_layout->setSpacing(4); - tool_buttons_layout->setMargin(0); - - toolArrowButton = new QPushButton(); - toolArrowButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/arrow.svg"))); - toolArrowButton->setCheckable(true); - 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", 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", 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", 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", 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", olive::timeline::TIMELINE_TOOL_SLIDE); - connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); - tool_buttons_layout->addWidget(toolSlideButton); - - toolHandButton = new QPushButton(); - toolHandButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/hand.svg"))); - toolHandButton->setCheckable(true); - - 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(); - toolTransitionButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/transition-tool.svg"))); - toolTransitionButton->setCheckable(true); - connect(toolTransitionButton, SIGNAL(clicked(bool)), this, SLOT(transition_tool_click())); - tool_buttons_layout->addWidget(toolTransitionButton); - - snappingButton = new QPushButton(); - snappingButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/magnet.svg"))); - snappingButton->setCheckable(true); - snappingButton->setChecked(true); - connect(snappingButton, SIGNAL(toggled(bool)), this, SLOT(snapping_clicked(bool))); - tool_buttons_layout->addWidget(snappingButton); - - zoomInButton = new QPushButton(); - zoomInButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/zoomin.svg"))); - connect(zoomInButton, SIGNAL(clicked(bool)), this, SLOT(zoom_in())); - tool_buttons_layout->addWidget(zoomInButton); - - zoomOutButton = new QPushButton(); - zoomOutButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/zoomout.svg"))); - connect(zoomOutButton, SIGNAL(clicked(bool)), this, SLOT(zoom_out())); - tool_buttons_layout->addWidget(zoomOutButton); - - recordButton = new QPushButton(); - recordButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/record.svg"))); - connect(recordButton, SIGNAL(clicked(bool)), this, SLOT(record_btn_click())); - tool_buttons_layout->addWidget(recordButton); - - addButton = new QPushButton(); - addButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/add-button.svg"))); - connect(addButton, SIGNAL(clicked()), this, SLOT(add_btn_click())); - tool_buttons_layout->addWidget(addButton); - - horizontalLayout->addWidget(tool_button_widget); - - timeline_area = new QWidget(); - QSizePolicy timeline_area_policy(QSizePolicy::Minimum, QSizePolicy::Minimum); - timeline_area_policy.setHorizontalStretch(1); - timeline_area_policy.setVerticalStretch(0); - timeline_area_policy.setHeightForWidth(timeline_area->sizePolicy().hasHeightForWidth()); - timeline_area->setSizePolicy(timeline_area_policy); - - QVBoxLayout* timeline_area_layout = new QVBoxLayout(timeline_area); - 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_header_layout->addWidget(headers); - timeline_area_layout->addLayout(timeline_header_layout); - - editAreas = new QWidget(); - QHBoxLayout* editAreaLayout = new QHBoxLayout(editAreas); - editAreaLayout->setSpacing(0); - editAreaLayout->setContentsMargins(0, 0, 0, 0); - - QSplitter* splitter = new QSplitter(); - splitter->setChildrenCollapsible(false); - splitter->setOrientation(Qt::Vertical); - - video_area = new TimelineArea(this); - areas.append(video_area); - splitter->addWidget(video_area); - - audio_area = new TimelineArea(this); - areas.append(audio_area); - splitter->addWidget(audio_area); - - editAreaLayout->addWidget(splitter); - - timeline_area_layout->addWidget(editAreas); - - horizontalScrollBar = new ResizableScrollBar(); - horizontalScrollBar->setMaximum(0); - horizontalScrollBar->setSingleStep(20); - horizontalScrollBar->setOrientation(Qt::Horizontal); - - timeline_area_layout->addWidget(horizontalScrollBar); - - horizontalLayout->addWidget(timeline_area); - - audio_monitor = new AudioMonitor(); - audio_monitor->setMinimumSize(QSize(50, 0)); - - horizontalLayout->addWidget(audio_monitor); - - setWidget(dockWidgetContents); -} - -void Timeline::set_tool() { - QPushButton* button = static_cast(sender()); - olive::timeline::current_tool = static_cast(button->property("tool").toInt()); - creating = false; - switch (olive::timeline::current_tool) { - case olive::timeline::TIMELINE_TOOL_EDIT: - timeline_area->setCursor(Qt::IBeamCursor); - break; - case olive::timeline::TIMELINE_TOOL_RAZOR: - timeline_area->setCursor(olive::cursor::Razor); - break; - case olive::timeline::TIMELINE_TOOL_HAND: - timeline_area->setCursor(Qt::OpenHandCursor); - break; - default: - timeline_area->setCursor(Qt::ArrowCursor); - } -} - -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(); -} +/*** + + 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 "timeline.h" + +#include +#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 "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 "global/clipboard.h" +#include "global/math.h" +#include "ui/timelineheader.h" +#include "ui/resizablescrollbar.h" +#include "ui/audiomonitor.h" +#include "ui/flowlayout.h" +#include "ui/cursors.h" +#include "ui/mainwindow.h" +#include "undo/undostack.h" +#include "global/debug.h" +#include "global/timing.h" +#include "ui/menu.h" + +Timeline::Timeline(QWidget *parent) : + Panel(parent), + cursor_frame(0), + cursor_track(nullptr), + zoom(1.0), + zoom_just_changed(false), + showing_all(false), + selecting(false), + rect_select_init(false), + rect_select_proc(false), + moving_init(false), + moving_proc(false), + move_insert(false), + 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(nullptr), + transition_tool_close_clip(nullptr), + hand_moving(false), + block_repaints(false), + scroll(0), + sequence_(nullptr) +{ + setup_ui(); + + headers->viewer = panel_sequence_viewer; + + video_area->SetAlignment(olive::timeline::kAlignmentBottom); + + tool_buttons.append(toolArrowButton); + tool_buttons.append(toolEditButton); + tool_buttons.append(toolRippleButton); + tool_buttons.append(toolRazorButton); + tool_buttons.append(toolSlipButton); + tool_buttons.append(toolSlideButton); + 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(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::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; + } + + if (sequence_ != nullptr) { + sequence_->SetGLContext(nullptr); + } + + sequence_ = sequence; + + if (sequence_ != nullptr) { + sequence_->SetGLContext(QOpenGLContext::globalShareContext()); + } + + update_sequence(); + video_area->SetTrackType(sequence_.get(), olive::kTypeVideo); + audio_area->SetTrackType(sequence_.get(), olive::kTypeAudio); + repaint_timeline(); + + emit SequenceChanged(sequence_); +} + +void Timeline::Retranslate() { + toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)"); + toolEditButton->setToolTip(tr("Edit Tool") + " (X)"); + toolRippleButton->setToolTip(tr("Ripple Tool") + " (B)"); + toolRazorButton->setToolTip(tr("Razor Tool") + " (C)"); + toolSlipButton->setToolTip(tr("Slip Tool") + " (Y)"); + toolSlideButton->setToolTip(tr("Slide Tool") + " (U)"); + toolHandButton->setToolTip(tr("Hand Tool") + " (H)"); + toolTransitionButton->setToolTip(tr("Transition Tool") + " (T)"); + snappingButton->setToolTip(tr("Snapping") + " (S)"); + zoomInButton->setToolTip(tr("Zoom In") + " (=)"); + zoomOutButton->setToolTip(tr("Zoom Out") + " (-)"); + recordButton->setToolTip(tr("Record audio")); + addButton->setToolTip(tr("Add title, solid, bars, etc.")); + + UpdateTitle(); +} + +void Timeline::toggle_show_all() { + if (sequence_ != nullptr) { + showing_all = !showing_all; + if (showing_all) { + old_zoom = zoom; + set_zoom_value(double(timeline_area->width() - 200) / double(sequence_->GetEndFrame())); + } else { + set_zoom_value(old_zoom); + } + } +} + +void Timeline::toggle_links() +{ + if (sequence_ != nullptr) { + sequence_->ToggleLinksOnSelected(); + } +} + +void Timeline::add_transition() { + ComboAction* ca = new ComboAction(); + bool adding = false; + + 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::undo_stack.push(ca); + } else { + delete ca; + } + + update_ui(true); +} + +void Timeline::nest() { + if (sequence_ != nullptr) { + // get selected clips + QVector selected_clips = sequence_->SelectedClips(); + + // nest them + if (!selected_clips.isEmpty()) { + + // get earliest point in selected clips + long earliest_point = LONG_MAX; + for (int i=0;itimeline_in(), earliest_point); + } + + ComboAction* ca = new ComboAction(); + + // create "nest" sequence with the same attributes as the current sequence + SequencePtr s = std::make_shared(); + + s->set_name(olive::project_model.GetNextSequenceName(tr("Nested Sequence"))); + s->set_width(sequence_->width()); + s->set_height(sequence_->height()); + s->set_frame_rate(sequence_->frame_rate()); + s->set_audio_frequency(sequence_->audio_frequency()); + s->set_audio_layout(sequence_->audio_layout()); + + QVector new_clips; + + // copy all selected clips to the nest + for (int i=0;iappend(new DeleteClipAction(c)); + + // copy to new + Track* track = s->TrackAt(c->type(), 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); + track->AddClip(copy); + + new_clips.append(copy); + } + + // relink clips in new nested sequences + olive::timeline::RelinkClips(selected_clips, new_clips); + + // add sequence to project + MediaPtr m = olive::project_model.CreateSequence(ca, s, false, nullptr); + + // add nested sequence to active sequence + QVector media_list; + media_list.append(m.get()); + olive::timeline::CreateGhostsFromMedia(sequence_.get(), earliest_point, media_list); + + // ensure ghosts won't overlap anything + 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 a track, and seek again. + g.track = g.track->Next(); + + // Restart entire loop again + j = -1; + break; + + } + } + } + } + + + sequence_->AddClipsFromGhosts(ca, ghosts); + + panel_graph_editor->set_row(nullptr); + panel_effect_controls->Clear(true); + sequence_->ClearSelections(); + + olive::undo_stack.push(ca); + + update_ui(true); + } + } +} + +void Timeline::update_sequence() { + bool null_sequence = (sequence_ == nullptr); + + for (int i=0;isetEnabled(!null_sequence); + } + snappingButton->setEnabled(!null_sequence); + zoomInButton->setEnabled(!null_sequence); + zoomOutButton->setEnabled(!null_sequence); + recordButton->setEnabled(!null_sequence); + addButton->setEnabled(!null_sequence); + headers->setEnabled(!null_sequence); + + UpdateTitle(); +} + +bool Timeline::focused() { + return (sequence_ != nullptr && (headers->hasFocus() || video_area->hasFocus() || audio_area->hasFocus())); +} + +void Timeline::repaint_timeline() { + if (!block_repaints) { + bool draw = true; + + if (sequence_ != nullptr + && !horizontalScrollBar->isSliderDown() + && !horizontalScrollBar->is_resizing() + && panel_sequence_viewer->playing + && !zoom_just_changed) { + // auto scroll + 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::config.autoscroll == olive::AUTOSCROLL_SMOOTH_SCROLL) { + if (center_scroll_to_playhead(horizontalScrollBar, zoom, sequence_->playhead)) { + draw = false; + } + } + } + + if (draw) { + headers->update(); + video_area->update(); + audio_area->update(); + + if (sequence_ != nullptr + && !zoom_just_changed) { + set_sb_max(); + } + } + + zoom_just_changed = false; + } +} + +void Timeline::select_all() { + if (sequence_ != nullptr) { + sequence_->SelectAll(); + repaint_timeline(); + } +} + +void Timeline::scroll_to_frame(long frame) { + scroll_to_frame_internal(horizontalScrollBar, frame, zoom, timeline_area->width()); +} + +void Timeline::resizeEvent(QResizeEvent *) { + // adjust maximum scrollbar + if (sequence_ != nullptr) set_sb_max(); + + + // resize tool button widget to its contents + QList tool_button_children = tool_button_widget->findChildren(); + + int horizontal_spacing = static_cast(tool_button_widget->layout())->horizontalSpacing(); + int vertical_spacing = static_cast(tool_button_widget->layout())->verticalSpacing(); + int total_area = tool_button_widget->height(); + + int button_count = tool_button_children.size(); + int button_height = tool_button_children.at(0)->sizeHint().height() + vertical_spacing; + + int cols = 0; + + int col_height; + + if (button_height < total_area) { + do { + cols++; + col_height = (qCeil(double(button_count)/double(cols))*button_height)-vertical_spacing; + } while (col_height > total_area); + } else { + cols = button_count; + } + + tool_button_widget->setFixedWidth((tool_button_children.at(0)->sizeHint().width())*cols + horizontal_spacing*(cols-1) + 1); +} + +void Timeline::toggle_enable_on_selected_clips() { + if (sequence_ != nullptr) { + + // get currently selected clips + QVector selected_clips = sequence_->SelectedClips(); + + if (!selected_clips.isEmpty()) { + // if clips are selected, create an undoable action + SetClipProperty* set_action = new SetClipProperty(kSetClipPropertyEnabled); + + // add each selected clip to the action + for (int i=0;iAddSetting(c, !c->enabled()); + } + + // push the action + olive::undo_stack.push(set_action); + update_ui(false); + } + } +} + +void Timeline::set_zoom_value(double v) { + // set zoom value + zoom = v; + + // update header zoom to match + headers->update_zoom(zoom); + + // set flag that zoom has just changed to prevent auto-scrolling since we change the scroll below + zoom_just_changed = true; + + // set scrollbar to center the playhead + if (sequence_ != nullptr) { + // update scrollbar maximum value for new zoom + set_sb_max(); + + if (!horizontalScrollBar->is_resizing()) { + center_scroll_to_playhead(horizontalScrollBar, zoom, sequence_->playhead); + } + } + + // repaint the timeline for the new zoom/location + repaint_timeline(); +} + +void Timeline::multiply_zoom(double m) { + showing_all = false; + set_zoom_value(zoom * m); +} + +void Timeline::zoom_in() { + multiply_zoom(2.0); +} + +void Timeline::zoom_out() { + multiply_zoom(0.5); +} + +void Timeline::ChangeTrackHeightUniformly(int diff) { + if (sequence_ != nullptr) { + sequence_->ChangeTrackHeightsRelatively(diff); + } + + // update the timeline + repaint_timeline(); +} + +void Timeline::IncreaseTrackHeight() { + ChangeTrackHeightUniformly(olive::timeline::kTrackHeightIncrement); +} + +void Timeline::DecreaseTrackHeight() { + ChangeTrackHeightUniformly(-olive::timeline::kTrackHeightIncrement); +} + +void Timeline::snapping_clicked(bool checked) { + olive::timeline::snapping = checked; +} + +/* +bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool relink) { + Clip* c = sequence_->clips.at(clip).get(); + if (c != nullptr) { + QVector pre_clips; + QVector post_clips; + + ClipPtr post = split_clip(ca, true, clip, frame); + + if (post == nullptr) { + return false; + } else { + post_clips.append(post); + + // if alt is not down, split clips links too + if (relink) { + pre_clips.append(clip); + + bool original_clip_is_selected = c->IsSelected(); + + // find linked clips of old clip + for (int i=0;ilinked.size();i++) { + int l = c->linked.at(i); + 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(sequence_.get(), post_clips)); + return true; + } + } + return false; +} +*/ + + + +void Timeline::copy(bool del) { + if (sequence_ != nullptr) { + sequence_->AddSelectionsToClipboard(del); + } +} + +void Timeline::ripple_delete() { + + 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::ripple_delete_empty_space() +{ + if (sequence_ != nullptr) { + ComboAction* ca = new ComboAction(); + sequence_->RippleDeleteEmptySpace(ca, cursor_track, cursor_frame); + olive::undo_stack.push(ca); + + repaint_timeline(); + } +} + +void Timeline::set_marker() { + // determine if any clips are selected, and if so add markers to clips rather than the sequence + + QVector selected_clips = sequence_->SelectedClips(); + + 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--; + } + } + + // Check if we removed them all + if (selected_clips.isEmpty()) { + return; + } + + // If not, let's create markers on them + Marker::SetOnClips(selected_clips); + + } +} + +void Timeline::delete_inout() { + if (sequence_ != nullptr) { + sequence_->DeleteInToOut(false); + } +} + +void Timeline::ripple_delete_inout() { + if (sequence_ != nullptr) { + sequence_->DeleteInToOut(true); + } +} + +void Timeline::ripple_to_in_point() { + if (sequence_ != nullptr) { + sequence_->EditToPoint(true, true); + } +} + +void Timeline::ripple_to_out_point() { + if (sequence_ != nullptr) { + sequence_->EditToPoint(false, true); + } +} + +void Timeline::edit_to_in_point() { + if (sequence_ != nullptr) { + sequence_->EditToPoint(true, false); + } +} + +void Timeline::edit_to_out_point() { + if (sequence_ != nullptr) { + sequence_->EditToPoint(false, false); + } +} + +void Timeline::deselect() { + 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 = qFloor(double(x) / zoom); + if (f < 0) { + return 0; + } + return f; +} + +int getScreenPointFromFrame(double zoom, long frame) { + return qRound(double(frame)*zoom); +} + +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 + QVector area_tracks = sequence_->GetTrackList(area->track_type()); + + for (int j=0;jview()->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; +} + +void Timeline::add_btn_click() { + Menu add_menu(this); + + QAction* titleMenuItem = new QAction(&add_menu); + titleMenuItem->setText(tr("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(olive::timeline::ADD_OBJ_SOLID); + add_menu.addAction(solidMenuItem); + + QAction* barsMenuItem = new QAction(&add_menu); + barsMenuItem->setText(tr("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(olive::timeline::ADD_OBJ_TONE); + add_menu.addAction(toneMenuItem); + + QAction* noiseMenuItem = new QAction(&add_menu); + noiseMenuItem->setText(tr("Noise...")); + noiseMenuItem->setData(olive::timeline::ADD_OBJ_NOISE); + add_menu.addAction(noiseMenuItem); + + connect(&add_menu, SIGNAL(triggered(QAction*)), this, SLOT(add_menu_item(QAction*))); + + add_menu.exec(QCursor::pos()); +} + +void Timeline::add_menu_item(QAction* action) { + creating = true; + creating_object = static_cast(action->data().toInt()); +} + +void Timeline::setScroll(int s) { + scroll = s; + headers->set_scroll(s); + repaint_timeline(); +} + +void Timeline::record_btn_click() { + if (olive::ActiveProjectFilename.isEmpty()) { + QMessageBox::critical(this, + tr("Unsaved Project"), + tr("You must save this project before you can record audio in it."), + QMessageBox::Ok); + } else { + creating = true; + 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); + } +} + +void Timeline::transition_tool_click() { + creating = false; + + Menu transition_menu(this); + + 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(); + + 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); + } + } + + connect(&transition_menu, SIGNAL(triggered(QAction*)), this, SLOT(transition_menu_select(QAction*))); + + toolTransitionButton->setChecked(false); + + transition_menu.exec(QCursor::pos()); +} + +void Timeline::transition_menu_select(QAction* a) { + transition_tool_meta = static_cast(a->data().toInt()); + timeline_area->setCursor(Qt::CrossCursor); + olive::timeline::current_tool = olive::timeline::TIMELINE_TOOL_TRANSITION; + toolTransitionButton->setChecked(true); +} + +void Timeline::resize_move(double z) { + set_zoom_value(zoom * z); +} + +void Timeline::set_sb_max() { + headers->set_scrollbar_max(horizontalScrollBar, sequence_->GetEndFrame(), editAreas->width() - getScreenPointFromFrame(zoom, 200)); +} + +void Timeline::UpdateTitle() { + setWindowTitle( + tr("Timeline: %1").arg( + (sequence_ == nullptr) ? tr("(none)") : sequence_->name() + ) + ); +} + +void Timeline::setup_ui() { + QWidget* dockWidgetContents = new QWidget(); + + QHBoxLayout* horizontalLayout = new QHBoxLayout(dockWidgetContents); + horizontalLayout->setSpacing(0); + horizontalLayout->setMargin(0); + + setWidget(dockWidgetContents); + + tool_button_widget = new QWidget(); + tool_button_widget->setObjectName("timeline_toolbar"); + tool_button_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + FlowLayout* tool_buttons_layout = new FlowLayout(tool_button_widget); + tool_buttons_layout->setSpacing(4); + tool_buttons_layout->setMargin(0); + + toolArrowButton = new QPushButton(); + toolArrowButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/arrow.svg"))); + toolArrowButton->setCheckable(true); + 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", 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", 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", 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", 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", olive::timeline::TIMELINE_TOOL_SLIDE); + connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); + tool_buttons_layout->addWidget(toolSlideButton); + + toolHandButton = new QPushButton(); + toolHandButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/hand.svg"))); + toolHandButton->setCheckable(true); + + 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(); + toolTransitionButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/transition-tool.svg"))); + toolTransitionButton->setCheckable(true); + connect(toolTransitionButton, SIGNAL(clicked(bool)), this, SLOT(transition_tool_click())); + tool_buttons_layout->addWidget(toolTransitionButton); + + snappingButton = new QPushButton(); + snappingButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/magnet.svg"))); + snappingButton->setCheckable(true); + snappingButton->setChecked(true); + connect(snappingButton, SIGNAL(toggled(bool)), this, SLOT(snapping_clicked(bool))); + tool_buttons_layout->addWidget(snappingButton); + + zoomInButton = new QPushButton(); + zoomInButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/zoomin.svg"))); + connect(zoomInButton, SIGNAL(clicked(bool)), this, SLOT(zoom_in())); + tool_buttons_layout->addWidget(zoomInButton); + + zoomOutButton = new QPushButton(); + zoomOutButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/zoomout.svg"))); + connect(zoomOutButton, SIGNAL(clicked(bool)), this, SLOT(zoom_out())); + tool_buttons_layout->addWidget(zoomOutButton); + + recordButton = new QPushButton(); + recordButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/record.svg"))); + connect(recordButton, SIGNAL(clicked(bool)), this, SLOT(record_btn_click())); + tool_buttons_layout->addWidget(recordButton); + + addButton = new QPushButton(); + addButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/add-button.svg"))); + connect(addButton, SIGNAL(clicked()), this, SLOT(add_btn_click())); + tool_buttons_layout->addWidget(addButton); + + horizontalLayout->addWidget(tool_button_widget); + + timeline_area = new QWidget(); + QSizePolicy timeline_area_policy(QSizePolicy::Minimum, QSizePolicy::Minimum); + timeline_area_policy.setHorizontalStretch(1); + timeline_area_policy.setVerticalStretch(0); + timeline_area_policy.setHeightForWidth(timeline_area->sizePolicy().hasHeightForWidth()); + timeline_area->setSizePolicy(timeline_area_policy); + + QVBoxLayout* timeline_area_layout = new QVBoxLayout(timeline_area); + 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_header_layout->addWidget(headers); + timeline_area_layout->addLayout(timeline_header_layout); + + editAreas = new QWidget(); + QHBoxLayout* editAreaLayout = new QHBoxLayout(editAreas); + editAreaLayout->setSpacing(0); + editAreaLayout->setContentsMargins(0, 0, 0, 0); + + QSplitter* splitter = new QSplitter(); + splitter->setChildrenCollapsible(false); + splitter->setOrientation(Qt::Vertical); + + video_area = new TimelineArea(this); + areas.append(video_area); + splitter->addWidget(video_area); + + audio_area = new TimelineArea(this); + areas.append(audio_area); + splitter->addWidget(audio_area); + + editAreaLayout->addWidget(splitter); + + timeline_area_layout->addWidget(editAreas); + + horizontalScrollBar = new ResizableScrollBar(); + horizontalScrollBar->setMaximum(0); + horizontalScrollBar->setSingleStep(20); + horizontalScrollBar->setOrientation(Qt::Horizontal); + + timeline_area_layout->addWidget(horizontalScrollBar); + + horizontalLayout->addWidget(timeline_area); + + audio_monitor = new AudioMonitor(); + audio_monitor->setMinimumSize(QSize(50, 0)); + + horizontalLayout->addWidget(audio_monitor); + + setWidget(dockWidgetContents); +} + +void Timeline::set_tool() { + QPushButton* button = static_cast(sender()); + olive::timeline::current_tool = static_cast(button->property("tool").toInt()); + creating = false; + switch (olive::timeline::current_tool) { + case olive::timeline::TIMELINE_TOOL_EDIT: + timeline_area->setCursor(Qt::IBeamCursor); + break; + case olive::timeline::TIMELINE_TOOL_RAZOR: + timeline_area->setCursor(olive::cursor::Razor); + break; + case olive::timeline::TIMELINE_TOOL_HAND: + timeline_area->setCursor(Qt::OpenHandCursor); + break; + default: + timeline_area->setCursor(Qt::ArrowCursor); + } +} + +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 1830c6bd4..48cf9481f 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -1,220 +1,220 @@ -/*** - - 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 TIMELINE_H -#define TIMELINE_H - -#include -#include -#include - -#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" - -class Timeline : public Panel -{ - Q_OBJECT -public: - explicit Timeline(QWidget *parent = nullptr); - - 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); - void update_sequence(); - - int getTimelineScreenPointFromFrame(long frame); - long getTimelineFrameFromScreenPoint(int x); - int getDisplayScreenPointFromFrame(long frame); - long getDisplayFrameFromScreenPoint(int x); - - QVector GetTracksInRectangle(int global_top, int global_bottom); - - void set_marker(); - - // shared information - long cursor_frame; - Track* cursor_track; - double zoom; - bool zoom_just_changed; - long drag_frame_start; - Track* drag_track_start; - void update_effect_controls(); - bool showing_all; - double old_zoom; - - // selecting functions - bool selecting; - QVector selection_cache; - void select_all(); - bool rect_select_init; - bool rect_select_proc; - QRect rect_select_rect; - - // moving - bool moving_init; - bool moving_proc; - QVector ghosts; - bool move_insert; - - // trimming - Clip* trim_target; - olive::timeline::TrimType trim_type; - int transition_select; - - // splitting - bool splitting; - QVector split_tracks; - - // importing - bool importing; - bool importing_files; - - // creating variables - bool creating; - olive::timeline::CreateObjects creating_object; - - // transition variables - bool transition_tool_init; - bool transition_tool_proc; - Clip* transition_tool_open_clip; - Clip* transition_tool_close_clip; - NodeType transition_tool_meta; - - // hand tool variables - bool hand_moving; - int drag_x_start; - int drag_y_start; - - bool block_repaints; - - TimelineHeader* headers; - AudioMonitor* audio_monitor; - ResizableScrollBar* horizontalScrollBar; - - QVector tool_buttons; - QButtonGroup* tool_button_group; - QPushButton* toolArrowButton; - QPushButton* toolEditButton; - QPushButton* toolRippleButton; - QPushButton* toolRazorButton; - QPushButton* toolSlipButton; - QPushButton* toolSlideButton; - QPushButton* toolHandButton; - QPushButton* toolTransitionButton; - QPushButton* snappingButton; - - void scroll_to_frame(long frame); - - bool can_ripple_empty_space(long frame, int track); - - virtual void Retranslate() override; -protected: - virtual void resizeEvent(QResizeEvent *event) override; -public slots: - void repaint_timeline(); - void toggle_show_all(); - void toggle_links(); - void deselect(); - void split_at_playhead(); - void ripple_delete(); - void ripple_delete_empty_space(); - void toggle_enable_on_selected_clips(); - - void delete_inout(); - void ripple_delete_inout(); - - void ripple_to_in_point(); - void ripple_to_out_point(); - void edit_to_in_point(); - void edit_to_out_point(); - - void IncreaseTrackHeight(); - void DecreaseTrackHeight(); - - void add_transition(); - - void nest(); - - void zoom_in(); - void zoom_out(); - -signals: - void SequenceChanged(SequencePtr s); - -private slots: - void snapping_clicked(bool checked); - void add_btn_click(); - void add_menu_item(QAction*); - void setScroll(int); - void record_btn_click(); - void transition_tool_click(); - 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); - void set_tool(int tool); - int scroll; - void set_sb_max(); - void UpdateTitle(); - - void setup_ui(); - - // ripple delete empty space variables - long rc_ripple_min; - long rc_ripple_max; - - QWidget* timeline_area; - TimelineArea* video_area; - TimelineArea* audio_area; - QVector areas; - QWidget* editAreas; - QPushButton* zoomInButton; - QPushButton* zoomOutButton; - QPushButton* recordButton; - QPushButton* addButton; - QWidget* tool_button_widget; -}; - -#endif // TIMELINE_H +/*** + + 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 TIMELINE_H +#define TIMELINE_H + +#include +#include +#include + +#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" + +class Timeline : public Panel +{ + Q_OBJECT +public: + explicit Timeline(QWidget *parent = nullptr); + + 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); + void update_sequence(); + + int getTimelineScreenPointFromFrame(long frame); + long getTimelineFrameFromScreenPoint(int x); + int getDisplayScreenPointFromFrame(long frame); + long getDisplayFrameFromScreenPoint(int x); + + QVector GetTracksInRectangle(int global_top, int global_bottom); + + void set_marker(); + + // shared information + long cursor_frame; + Track* cursor_track; + double zoom; + bool zoom_just_changed; + long drag_frame_start; + Track* drag_track_start; + void update_effect_controls(); + bool showing_all; + double old_zoom; + + // selecting functions + bool selecting; + QVector selection_cache; + void select_all(); + bool rect_select_init; + bool rect_select_proc; + QRect rect_select_rect; + + // moving + bool moving_init; + bool moving_proc; + QVector ghosts; + bool move_insert; + + // trimming + Clip* trim_target; + olive::timeline::TrimType trim_type; + int transition_select; + + // splitting + bool splitting; + QVector split_tracks; + + // importing + bool importing; + bool importing_files; + + // creating variables + bool creating; + olive::timeline::CreateObjects creating_object; + + // transition variables + bool transition_tool_init; + bool transition_tool_proc; + Clip* transition_tool_open_clip; + Clip* transition_tool_close_clip; + NodeType transition_tool_meta; + + // hand tool variables + bool hand_moving; + int drag_x_start; + int drag_y_start; + + bool block_repaints; + + TimelineHeader* headers; + AudioMonitor* audio_monitor; + ResizableScrollBar* horizontalScrollBar; + + QVector tool_buttons; + QButtonGroup* tool_button_group; + QPushButton* toolArrowButton; + QPushButton* toolEditButton; + QPushButton* toolRippleButton; + QPushButton* toolRazorButton; + QPushButton* toolSlipButton; + QPushButton* toolSlideButton; + QPushButton* toolHandButton; + QPushButton* toolTransitionButton; + QPushButton* snappingButton; + + void scroll_to_frame(long frame); + + bool can_ripple_empty_space(long frame, int track); + + virtual void Retranslate() override; +protected: + virtual void resizeEvent(QResizeEvent *event) override; +public slots: + void repaint_timeline(); + void toggle_show_all(); + void toggle_links(); + void deselect(); + void split_at_playhead(); + void ripple_delete(); + void ripple_delete_empty_space(); + void toggle_enable_on_selected_clips(); + + void delete_inout(); + void ripple_delete_inout(); + + void ripple_to_in_point(); + void ripple_to_out_point(); + void edit_to_in_point(); + void edit_to_out_point(); + + void IncreaseTrackHeight(); + void DecreaseTrackHeight(); + + void add_transition(); + + void nest(); + + void zoom_in(); + void zoom_out(); + +signals: + void SequenceChanged(SequencePtr s); + +private slots: + void snapping_clicked(bool checked); + void add_btn_click(); + void add_menu_item(QAction*); + void setScroll(int); + void record_btn_click(); + void transition_tool_click(); + 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); + void set_tool(int tool); + int scroll; + void set_sb_max(); + void UpdateTitle(); + + void setup_ui(); + + // ripple delete empty space variables + long rc_ripple_min; + long rc_ripple_max; + + QWidget* timeline_area; + TimelineArea* video_area; + TimelineArea* audio_area; + QVector areas; + QWidget* editAreas; + QPushButton* zoomInButton; + QPushButton* zoomOutButton; + QPushButton* recordButton; + QPushButton* addButton; + QWidget* tool_button_widget; +}; + +#endif // TIMELINE_H diff --git a/project/footage.cpp b/project/footage.cpp index a08d052fc..25ed0f969 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -1,179 +1,179 @@ -/*** - - 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 "footage.h" - -#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_associated(true), - proxy(false), - start_number(0) -{ - ready_lock.lock(); -} - -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(); - } - video_tracks.clear(); - audio_tracks.clear(); - ready = false; -} - -long Footage::get_length_in_frames(double frame_rate) { - if (length >= 0) { - return qFloor((double(length) / double(AV_TIME_BASE)) * frame_rate / speed); - } - return 0; -} - -FootageStream* Footage::get_stream_from_file_index(bool video, int index) { - if (video) { - for (int i=0;i. + +***/ + +#include "footage.h" + +#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_associated(true), + proxy(false), + start_number(0) +{ + ready_lock.lock(); +} + +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(); + } + video_tracks.clear(); + audio_tracks.clear(); + ready = false; +} + +long Footage::get_length_in_frames(double frame_rate) { + if (length >= 0) { + return qFloor((double(length) / double(AV_TIME_BASE)) * frame_rate / speed); + } + return 0; +} + +FootageStream* Footage::get_stream_from_file_index(bool video, int index) { + if (video) { + for (int i=0;i. - -***/ - -#ifndef FOOTAGE_H -#define FOOTAGE_H - -extern "C" { - #include -} - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "timeline/marker.h" - -enum VideoInterlacingMode { - VIDEO_PROGRESSIVE, - VIDEO_TOP_FIELD_FIRST, - VIDEO_BOTTOM_FIELD_FIRST -}; - -class Sequence; -class Clip; -class PreviewGenerator; -class Footage; - -struct FootageStream { - // Format stream index - Footage* footage; - int file_index; - - // Video parameters - int video_width; - int video_height; - double video_frame_rate; - int video_interlacing; - int video_auto_interlacing; - bool infinite_length; - - // Audio parameters - int audio_channels; - int audio_layout; - int audio_frequency; - - // Enabled - bool enabled; - - // preview thumbnail/waveform - bool preview_done; - QImage video_preview; - QVector audio_preview; -}; - -class Footage { -public: - Footage(); - ~Footage(); - - void Save(QXmlStreamWriter& stream); - - // footage metadata - QString url; - QString name; - int64_t length; - QVector video_tracks; - QVector audio_tracks; - int save_id; - bool ready; - bool invalid; - double speed; - bool alpha_is_associated; - int start_number; - - // color management - QString Colorspace(); - void SetColorspace(const QString& cs); - - // proxy config - bool proxy; - QString proxy_path; - - // thumbnail/waveform generation - PreviewGenerator* preview_gen; - QMutex ready_lock; - - // in/out points - bool using_inout; - long in; - long out; - - // markers - QVector markers; - - // functions - 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; - -#endif // FOOTAGE_H +/*** + + 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 FOOTAGE_H +#define FOOTAGE_H + +extern "C" { + #include +} + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "timeline/marker.h" + +enum VideoInterlacingMode { + VIDEO_PROGRESSIVE, + VIDEO_TOP_FIELD_FIRST, + VIDEO_BOTTOM_FIELD_FIRST +}; + +class Sequence; +class Clip; +class PreviewGenerator; +class Footage; + +struct FootageStream { + // Format stream index + Footage* footage; + int file_index; + + // Video parameters + int video_width; + int video_height; + double video_frame_rate; + int video_interlacing; + int video_auto_interlacing; + bool infinite_length; + + // Audio parameters + int audio_channels; + int audio_layout; + int audio_frequency; + + // Enabled + bool enabled; + + // preview thumbnail/waveform + bool preview_done; + QImage video_preview; + QVector audio_preview; +}; + +class Footage { +public: + Footage(); + ~Footage(); + + void Save(QXmlStreamWriter& stream); + + // footage metadata + QString url; + QString name; + int64_t length; + QVector video_tracks; + QVector audio_tracks; + int save_id; + bool ready; + bool invalid; + double speed; + bool alpha_is_associated; + int start_number; + + // color management + QString Colorspace(); + void SetColorspace(const QString& cs); + + // proxy config + bool proxy; + QString proxy_path; + + // thumbnail/waveform generation + PreviewGenerator* preview_gen; + QMutex ready_lock; + + // in/out points + bool using_inout; + long in; + long out; + + // markers + QVector markers; + + // functions + 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; + +#endif // FOOTAGE_H diff --git a/project/previewgenerator.cpp b/project/previewgenerator.cpp index 95704776e..937841506 100644 --- a/project/previewgenerator.cpp +++ b/project/previewgenerator.cpp @@ -1,628 +1,628 @@ -/*** - - 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 "previewgenerator.h" - -#include "ui/mediaiconservice.h" -#include "project/media.h" -#include "project/footage.h" -#include "panels/viewer.h" -#include "panels/project.h" -#include "global/config.h" -#include "global/path.h" -#include "global/debug.h" - -#include -#include -#include -#include -#include -#include -#include - -QSemaphore sem(5); // only 5 preview generators can run at one time - -PreviewGenerator::PreviewGenerator(Media* i) : - QThread(nullptr) -{ - fmt_ctx_ = (nullptr); - media_ = (i); - retrieve_duration_ = (false); - contains_still_image_ = (false); - cancelled_ = (false); - footage_ = media_->to_footage(); - - footage_->preview_gen = this; - - data_dir_ = QDir(get_data_dir().filePath("previews")); - if (!data_dir_.exists()) { - data_dir_.mkpath("."); - } - - connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); - - // set up throbber animation - olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_LOADING); - - start(QThread::LowPriority); -} - -void PreviewGenerator::parse_media() { - // detect video/audio streams in file - for (int i=0;inb_streams);i++) { - // Find the decoder for the video stream - if (avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id) == nullptr) { - qCritical() << "Unsupported codec in stream" << i << "of file" << footage_->name; - } else { - FootageStream ms; - ms.preview_done = false; - ms.file_index = i; - ms.enabled = true; - ms.infinite_length = false; - - bool append = false; - - if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO - && fmt_ctx_->streams[i]->codecpar->width > 0 - && fmt_ctx_->streams[i]->codecpar->height > 0) { - - // heuristic to determine if video is a still image (if it is, we treat it differently in the playback/render process) - if (fmt_ctx_->streams[i]->avg_frame_rate.den == 0 - && fmt_ctx_->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this - if (footage_->url.contains('%')) { - // must be an image sequence - ms.video_frame_rate = 25; - } else { - ms.infinite_length = true; - contains_still_image_ = true; - ms.video_frame_rate = 0; - } - - } else { - // using ffmpeg's built-in heuristic - ms.video_frame_rate = av_q2d(av_guess_frame_rate(fmt_ctx_, fmt_ctx_->streams[i], nullptr)); - } - - ms.video_width = fmt_ctx_->streams[i]->codecpar->width; - ms.video_height = fmt_ctx_->streams[i]->codecpar->height; - - // default value, we get the true value later in generate_waveform() - ms.video_auto_interlacing = VIDEO_PROGRESSIVE; - ms.video_interlacing = VIDEO_PROGRESSIVE; - - append = true; - } else if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - ms.audio_channels = fmt_ctx_->streams[i]->codecpar->channels; - ms.audio_layout = int(fmt_ctx_->streams[i]->codecpar->channel_layout); - ms.audio_frequency = fmt_ctx_->streams[i]->codecpar->sample_rate; - - append = true; - } - - if (append) { - QVector& stream_list = (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ? - footage_->audio_tracks : footage_->video_tracks; - - for (int j=0;jlength = fmt_ctx_->duration; - - if (fmt_ctx_->duration == INT64_MIN) { - retrieve_duration_ = true; - } else { - finalize_media(); - } -} - -bool PreviewGenerator::retrieve_preview(const QString& hash) { - // returns true if generate_waveform must be run, false if we got all previews from cached files - if (retrieve_duration_) { - //dout << "[NOTE] " << media->name << "needs to retrieve duration"; - return true; - } - - bool found = true; - for (int i=0;ivideo_tracks.size();i++) { - FootageStream& ms = footage_->video_tracks[i]; - QString thumb_path = get_thumbnail_path(hash, ms); - QFile f(thumb_path); - if (f.exists() && ms.video_preview.load(thumb_path)) { - ms.preview_done = true; - } else { - found = false; - break; - } - } - for (int i=0;iaudio_tracks.size();i++) { - FootageStream& ms = footage_->audio_tracks[i]; - QString waveform_path = get_waveform_path(hash, ms); - QFile f(waveform_path); - if (f.exists()) { - //dout << "loaded wave" << ms->file_index << "from" << waveform_path; - f.open(QFile::ReadOnly); - QByteArray data = f.readAll(); - ms.audio_preview.resize(data.size()); - memcpy(ms.audio_preview.data(), data, data.size()); - ms.preview_done = true; - f.close(); - } else { - found = false; - break; - } - } - if (!found) { - for (int i=0;ivideo_tracks.size();i++) { - FootageStream& ms = footage_->video_tracks[i]; - ms.preview_done = false; - } - for (int i=0;iaudio_tracks.size();i++) { - FootageStream& ms = footage_->audio_tracks[i]; - ms.audio_preview.clear(); - ms.preview_done = false; - } - } - return !found; -} - -void PreviewGenerator::finalize_media() { - if (!cancelled_) { - bool footage_is_ready = true; - - if (footage_->video_tracks.isEmpty() && footage_->audio_tracks.isEmpty()) { - // ERROR - footage_is_ready = false; - invalidate_media(tr("Failed to find any valid video/audio streams")); - } else if (!footage_->video_tracks.isEmpty() && !contains_still_image_) { - // VIDEO - olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_VIDEO); - } else if (!footage_->audio_tracks.isEmpty()) { - // AUDIO - olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_AUDIO); - } else { - // IMAGE - olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_IMAGE); - } - - if (footage_is_ready) { - footage_->ready_lock.unlock(); - footage_->ready = true; - media_->update_tooltip(); - } - - QVector all_sequences = olive::project_model.GetAllSequences(); - for (int i=0;ito_sequence()->RefreshClipsUsingMedia(media_); - } - } -} - -void PreviewGenerator::invalidate_media(const QString &error_msg) -{ - media_->update_tooltip(error_msg); - olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_ERROR); - footage_->invalid = true; - footage_->ready_lock.unlock(); -} - -void PreviewGenerator::generate_waveform() { - SwsContext* sws_ctx; - SwrContext* swr_ctx; - AVFrame* temp_frame = av_frame_alloc(); - - // stores codec contexts for format's streams - AVCodecContext** codec_ctx = new AVCodecContext* [fmt_ctx_->nb_streams]; - - // stores media lengths while scanning in case the format has no duration metadata - int64_t* media_lengths = new int64_t[fmt_ctx_->nb_streams]{0}; - - // stores samples while scanning before they get sent to preview file - 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 - bool create_previews = false; - - for (unsigned int i=0;inb_streams;i++) { - - // default to nullptr values for easier memory management later - codec_ctx[i] = nullptr; - waveform_cache_data[i] = nullptr; - - // 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::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) { - - // alloc the context and load the params into it - codec_ctx[i] = avcodec_alloc_context3(codec); - avcodec_parameters_to_context(codec_ctx[i], fmt_ctx_->streams[i]->codecpar); - - // open the decoder - avcodec_open2(codec_ctx[i], codec, nullptr); - - // audio specific functions - if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - - // allocate sample cache for this stream - 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 qint8[2]; - } - - // if codec context has no defined channel layout, guess it from the channel count - if (codec_ctx[i]->channel_layout == 0) { - codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx_->streams[i]->codecpar->channels); - } - - } - - // enable next step of process - create_previews = true; - } - } - } - - if (create_previews) { - // TODO may be unnecessary - doesn't av_read_frame allocate a packet itself? - AVPacket* packet = av_packet_alloc(); - - bool done = true; - - bool end_of_file = false; - - // get the ball rolling - do { - av_read_frame(fmt_ctx_, packet); - } while (codec_ctx[packet->stream_index] == nullptr); - avcodec_send_packet(codec_ctx[packet->stream_index], packet); - - while (!end_of_file) { - while (codec_ctx[packet->stream_index] == nullptr || avcodec_receive_frame(codec_ctx[packet->stream_index], temp_frame) == AVERROR(EAGAIN)) { - av_packet_unref(packet); - int read_ret = av_read_frame(fmt_ctx_, packet); - - if (read_ret < 0) { - end_of_file = true; - if (read_ret != AVERROR_EOF) qCritical() << "Failed to read packet for preview generation" << read_ret; - break; - } - if (codec_ctx[packet->stream_index] != nullptr) { - int send_ret = avcodec_send_packet(codec_ctx[packet->stream_index], packet); - if (send_ret < 0 && send_ret != AVERROR(EAGAIN)) { - qCritical() << "Failed to send packet for preview generation - aborting" << send_ret; - end_of_file = true; - break; - } - } - } - if (!end_of_file) { - FootageStream* s = footage_->get_stream_from_file_index(fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index); - if (s != nullptr) { - if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - if (!s->preview_done) { - int dstH = olive::config.thumbnail_resolution; - int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height))); - - sws_ctx = sws_getContext( - temp_frame->width, - temp_frame->height, - static_cast(temp_frame->format), - dstW, - dstH, - static_cast(AV_PIX_FMT_RGBA), - SWS_FAST_BILINEAR, - nullptr, - nullptr, - nullptr - ); - - int linesize[AV_NUM_DATA_POINTERS]; - linesize[0] = dstW*4; - - s->video_preview = QImage(dstW, dstH, QImage::Format_RGBA8888); - uint8_t* data = s->video_preview.bits(); - - sws_scale(sws_ctx, - temp_frame->data, - temp_frame->linesize, - 0, - temp_frame->height, - &data, - linesize); - - // is video interlaced? - s->video_auto_interlacing = (temp_frame->interlaced_frame) ? ((temp_frame->top_field_first) ? VIDEO_TOP_FIELD_FIRST : VIDEO_BOTTOM_FIELD_FIRST) : VIDEO_PROGRESSIVE; - s->video_interlacing = s->video_auto_interlacing; - - s->preview_done = true; - - sws_freeContext(sws_ctx); - - if (!retrieve_duration_) { - avcodec_close(codec_ctx[packet->stream_index]); - codec_ctx[packet->stream_index] = nullptr; - } - } - media_lengths[packet->stream_index]++; - } else if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - 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_U8P; - - swr_ctx = swr_alloc_set_opts( - nullptr, - temp_frame->channel_layout, - static_cast(swr_frame->format), - temp_frame->sample_rate, - temp_frame->channel_layout, - static_cast(temp_frame->format), - temp_frame->sample_rate, - 0, - nullptr - ); - - swr_init(swr_ctx); - - swr_convert_frame(swr_ctx, swr_frame, temp_frame); - - // `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::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)); - - // total amount of data in this frame - int nb_bytes = swr_frame->nb_samples * sample_size; - - // loop through entire frame - for (int i=0;ichannels;j++) { - 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); - s->audio_preview.append(max); - } - - waveform_cache_count = 0; - } - - // standard processing for each channel of information - for (int j=0;jchannels;j++) { - 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) { - min = 0; - max = 0; - } - - // 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); - } - - waveform_cache_count++; - - if (cancelled_) { - break; - } - } - - swr_free(&swr_ctx); - av_frame_free(&swr_frame); - - if (cancelled_) { - end_of_file = true; - break; - } - } - } - - // check if we've got all our previews - if (retrieve_duration_) { - done = false; - } else if (footage_->audio_tracks.size() == 0) { - done = true; - for (int i=0;ivideo_tracks.size();i++) { - if (!footage_->video_tracks.at(i).preview_done) { - done = false; - break; - } - } - if (done) { - end_of_file = true; - break; - } - } - av_packet_unref(packet); - } - } - - av_frame_free(&temp_frame); - av_packet_free(&packet); - - for (unsigned int i=0;inb_streams;i++) { - if (waveform_cache_data[i] != nullptr) { - for (int j=0;jchannels;j++) { - delete [] waveform_cache_data[i][j]; - } - delete [] waveform_cache_data[i]; - } - - if (codec_ctx[i] != nullptr) { - avcodec_close(codec_ctx[i]); - avcodec_free_context(&codec_ctx[i]); - } - } - - // by this point, we'll have made all audio waveform previews - for (int i=0;iaudio_tracks.size();i++) { - footage_->audio_tracks[i].preview_done = true; - } - } - - if (retrieve_duration_) { - footage_->length = 0; - unsigned int maximum_stream = 0; - for (unsigned int i=0;inb_streams;i++) { - if (media_lengths[i] > media_lengths[maximum_stream]) { - maximum_stream = i; - } - } - - // FIXME: length is currently retrieved as a frame count rather than a timestamp - footage_->length = qRound(double(media_lengths[maximum_stream]) / av_q2d(fmt_ctx_->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE); - - finalize_media(); - } - - delete [] waveform_cache_data; - delete [] media_lengths; - delete [] codec_ctx; -} - -QString PreviewGenerator::get_thumbnail_path(const QString& hash, const FootageStream& ms) { - return data_dir_.filePath(QString("%1t%2").arg(hash, QString::number(ms.file_index))); -} - -QString PreviewGenerator::get_waveform_path(const QString& hash, const FootageStream& ms) { - return data_dir_.filePath(QString("%1w%2").arg(hash, QString::number(ms.file_index))); -} - -void PreviewGenerator::run() { - Q_ASSERT(footage_ != nullptr); - Q_ASSERT(media_ != nullptr); - - const QString url = footage_->url; - QByteArray ba = url.toUtf8(); - char* filename = new char[ba.size()+1]; - strcpy(filename, ba.data()); - - QString errorStr; - bool error = false; - - AVDictionary* format_opts = nullptr; - - // for image sequences that don't start at 0, set the index where it does start - if (footage_->start_number > 0) { - av_dict_set(&format_opts, "start_number", QString::number(footage_->start_number).toUtf8(), 0); - } - - int errCode = avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts); - if(errCode != 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - errorStr = tr("Could not open file - %1").arg(err); - error = true; - } else { - errCode = avformat_find_stream_info(fmt_ctx_, nullptr); - if (errCode < 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - errorStr = tr("Could not find stream information - %1").arg(err); - error = true; - } else { - av_dump_format(fmt_ctx_, 0, filename, 0); - parse_media(); - - // see if we already have data for this - QString hash = get_file_hash(footage_->url); - - if (retrieve_preview(hash)) { - sem.acquire(); - - if (!cancelled_) { - generate_waveform(); - - if (!cancelled_) { - // save preview to file - for (int i=0;ivideo_tracks.size();i++) { - FootageStream& ms = footage_->video_tracks[i]; - ms.video_preview.save(get_thumbnail_path(hash, ms), "PNG"); - //dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); - } - for (int i=0;iaudio_tracks.size();i++) { - FootageStream& ms = footage_->audio_tracks[i]; - QFile f(get_waveform_path(hash, ms)); - f.open(QFile::WriteOnly); - 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); - } - } - } - - sem.release(); - } - } - avformat_close_input(&fmt_ctx_); - } - - if (!cancelled_) { - if (error) { - invalidate_media(errorStr); - } - } - - delete [] filename; - footage_->preview_gen = nullptr; -} - -void PreviewGenerator::cancel() { - cancelled_ = true; - wait(); -} - -void PreviewGenerator::AnalyzeMedia(Media *m) -{ - // PreviewGenerator's constructor starts the thread, sets a reference of itself as the media's generator, - // and connects its thread completion to its own deletion, therefore handling its own memory. Nothing else needs to - // be done. - new PreviewGenerator(m); -} +/*** + + 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 "previewgenerator.h" + +#include "ui/mediaiconservice.h" +#include "project/media.h" +#include "project/footage.h" +#include "panels/viewer.h" +#include "panels/project.h" +#include "global/config.h" +#include "global/path.h" +#include "global/debug.h" + +#include +#include +#include +#include +#include +#include +#include + +QSemaphore sem(5); // only 5 preview generators can run at one time + +PreviewGenerator::PreviewGenerator(Media* i) : + QThread(nullptr) +{ + fmt_ctx_ = (nullptr); + media_ = (i); + retrieve_duration_ = (false); + contains_still_image_ = (false); + cancelled_ = (false); + footage_ = media_->to_footage(); + + footage_->preview_gen = this; + + data_dir_ = QDir(get_data_dir().filePath("previews")); + if (!data_dir_.exists()) { + data_dir_.mkpath("."); + } + + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); + + // set up throbber animation + olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_LOADING); + + start(QThread::LowPriority); +} + +void PreviewGenerator::parse_media() { + // detect video/audio streams in file + for (int i=0;inb_streams);i++) { + // Find the decoder for the video stream + if (avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id) == nullptr) { + qCritical() << "Unsupported codec in stream" << i << "of file" << footage_->name; + } else { + FootageStream ms; + ms.preview_done = false; + ms.file_index = i; + ms.enabled = true; + ms.infinite_length = false; + + bool append = false; + + if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO + && fmt_ctx_->streams[i]->codecpar->width > 0 + && fmt_ctx_->streams[i]->codecpar->height > 0) { + + // heuristic to determine if video is a still image (if it is, we treat it differently in the playback/render process) + if (fmt_ctx_->streams[i]->avg_frame_rate.den == 0 + && fmt_ctx_->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this + if (footage_->url.contains('%')) { + // must be an image sequence + ms.video_frame_rate = 25; + } else { + ms.infinite_length = true; + contains_still_image_ = true; + ms.video_frame_rate = 0; + } + + } else { + // using ffmpeg's built-in heuristic + ms.video_frame_rate = av_q2d(av_guess_frame_rate(fmt_ctx_, fmt_ctx_->streams[i], nullptr)); + } + + ms.video_width = fmt_ctx_->streams[i]->codecpar->width; + ms.video_height = fmt_ctx_->streams[i]->codecpar->height; + + // default value, we get the true value later in generate_waveform() + ms.video_auto_interlacing = VIDEO_PROGRESSIVE; + ms.video_interlacing = VIDEO_PROGRESSIVE; + + append = true; + } else if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + ms.audio_channels = fmt_ctx_->streams[i]->codecpar->channels; + ms.audio_layout = int(fmt_ctx_->streams[i]->codecpar->channel_layout); + ms.audio_frequency = fmt_ctx_->streams[i]->codecpar->sample_rate; + + append = true; + } + + if (append) { + QVector& stream_list = (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ? + footage_->audio_tracks : footage_->video_tracks; + + for (int j=0;jlength = fmt_ctx_->duration; + + if (fmt_ctx_->duration == INT64_MIN) { + retrieve_duration_ = true; + } else { + finalize_media(); + } +} + +bool PreviewGenerator::retrieve_preview(const QString& hash) { + // returns true if generate_waveform must be run, false if we got all previews from cached files + if (retrieve_duration_) { + //dout << "[NOTE] " << media->name << "needs to retrieve duration"; + return true; + } + + bool found = true; + for (int i=0;ivideo_tracks.size();i++) { + FootageStream& ms = footage_->video_tracks[i]; + QString thumb_path = get_thumbnail_path(hash, ms); + QFile f(thumb_path); + if (f.exists() && ms.video_preview.load(thumb_path)) { + ms.preview_done = true; + } else { + found = false; + break; + } + } + for (int i=0;iaudio_tracks.size();i++) { + FootageStream& ms = footage_->audio_tracks[i]; + QString waveform_path = get_waveform_path(hash, ms); + QFile f(waveform_path); + if (f.exists()) { + //dout << "loaded wave" << ms->file_index << "from" << waveform_path; + f.open(QFile::ReadOnly); + QByteArray data = f.readAll(); + ms.audio_preview.resize(data.size()); + memcpy(ms.audio_preview.data(), data, data.size()); + ms.preview_done = true; + f.close(); + } else { + found = false; + break; + } + } + if (!found) { + for (int i=0;ivideo_tracks.size();i++) { + FootageStream& ms = footage_->video_tracks[i]; + ms.preview_done = false; + } + for (int i=0;iaudio_tracks.size();i++) { + FootageStream& ms = footage_->audio_tracks[i]; + ms.audio_preview.clear(); + ms.preview_done = false; + } + } + return !found; +} + +void PreviewGenerator::finalize_media() { + if (!cancelled_) { + bool footage_is_ready = true; + + if (footage_->video_tracks.isEmpty() && footage_->audio_tracks.isEmpty()) { + // ERROR + footage_is_ready = false; + invalidate_media(tr("Failed to find any valid video/audio streams")); + } else if (!footage_->video_tracks.isEmpty() && !contains_still_image_) { + // VIDEO + olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_VIDEO); + } else if (!footage_->audio_tracks.isEmpty()) { + // AUDIO + olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_AUDIO); + } else { + // IMAGE + olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_IMAGE); + } + + if (footage_is_ready) { + footage_->ready_lock.unlock(); + footage_->ready = true; + media_->update_tooltip(); + } + + QVector all_sequences = olive::project_model.GetAllSequences(); + for (int i=0;ito_sequence()->RefreshClipsUsingMedia(media_); + } + } +} + +void PreviewGenerator::invalidate_media(const QString &error_msg) +{ + media_->update_tooltip(error_msg); + olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_ERROR); + footage_->invalid = true; + footage_->ready_lock.unlock(); +} + +void PreviewGenerator::generate_waveform() { + SwsContext* sws_ctx; + SwrContext* swr_ctx; + AVFrame* temp_frame = av_frame_alloc(); + + // stores codec contexts for format's streams + AVCodecContext** codec_ctx = new AVCodecContext* [fmt_ctx_->nb_streams]; + + // stores media lengths while scanning in case the format has no duration metadata + int64_t* media_lengths = new int64_t[fmt_ctx_->nb_streams]{0}; + + // stores samples while scanning before they get sent to preview file + 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 + bool create_previews = false; + + for (unsigned int i=0;inb_streams;i++) { + + // default to nullptr values for easier memory management later + codec_ctx[i] = nullptr; + waveform_cache_data[i] = nullptr; + + // 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::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) { + + // alloc the context and load the params into it + codec_ctx[i] = avcodec_alloc_context3(codec); + avcodec_parameters_to_context(codec_ctx[i], fmt_ctx_->streams[i]->codecpar); + + // open the decoder + avcodec_open2(codec_ctx[i], codec, nullptr); + + // audio specific functions + if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + + // allocate sample cache for this stream + 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 qint8[2]; + } + + // if codec context has no defined channel layout, guess it from the channel count + if (codec_ctx[i]->channel_layout == 0) { + codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx_->streams[i]->codecpar->channels); + } + + } + + // enable next step of process + create_previews = true; + } + } + } + + if (create_previews) { + // TODO may be unnecessary - doesn't av_read_frame allocate a packet itself? + AVPacket* packet = av_packet_alloc(); + + bool done = true; + + bool end_of_file = false; + + // get the ball rolling + do { + av_read_frame(fmt_ctx_, packet); + } while (codec_ctx[packet->stream_index] == nullptr); + avcodec_send_packet(codec_ctx[packet->stream_index], packet); + + while (!end_of_file) { + while (codec_ctx[packet->stream_index] == nullptr || avcodec_receive_frame(codec_ctx[packet->stream_index], temp_frame) == AVERROR(EAGAIN)) { + av_packet_unref(packet); + int read_ret = av_read_frame(fmt_ctx_, packet); + + if (read_ret < 0) { + end_of_file = true; + if (read_ret != AVERROR_EOF) qCritical() << "Failed to read packet for preview generation" << read_ret; + break; + } + if (codec_ctx[packet->stream_index] != nullptr) { + int send_ret = avcodec_send_packet(codec_ctx[packet->stream_index], packet); + if (send_ret < 0 && send_ret != AVERROR(EAGAIN)) { + qCritical() << "Failed to send packet for preview generation - aborting" << send_ret; + end_of_file = true; + break; + } + } + } + if (!end_of_file) { + FootageStream* s = footage_->get_stream_from_file_index(fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index); + if (s != nullptr) { + if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (!s->preview_done) { + int dstH = olive::config.thumbnail_resolution; + int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height))); + + sws_ctx = sws_getContext( + temp_frame->width, + temp_frame->height, + static_cast(temp_frame->format), + dstW, + dstH, + static_cast(AV_PIX_FMT_RGBA), + SWS_FAST_BILINEAR, + nullptr, + nullptr, + nullptr + ); + + int linesize[AV_NUM_DATA_POINTERS]; + linesize[0] = dstW*4; + + s->video_preview = QImage(dstW, dstH, QImage::Format_RGBA8888); + uint8_t* data = s->video_preview.bits(); + + sws_scale(sws_ctx, + temp_frame->data, + temp_frame->linesize, + 0, + temp_frame->height, + &data, + linesize); + + // is video interlaced? + s->video_auto_interlacing = (temp_frame->interlaced_frame) ? ((temp_frame->top_field_first) ? VIDEO_TOP_FIELD_FIRST : VIDEO_BOTTOM_FIELD_FIRST) : VIDEO_PROGRESSIVE; + s->video_interlacing = s->video_auto_interlacing; + + s->preview_done = true; + + sws_freeContext(sws_ctx); + + if (!retrieve_duration_) { + avcodec_close(codec_ctx[packet->stream_index]); + codec_ctx[packet->stream_index] = nullptr; + } + } + media_lengths[packet->stream_index]++; + } else if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + 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_U8P; + + swr_ctx = swr_alloc_set_opts( + nullptr, + temp_frame->channel_layout, + static_cast(swr_frame->format), + temp_frame->sample_rate, + temp_frame->channel_layout, + static_cast(temp_frame->format), + temp_frame->sample_rate, + 0, + nullptr + ); + + swr_init(swr_ctx); + + swr_convert_frame(swr_ctx, swr_frame, temp_frame); + + // `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::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)); + + // total amount of data in this frame + int nb_bytes = swr_frame->nb_samples * sample_size; + + // loop through entire frame + for (int i=0;ichannels;j++) { + 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); + s->audio_preview.append(max); + } + + waveform_cache_count = 0; + } + + // standard processing for each channel of information + for (int j=0;jchannels;j++) { + 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) { + min = 0; + max = 0; + } + + // 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); + } + + waveform_cache_count++; + + if (cancelled_) { + break; + } + } + + swr_free(&swr_ctx); + av_frame_free(&swr_frame); + + if (cancelled_) { + end_of_file = true; + break; + } + } + } + + // check if we've got all our previews + if (retrieve_duration_) { + done = false; + } else if (footage_->audio_tracks.size() == 0) { + done = true; + for (int i=0;ivideo_tracks.size();i++) { + if (!footage_->video_tracks.at(i).preview_done) { + done = false; + break; + } + } + if (done) { + end_of_file = true; + break; + } + } + av_packet_unref(packet); + } + } + + av_frame_free(&temp_frame); + av_packet_free(&packet); + + for (unsigned int i=0;inb_streams;i++) { + if (waveform_cache_data[i] != nullptr) { + for (int j=0;jchannels;j++) { + delete [] waveform_cache_data[i][j]; + } + delete [] waveform_cache_data[i]; + } + + if (codec_ctx[i] != nullptr) { + avcodec_close(codec_ctx[i]); + avcodec_free_context(&codec_ctx[i]); + } + } + + // by this point, we'll have made all audio waveform previews + for (int i=0;iaudio_tracks.size();i++) { + footage_->audio_tracks[i].preview_done = true; + } + } + + if (retrieve_duration_) { + footage_->length = 0; + unsigned int maximum_stream = 0; + for (unsigned int i=0;inb_streams;i++) { + if (media_lengths[i] > media_lengths[maximum_stream]) { + maximum_stream = i; + } + } + + // FIXME: length is currently retrieved as a frame count rather than a timestamp + footage_->length = qRound(double(media_lengths[maximum_stream]) / av_q2d(fmt_ctx_->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE); + + finalize_media(); + } + + delete [] waveform_cache_data; + delete [] media_lengths; + delete [] codec_ctx; +} + +QString PreviewGenerator::get_thumbnail_path(const QString& hash, const FootageStream& ms) { + return data_dir_.filePath(QString("%1t%2").arg(hash, QString::number(ms.file_index))); +} + +QString PreviewGenerator::get_waveform_path(const QString& hash, const FootageStream& ms) { + return data_dir_.filePath(QString("%1w%2").arg(hash, QString::number(ms.file_index))); +} + +void PreviewGenerator::run() { + Q_ASSERT(footage_ != nullptr); + Q_ASSERT(media_ != nullptr); + + const QString url = footage_->url; + QByteArray ba = url.toUtf8(); + char* filename = new char[ba.size()+1]; + strcpy(filename, ba.data()); + + QString errorStr; + bool error = false; + + AVDictionary* format_opts = nullptr; + + // for image sequences that don't start at 0, set the index where it does start + if (footage_->start_number > 0) { + av_dict_set(&format_opts, "start_number", QString::number(footage_->start_number).toUtf8(), 0); + } + + int errCode = avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts); + if(errCode != 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + errorStr = tr("Could not open file - %1").arg(err); + error = true; + } else { + errCode = avformat_find_stream_info(fmt_ctx_, nullptr); + if (errCode < 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + errorStr = tr("Could not find stream information - %1").arg(err); + error = true; + } else { + av_dump_format(fmt_ctx_, 0, filename, 0); + parse_media(); + + // see if we already have data for this + QString hash = get_file_hash(footage_->url); + + if (retrieve_preview(hash)) { + sem.acquire(); + + if (!cancelled_) { + generate_waveform(); + + if (!cancelled_) { + // save preview to file + for (int i=0;ivideo_tracks.size();i++) { + FootageStream& ms = footage_->video_tracks[i]; + ms.video_preview.save(get_thumbnail_path(hash, ms), "PNG"); + //dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); + } + for (int i=0;iaudio_tracks.size();i++) { + FootageStream& ms = footage_->audio_tracks[i]; + QFile f(get_waveform_path(hash, ms)); + f.open(QFile::WriteOnly); + 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); + } + } + } + + sem.release(); + } + } + avformat_close_input(&fmt_ctx_); + } + + if (!cancelled_) { + if (error) { + invalidate_media(errorStr); + } + } + + delete [] filename; + footage_->preview_gen = nullptr; +} + +void PreviewGenerator::cancel() { + cancelled_ = true; + wait(); +} + +void PreviewGenerator::AnalyzeMedia(Media *m) +{ + // PreviewGenerator's constructor starts the thread, sets a reference of itself as the media's generator, + // and connects its thread completion to its own deletion, therefore handling its own memory. Nothing else needs to + // be done. + new PreviewGenerator(m); +} diff --git a/project/previewgenerator.h b/project/previewgenerator.h index 9f7ddea25..b2c401b22 100644 --- a/project/previewgenerator.h +++ b/project/previewgenerator.h @@ -1,65 +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 PREVIEWGENERATOR_H -#define PREVIEWGENERATOR_H - -#include -#include -#include - -#include "project/footage.h" -#include "project/media.h" - -extern "C" { -#include -#include -#include -#include -} - -class PreviewGenerator : public QThread -{ - Q_OBJECT -public: - PreviewGenerator(Media*); - void run(); - void cancel(); - - static void AnalyzeMedia(Media*); -private: - void parse_media(); - bool retrieve_preview(const QString &hash); - void generate_waveform(); - void finalize_media(); - void invalidate_media(const QString& error_msg); - QString get_thumbnail_path(const QString &hash, const FootageStream &ms); - QString get_waveform_path(const QString& hash, const FootageStream &ms); - - AVFormatContext* fmt_ctx_; - Media* media_; - Footage* footage_; - bool retrieve_duration_; - bool contains_still_image_; - bool cancelled_; - QDir data_dir_; -}; - -#endif // PREVIEWGENERATOR_H +/*** + + 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 PREVIEWGENERATOR_H +#define PREVIEWGENERATOR_H + +#include +#include +#include + +#include "project/footage.h" +#include "project/media.h" + +extern "C" { +#include +#include +#include +#include +} + +class PreviewGenerator : public QThread +{ + Q_OBJECT +public: + PreviewGenerator(Media*); + void run(); + void cancel(); + + static void AnalyzeMedia(Media*); +private: + void parse_media(); + bool retrieve_preview(const QString &hash); + void generate_waveform(); + void finalize_media(); + void invalidate_media(const QString& error_msg); + QString get_thumbnail_path(const QString &hash, const FootageStream &ms); + QString get_waveform_path(const QString& hash, const FootageStream &ms); + + AVFormatContext* fmt_ctx_; + Media* media_; + Footage* footage_; + bool retrieve_duration_; + bool contains_still_image_; + bool cancelled_; + QDir data_dir_; +}; + +#endif // PREVIEWGENERATOR_H diff --git a/project/projectelements.h b/project/projectelements.h index c83feaaa5..3b17f454f 100644 --- a/project/projectelements.h +++ b/project/projectelements.h @@ -1,34 +1,34 @@ -/*** - - 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 PROJECTELEMENTS_H -#define PROJECTELEMENTS_H - -/** - - Simple header to include all classes used for an Olive project file - - */ - -// includes elements the user can use in a project -#include "media.h" -#include "footage.h" - -#endif // PROJECTELEMENTS_H +/*** + + 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 PROJECTELEMENTS_H +#define PROJECTELEMENTS_H + +/** + + Simple header to include all classes used for an Olive project file + + */ + +// includes elements the user can use in a project +#include "media.h" +#include "footage.h" + +#endif // PROJECTELEMENTS_H diff --git a/project/projectfilter.cpp b/project/projectfilter.cpp index e7691007a..2f0c33008 100644 --- a/project/projectfilter.cpp +++ b/project/projectfilter.cpp @@ -1,84 +1,84 @@ -/*** - - 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 "projectfilter.h" - -#include "project/media.h" -#include "timeline/sequence.h" - -#include - -ProjectFilter::ProjectFilter(QObject *parent) : - QSortFilterProxyModel(parent), - show_sequences(true) -{} - -bool ProjectFilter::get_show_sequences() { - return show_sequences; -} - -void ProjectFilter::set_show_sequences(bool b) { - show_sequences = b; - invalidateFilter(); -} - -void ProjectFilter::update_search_filter(const QString &s) { - search_filter = s; - invalidateFilter(); -} - -bool ProjectFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { - // retrieve media object from index - QModelIndex index = sourceModel()->index(source_row, 0, source_parent); - Media* media = static_cast(index.internalPointer()); - - // hide sequences if show_sequences is false - if (!show_sequences) { - if (media != nullptr && media->get_type() == MEDIA_TYPE_SEQUENCE) { - return false; - } - } - - // filter by search filter string - if (!search_filter.isEmpty()) { - // search markers if media is a sequene - bool marker_contains_search = false; - - if (media->get_type() == MEDIA_TYPE_SEQUENCE - || media->get_type() == MEDIA_TYPE_FOOTAGE) { - QVector& markers = media->get_markers(); - for (int i=0;iget_type() != MEDIA_TYPE_FOLDER - && !media->get_name().contains(search_filter, Qt::CaseInsensitive)) { - return false; - } - } - - return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); -} +/*** + + 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 "projectfilter.h" + +#include "project/media.h" +#include "timeline/sequence.h" + +#include + +ProjectFilter::ProjectFilter(QObject *parent) : + QSortFilterProxyModel(parent), + show_sequences(true) +{} + +bool ProjectFilter::get_show_sequences() { + return show_sequences; +} + +void ProjectFilter::set_show_sequences(bool b) { + show_sequences = b; + invalidateFilter(); +} + +void ProjectFilter::update_search_filter(const QString &s) { + search_filter = s; + invalidateFilter(); +} + +bool ProjectFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + // retrieve media object from index + QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + Media* media = static_cast(index.internalPointer()); + + // hide sequences if show_sequences is false + if (!show_sequences) { + if (media != nullptr && media->get_type() == MEDIA_TYPE_SEQUENCE) { + return false; + } + } + + // filter by search filter string + if (!search_filter.isEmpty()) { + // search markers if media is a sequene + bool marker_contains_search = false; + + if (media->get_type() == MEDIA_TYPE_SEQUENCE + || media->get_type() == MEDIA_TYPE_FOOTAGE) { + QVector& markers = media->get_markers(); + for (int i=0;iget_type() != MEDIA_TYPE_FOLDER + && !media->get_name().contains(search_filter, Qt::CaseInsensitive)) { + return false; + } + } + + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); +} diff --git a/project/projectfilter.h b/project/projectfilter.h index 40eee7b0a..b5f633de6 100644 --- a/project/projectfilter.h +++ b/project/projectfilter.h @@ -1,57 +1,57 @@ -/*** - - 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 PROJECTFILTER_H -#define PROJECTFILTER_H - -#include - -class ProjectFilter : public QSortFilterProxyModel { - Q_OBJECT -public: - ProjectFilter(QObject *parent = nullptr); - - // are sequences visible - bool get_show_sequences(); - -public slots: - - // set whether sequences are visible - void set_show_sequences(bool b); - - // update search filter - void update_search_filter(const QString& s); - -protected: - - // function that filters whether rows are displayed or not - virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; - -private: - - // internal variable for whether to show sequences - bool show_sequences; - - // search filter variable - QString search_filter; - -}; - -#endif // PROJECTFILTER_H +/*** + + 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 PROJECTFILTER_H +#define PROJECTFILTER_H + +#include + +class ProjectFilter : public QSortFilterProxyModel { + Q_OBJECT +public: + ProjectFilter(QObject *parent = nullptr); + + // are sequences visible + bool get_show_sequences(); + +public slots: + + // set whether sequences are visible + void set_show_sequences(bool b); + + // update search filter + void update_search_filter(const QString& s); + +protected: + + // function that filters whether rows are displayed or not + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +private: + + // internal variable for whether to show sequences + bool show_sequences; + + // search filter variable + QString search_filter; + +}; + +#endif // PROJECTFILTER_H diff --git a/project/proxygenerator.cpp b/project/proxygenerator.cpp index f90f5699d..57830019e 100644 --- a/project/proxygenerator.cpp +++ b/project/proxygenerator.cpp @@ -1,427 +1,427 @@ -/*** - - 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 "proxygenerator.h" - -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) {} - -void ProxyGenerator::transcode(const ProxyInfo& info) { - Footage* footage = info.media->to_footage(); - - // set progress to 0 - current_progress = 0.0; - - // for image sequences that don't start at 0, set the index where it does start - AVDictionary* format_opts = nullptr; - if (footage->start_number > 0) { - av_dict_set(&format_opts, "start_number", QString::number(footage->start_number).toUtf8(), 0); - } - - // open input file - AVFormatContext* input_fmt_ctx = nullptr; - avformat_open_input(&input_fmt_ctx, footage->url.toUtf8(), nullptr, &format_opts); - - // open output file - AVFormatContext* output_fmt_ctx = nullptr; - avformat_alloc_output_context2(&output_fmt_ctx, nullptr, nullptr, info.path.toUtf8()); - - // open output file writing handle - avio_open(&output_fmt_ctx->pb, info.path.toUtf8(), AVIO_FLAG_WRITE); - - // get stream info from input file - avformat_find_stream_info(input_fmt_ctx, nullptr); - - // create array of input decoders - QVector input_streams; - input_streams.resize(input_fmt_ctx->nb_streams); - input_streams.fill(nullptr); - - // create array of output encoders - QVector output_streams; - output_streams.resize(input_fmt_ctx->nb_streams); - output_streams.fill(nullptr); - - // create array of swscale contexts for pixel format conversion - QVector sws_contexts; - sws_contexts.resize(input_fmt_ctx->nb_streams); - sws_contexts.fill(nullptr); - - // loop through file to find compatible video streams - for (int i=0;inb_streams);i++) { - AVStream* in_stream = input_fmt_ctx->streams[i]; - - // create new stream in output - AVStream* out_stream = avformat_new_stream(output_fmt_ctx, nullptr); - out_stream->id = in_stream->id; - - // find decoder for this codec - AVCodec* dec_codec = avcodec_find_decoder(in_stream->codecpar->codec_id); - - // find encoder for chosen proxy type - AVCodec* enc_codec = avcodec_find_encoder(temp_enc_codec); - - // we only transcode video streams, others we just passthrough - if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && dec_codec != nullptr) { - - // allocate decoding context for this stream - AVCodecContext* dec_ctx = avcodec_alloc_context3(dec_codec); - - // copy parameters from stream to decoding context - avcodec_parameters_to_context(dec_ctx, in_stream->codecpar); - - // open decoder - avcodec_open2(dec_ctx, dec_codec, nullptr); - - // store decoding context in array - input_streams[i] = dec_ctx; - - // retrieve more information about this stream - av_dump_format(input_fmt_ctx, i, footage->url.toUtf8(), 0); - - // allocate encoding context for this stream - AVCodecContext* enc_ctx = avcodec_alloc_context3(enc_codec); - - // copy properties from decoding context to encoding context - enc_ctx->codec_id = temp_enc_codec; - enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO; - 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 = 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; - - // if format uses global headers, add flag to enc_ctx - if (output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { - enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - } - - // set encoder options (mostly just multithreading) - AVDictionary* opts = nullptr; - av_dict_set(&opts, "threads", "auto", 0); - - // open encoder - avcodec_open2(enc_ctx, enc_codec, &opts); - - // copy parameters from encoding context to stream - avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); - - // store encoding context in array - output_streams[i] = enc_ctx; - - // create swscontext for this stream - SwsContext* sws_ctx = sws_getContext( - in_stream->codecpar->width, - in_stream->codecpar->height, - static_cast(in_stream->codecpar->format), - enc_ctx->width, - enc_ctx->height, - enc_ctx->pix_fmt, - 0, - nullptr, - nullptr, - nullptr - ); - - sws_contexts[i] = sws_ctx; - } else { - avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); - } - } - - // write video header - avformat_write_header(output_fmt_ctx, nullptr); - - // packet that av_read_frame will dump file packets into - AVPacket packet; - av_init_packet(&packet); - - // frame that decoder will decode into - AVFrame* dec_frame = av_frame_alloc(); - - // main transcoding loop - while (!skip) { - - // cache stream index - int stream_index = packet.stream_index; - - // retrieve frame from decoder (this will clear the last frame so we don't have to do that) - int read_ret = -1; - int recfr_ret = -1; - do { - // read from input file - read_ret = av_read_frame(input_fmt_ctx, &packet); - - // handle errors - if (read_ret < 0) { - - // AVERROR_EOF means we've simply reached the end of the file, otherwise this is an error - if (read_ret != AVERROR_EOF) { - qWarning() << "Proxy generation for file" << footage->url << "ended prematurely"; - } - - // either way, we shall abort reading - break; - } - - stream_index = packet.stream_index; - - // determine whether this frame is from a stream we're transcoding - if (input_streams.at(stream_index) == nullptr) { - // if we didn't allocate a decoder for this earlier, we just pass it through - - av_packet_rescale_ts(&packet, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base); - - // write packet to output - av_interleaved_write_frame(output_fmt_ctx, &packet); - - } else { - // we're going to transcode this packet. - - // send packet to decoder - avcodec_send_packet(input_streams.at(stream_index), &packet); - - // use timestamp and stream duration to create a rough estimation of the progress through this file - current_progress = qCeil((double(packet.pts)/double(input_fmt_ctx->streams[packet.stream_index]->duration))*100); - - } - - // free packet allocated by av_read_frame - av_packet_unref(&packet); - } while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN) && !skip); - - // error/eof handling - cancel while loop - if (read_ret < 0 || skip) { - break; - } - - // free packet as we're about to use it for encoding - av_packet_unref(&packet); - - // rescale input frame timestamp to output timestamp - dec_frame->pts = av_rescale_q(dec_frame->pts, - input_fmt_ctx->streams[stream_index]->time_base, - output_fmt_ctx->streams[stream_index]->time_base); - - // determine if the pix_fmt, width, and/or height is different, so if we need to convert - bool convert_pix_fmt = (output_streams.at(stream_index)->pix_fmt != input_streams.at(stream_index)->pix_fmt - || output_streams.at(stream_index)->width != input_streams.at(stream_index)->width - || output_streams.at(stream_index)->height != input_streams.at(stream_index)->height); - - // create reference to the frame to be sent to the encoder - AVFrame* enc_frame = dec_frame; - - if (convert_pix_fmt) { - // create sws frame for pixel format conversion - enc_frame = av_frame_alloc(); - enc_frame->width = output_streams.at(stream_index)->width; - enc_frame->height = output_streams.at(stream_index)->height; - enc_frame->format = output_streams.at(stream_index)->pix_fmt; - av_frame_get_buffer(enc_frame, 0); - - // convert pixel format to format expected by the encoder - sws_scale(sws_contexts.at(stream_index), dec_frame->data, dec_frame->linesize, 0, dec_frame->height, enc_frame->data, enc_frame->linesize); - - // set same pts as dec_frame - enc_frame->pts = dec_frame->pts; - } - - // send frame to encoder - avcodec_send_frame(output_streams.at(stream_index), enc_frame); - - if (convert_pix_fmt) { - // free sws frame since we made one before - av_frame_free(&enc_frame); - } - - // return value for packet receiving - int recret; - - // loop through receiving packets - while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0 && !skip) { - - // set packet stream index to current stream index - packet.stream_index = stream_index; - - // write frame to file - av_interleaved_write_frame(output_fmt_ctx, &packet); - - // unref old packet - av_packet_unref(&packet); - - } - - } - - // free dec_frame - av_frame_free(&dec_frame); - - // write video trailer - av_write_trailer(output_fmt_ctx); - - // free stream contexts - for (int i=0;inb_streams);i++) { - if (input_streams[i] != nullptr) { - // free swscale contexts - sws_freeContext(sws_contexts[i]); - - // free input decoding context - avcodec_close(input_streams[i]); - avcodec_free_context(&input_streams[i]); - - // free output encoding context - avcodec_close(output_streams[i]); - avcodec_free_context(&output_streams[i]); - } - } - - // close output file handle - avio_closep(&output_fmt_ctx->pb); - - // close output file - avformat_free_context(output_fmt_ctx); - - // close input file - avformat_close_input(&input_fmt_ctx); - - // set footage to use newly generated proxy - footage->proxy = true; - footage->proxy_path = info.path; - - qInfo() << "Finished creating proxy for" << footage->url; - QMetaObject::invokeMethod(olive::MainWindow->statusBar(), - "showMessage", - Qt::QueuedConnection, - Q_ARG(QString, tr("Finished generating proxy for \"%1\"").arg(footage->url))); -} - -// main proxy generating loop -void ProxyGenerator::run() { - // mutex used for thread safe signalling - mutex.lock(); - - while (!cancelled) { - // wait for queue() to be called - waitCond.wait(&mutex); - - // quit thread if cancel() was called - if (cancelled) break; - - // loop through queue until the queue is empty - while (proxy_queue.size() > 0) { - - // grab proxy info - const ProxyInfo& info = proxy_queue.first(); - - // create directory for info - QFileInfo(info.path).dir().mkpath("."); - - // 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(); - - // quit loop if cancel() was called - if (cancelled) break; - - } - } - - mutex.unlock(); -} - -// called to add footage to generate proxies for -void ProxyGenerator::queue(const ProxyInfo &info) { - // remove any queued proxies with the same footage - if (!proxy_queue.isEmpty() - && proxy_queue.first().media == info.media) { - // if the thread is currently processing a proxy with the same footage, abort it - skip = true; - } - - // scan through the rest of the queue for another proxy with the same footage (start with 1 since we already processed first()) - for (int i=1;i. + +***/ + +#include "proxygenerator.h" + +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) {} + +void ProxyGenerator::transcode(const ProxyInfo& info) { + Footage* footage = info.media->to_footage(); + + // set progress to 0 + current_progress = 0.0; + + // for image sequences that don't start at 0, set the index where it does start + AVDictionary* format_opts = nullptr; + if (footage->start_number > 0) { + av_dict_set(&format_opts, "start_number", QString::number(footage->start_number).toUtf8(), 0); + } + + // open input file + AVFormatContext* input_fmt_ctx = nullptr; + avformat_open_input(&input_fmt_ctx, footage->url.toUtf8(), nullptr, &format_opts); + + // open output file + AVFormatContext* output_fmt_ctx = nullptr; + avformat_alloc_output_context2(&output_fmt_ctx, nullptr, nullptr, info.path.toUtf8()); + + // open output file writing handle + avio_open(&output_fmt_ctx->pb, info.path.toUtf8(), AVIO_FLAG_WRITE); + + // get stream info from input file + avformat_find_stream_info(input_fmt_ctx, nullptr); + + // create array of input decoders + QVector input_streams; + input_streams.resize(input_fmt_ctx->nb_streams); + input_streams.fill(nullptr); + + // create array of output encoders + QVector output_streams; + output_streams.resize(input_fmt_ctx->nb_streams); + output_streams.fill(nullptr); + + // create array of swscale contexts for pixel format conversion + QVector sws_contexts; + sws_contexts.resize(input_fmt_ctx->nb_streams); + sws_contexts.fill(nullptr); + + // loop through file to find compatible video streams + for (int i=0;inb_streams);i++) { + AVStream* in_stream = input_fmt_ctx->streams[i]; + + // create new stream in output + AVStream* out_stream = avformat_new_stream(output_fmt_ctx, nullptr); + out_stream->id = in_stream->id; + + // find decoder for this codec + AVCodec* dec_codec = avcodec_find_decoder(in_stream->codecpar->codec_id); + + // find encoder for chosen proxy type + AVCodec* enc_codec = avcodec_find_encoder(temp_enc_codec); + + // we only transcode video streams, others we just passthrough + if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && dec_codec != nullptr) { + + // allocate decoding context for this stream + AVCodecContext* dec_ctx = avcodec_alloc_context3(dec_codec); + + // copy parameters from stream to decoding context + avcodec_parameters_to_context(dec_ctx, in_stream->codecpar); + + // open decoder + avcodec_open2(dec_ctx, dec_codec, nullptr); + + // store decoding context in array + input_streams[i] = dec_ctx; + + // retrieve more information about this stream + av_dump_format(input_fmt_ctx, i, footage->url.toUtf8(), 0); + + // allocate encoding context for this stream + AVCodecContext* enc_ctx = avcodec_alloc_context3(enc_codec); + + // copy properties from decoding context to encoding context + enc_ctx->codec_id = temp_enc_codec; + enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO; + 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 = 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; + + // if format uses global headers, add flag to enc_ctx + if (output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { + enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + // set encoder options (mostly just multithreading) + AVDictionary* opts = nullptr; + av_dict_set(&opts, "threads", "auto", 0); + + // open encoder + avcodec_open2(enc_ctx, enc_codec, &opts); + + // copy parameters from encoding context to stream + avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); + + // store encoding context in array + output_streams[i] = enc_ctx; + + // create swscontext for this stream + SwsContext* sws_ctx = sws_getContext( + in_stream->codecpar->width, + in_stream->codecpar->height, + static_cast(in_stream->codecpar->format), + enc_ctx->width, + enc_ctx->height, + enc_ctx->pix_fmt, + 0, + nullptr, + nullptr, + nullptr + ); + + sws_contexts[i] = sws_ctx; + } else { + avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); + } + } + + // write video header + avformat_write_header(output_fmt_ctx, nullptr); + + // packet that av_read_frame will dump file packets into + AVPacket packet; + av_init_packet(&packet); + + // frame that decoder will decode into + AVFrame* dec_frame = av_frame_alloc(); + + // main transcoding loop + while (!skip) { + + // cache stream index + int stream_index = packet.stream_index; + + // retrieve frame from decoder (this will clear the last frame so we don't have to do that) + int read_ret = -1; + int recfr_ret = -1; + do { + // read from input file + read_ret = av_read_frame(input_fmt_ctx, &packet); + + // handle errors + if (read_ret < 0) { + + // AVERROR_EOF means we've simply reached the end of the file, otherwise this is an error + if (read_ret != AVERROR_EOF) { + qWarning() << "Proxy generation for file" << footage->url << "ended prematurely"; + } + + // either way, we shall abort reading + break; + } + + stream_index = packet.stream_index; + + // determine whether this frame is from a stream we're transcoding + if (input_streams.at(stream_index) == nullptr) { + // if we didn't allocate a decoder for this earlier, we just pass it through + + av_packet_rescale_ts(&packet, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base); + + // write packet to output + av_interleaved_write_frame(output_fmt_ctx, &packet); + + } else { + // we're going to transcode this packet. + + // send packet to decoder + avcodec_send_packet(input_streams.at(stream_index), &packet); + + // use timestamp and stream duration to create a rough estimation of the progress through this file + current_progress = qCeil((double(packet.pts)/double(input_fmt_ctx->streams[packet.stream_index]->duration))*100); + + } + + // free packet allocated by av_read_frame + av_packet_unref(&packet); + } while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN) && !skip); + + // error/eof handling - cancel while loop + if (read_ret < 0 || skip) { + break; + } + + // free packet as we're about to use it for encoding + av_packet_unref(&packet); + + // rescale input frame timestamp to output timestamp + dec_frame->pts = av_rescale_q(dec_frame->pts, + input_fmt_ctx->streams[stream_index]->time_base, + output_fmt_ctx->streams[stream_index]->time_base); + + // determine if the pix_fmt, width, and/or height is different, so if we need to convert + bool convert_pix_fmt = (output_streams.at(stream_index)->pix_fmt != input_streams.at(stream_index)->pix_fmt + || output_streams.at(stream_index)->width != input_streams.at(stream_index)->width + || output_streams.at(stream_index)->height != input_streams.at(stream_index)->height); + + // create reference to the frame to be sent to the encoder + AVFrame* enc_frame = dec_frame; + + if (convert_pix_fmt) { + // create sws frame for pixel format conversion + enc_frame = av_frame_alloc(); + enc_frame->width = output_streams.at(stream_index)->width; + enc_frame->height = output_streams.at(stream_index)->height; + enc_frame->format = output_streams.at(stream_index)->pix_fmt; + av_frame_get_buffer(enc_frame, 0); + + // convert pixel format to format expected by the encoder + sws_scale(sws_contexts.at(stream_index), dec_frame->data, dec_frame->linesize, 0, dec_frame->height, enc_frame->data, enc_frame->linesize); + + // set same pts as dec_frame + enc_frame->pts = dec_frame->pts; + } + + // send frame to encoder + avcodec_send_frame(output_streams.at(stream_index), enc_frame); + + if (convert_pix_fmt) { + // free sws frame since we made one before + av_frame_free(&enc_frame); + } + + // return value for packet receiving + int recret; + + // loop through receiving packets + while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0 && !skip) { + + // set packet stream index to current stream index + packet.stream_index = stream_index; + + // write frame to file + av_interleaved_write_frame(output_fmt_ctx, &packet); + + // unref old packet + av_packet_unref(&packet); + + } + + } + + // free dec_frame + av_frame_free(&dec_frame); + + // write video trailer + av_write_trailer(output_fmt_ctx); + + // free stream contexts + for (int i=0;inb_streams);i++) { + if (input_streams[i] != nullptr) { + // free swscale contexts + sws_freeContext(sws_contexts[i]); + + // free input decoding context + avcodec_close(input_streams[i]); + avcodec_free_context(&input_streams[i]); + + // free output encoding context + avcodec_close(output_streams[i]); + avcodec_free_context(&output_streams[i]); + } + } + + // close output file handle + avio_closep(&output_fmt_ctx->pb); + + // close output file + avformat_free_context(output_fmt_ctx); + + // close input file + avformat_close_input(&input_fmt_ctx); + + // set footage to use newly generated proxy + footage->proxy = true; + footage->proxy_path = info.path; + + qInfo() << "Finished creating proxy for" << footage->url; + QMetaObject::invokeMethod(olive::MainWindow->statusBar(), + "showMessage", + Qt::QueuedConnection, + Q_ARG(QString, tr("Finished generating proxy for \"%1\"").arg(footage->url))); +} + +// main proxy generating loop +void ProxyGenerator::run() { + // mutex used for thread safe signalling + mutex.lock(); + + while (!cancelled) { + // wait for queue() to be called + waitCond.wait(&mutex); + + // quit thread if cancel() was called + if (cancelled) break; + + // loop through queue until the queue is empty + while (proxy_queue.size() > 0) { + + // grab proxy info + const ProxyInfo& info = proxy_queue.first(); + + // create directory for info + QFileInfo(info.path).dir().mkpath("."); + + // 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(); + + // quit loop if cancel() was called + if (cancelled) break; + + } + } + + mutex.unlock(); +} + +// called to add footage to generate proxies for +void ProxyGenerator::queue(const ProxyInfo &info) { + // remove any queued proxies with the same footage + if (!proxy_queue.isEmpty() + && proxy_queue.first().media == info.media) { + // if the thread is currently processing a proxy with the same footage, abort it + skip = true; + } + + // scan through the rest of the queue for another proxy with the same footage (start with 1 since we already processed first()) + for (int i=1;i. - -***/ - -#ifndef PROXYGENERATOR_H -#define PROXYGENERATOR_H - -#include -#include -#include -#include - -#include "project/media.h" - -struct ProxyInfo { - Media* media; - double size_multiplier; - int codec_type; - QString path; -}; - -class ProxyGenerator : public QThread { - Q_OBJECT -public: - ProxyGenerator(); - void run(); - void queue(const ProxyInfo& info); - void cancel(); - double get_proxy_progress(Media *f); -private: - // queue of footage to process proxies for - QVector proxy_queue; - - // threading objects - QWaitCondition waitCond; - QMutex mutex; - - // set to true if you want to permanently close ProxyGenerator - bool cancelled; - - // set to true if you want to abort the footage currently being processed - bool skip; - - // stores progress in percent of proxy currently being processed - double current_progress; - - // function that performs the actual transcode - void transcode(const ProxyInfo& info); -}; - -namespace olive { - // proxy generator is a global omnipotent entity - extern ProxyGenerator proxy_generator; -} - -#endif // PROXYGENERATOR_H +/*** + + 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 PROXYGENERATOR_H +#define PROXYGENERATOR_H + +#include +#include +#include +#include + +#include "project/media.h" + +struct ProxyInfo { + Media* media; + double size_multiplier; + int codec_type; + QString path; +}; + +class ProxyGenerator : public QThread { + Q_OBJECT +public: + ProxyGenerator(); + void run(); + void queue(const ProxyInfo& info); + void cancel(); + double get_proxy_progress(Media *f); +private: + // queue of footage to process proxies for + QVector proxy_queue; + + // threading objects + QWaitCondition waitCond; + QMutex mutex; + + // set to true if you want to permanently close ProxyGenerator + bool cancelled; + + // set to true if you want to abort the footage currently being processed + bool skip; + + // stores progress in percent of proxy currently being processed + double current_progress; + + // function that performs the actual transcode + void transcode(const ProxyInfo& info); +}; + +namespace olive { + // proxy generator is a global omnipotent entity + extern ProxyGenerator proxy_generator; +} + +#endif // PROXYGENERATOR_H diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index cff3d855f..433a007c3 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -1,466 +1,466 @@ -/*** - - 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 "sourcescommon.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "ui/menuhelper.h" -#include "panels/panels.h" -#include "project/media.h" -#include "undo/undo.h" -#include "rendering/renderfunctions.h" -#include "panels/timeline.h" -#include "panels/project.h" -#include "project/footage.h" -#include "panels/viewer.h" -#include "project/projectfilter.h" -#include "timeline/sequence.h" -#include "global/config.h" -#include "global/global.h" -#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" - -SourcesCommon::SourcesCommon(Project* parent, ProjectFilter &sort_filter) : - editing_item(nullptr), - project_parent(parent), - sort_filter_(sort_filter) -{ - rename_timer.setInterval(1000); - connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); -} - -void SourcesCommon::create_seq_from_selected() { - if (!selected_items.isEmpty()) { - QVector media_list; - for (int i=0;iitem_to_media(selected_items.at(i))); - } - - ComboAction* ca = new ComboAction(); - SequencePtr s = olive::project::CreateSequenceFromMedia(media_list); - - // add clips to it - s->AddClipsFromGhosts(ca, olive::timeline::CreateGhostsFromMedia(s.get(), 0, media_list)); - - olive::project_model.CreateSequence(ca, s, true, nullptr); - olive::undo_stack.push(ca); - } -} - -void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& items) { - Menu menu(parent); - - selected_items = items; - - QAction* import_action = menu.addAction(tr("Import...")); - 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); - olive::MenuHelper.make_new_menu(new_menu); - - Menu* view_menu = new Menu(tr("View")); - menu.addMenu(view_menu); - - QAction* tree_view_action = view_menu->addAction(tr("Tree View")); - connect(tree_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_tree_view())); - - QAction* icon_view_action = view_menu->addAction(tr("Icon View")); - connect(icon_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_icon_view())); - - QAction* toolbar_action = view_menu->addAction(tr("Show Toolbar")); - toolbar_action->setCheckable(true); - toolbar_action->setChecked(project_parent->IsToolbarVisible()); - connect(toolbar_action, SIGNAL(triggered(bool)), project_parent, SLOT(SetToolbarVisible(bool))); - - QAction* show_sequences = view_menu->addAction(tr("Show Sequences")); - show_sequences->setCheckable(true); - show_sequences->setChecked(sort_filter_.get_show_sequences()); - connect(show_sequences, SIGNAL(triggered(bool)), &sort_filter_, SLOT(set_show_sequences(bool))); - - if (items.size() > 0) { - if (items.size() == 1) { - Media* first_media = project_parent->item_to_media(items.at(0)); - - // replace footage - int type = first_media->get_type(); - if (type == MEDIA_TYPE_FOOTAGE) { - QAction* replace_action = menu.addAction(tr("Replace/Relink Media")); - QObject::connect(replace_action, SIGNAL(triggered(bool)), project_parent, SLOT(replace_selected_file())); - -#if defined(Q_OS_WIN) - QAction* reveal_in_explorer = menu.addAction(tr("Reveal in Explorer")); -#elif defined(Q_OS_MAC) - QAction* reveal_in_explorer = menu.addAction(tr("Reveal in Finder")); -#else - QAction* reveal_in_explorer = menu.addAction(tr("Reveal in File Manager")); -#endif - QObject::connect(reveal_in_explorer, SIGNAL(triggered(bool)), this, SLOT(reveal_in_browser())); - } - if (type != MEDIA_TYPE_FOLDER) { - QAction* replace_clip_media = menu.addAction(tr("Replace Clips Using This Media")); - QObject::connect(replace_clip_media, SIGNAL(triggered(bool)), project_parent, SLOT(replace_clip_media())); - } - } - - // analyze selected footage types - bool all_sequences = true; - bool all_footage = true; - - cached_selected_footage.clear(); - for (int i=0;iitem_to_media(items.at(i)); - if (m->get_type() != MEDIA_TYPE_SEQUENCE) { - all_sequences = false; - } - if (m->get_type() == MEDIA_TYPE_FOOTAGE) { - cached_selected_footage.append(m); - } else { - all_footage = false; - } - } - - // create sequence from - QAction* create_seq_from = menu.addAction(tr("Create Sequence With This Media")); - QObject::connect(create_seq_from, SIGNAL(triggered(bool)), this, SLOT(create_seq_from_selected())); - - // ONLY sequences are selected - if (all_sequences) { - // ONLY sequences are selected - QAction* duplicate_action = menu.addAction(tr("Duplicate")); - QObject::connect(duplicate_action, SIGNAL(triggered(bool)), project_parent, SLOT(duplicate_selected())); - } - - // ONLY footage is selected - if (all_footage) { - QAction* delete_footage_from_sequences = menu.addAction(tr("Delete All Clips Using This Media")); - QObject::connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), project_parent, SLOT(delete_clips_using_selected_media())); - - Menu* proxies = new Menu(tr("Proxy")); - menu.addMenu(proxies); - - // special case if one footage item is selected and its proxy is currently being generated - if (cached_selected_footage.size() == 1 - && cached_selected_footage.at(0)->to_footage()->proxy - && cached_selected_footage.at(0)->to_footage()->proxy_path.isEmpty()) { - QAction* action = proxies->addAction(tr("Generating proxy: %1% complete").arg( - olive::proxy_generator.get_proxy_progress(cached_selected_footage.at(0)) - ) - ); - action->setEnabled(false); - } else { - // determine whether any selected footage has or doesn't have proxies - bool footage_without_proxies_exists = false; - bool footage_with_proxies_exists = false; - - for (int i=0;ito_footage()->proxy) { - footage_with_proxies_exists = true; - } else { - footage_without_proxies_exists = true; - } - } - - // if footage was selected WITHOUT proxies - if (footage_without_proxies_exists) { - QString create_proxy_text; - - if (footage_with_proxies_exists) { - // some of the footage already has proxies, so we use a different string - create_proxy_text = tr("Create/Modify Proxy"); - } else { - // none of the footage has proxies - create_proxy_text = tr("Create Proxy"); - } - - proxies->addAction(create_proxy_text, this, SLOT(open_create_proxy_dialog())); - } - - // if footage was selected WITH proxies - if (footage_with_proxies_exists) { - - if (!footage_without_proxies_exists) { - // if all the footage has proxies, we didn't make a "Create/Modify" above, so we create one here (but only "modify") - proxies->addAction(tr("Modify Proxy"), this, SLOT(open_create_proxy_dialog())); - } - - proxies->addAction(tr("Restore Original"), this, SLOT(clear_proxies_from_selected())); - } - } - } - - // delete media - QAction* delete_action = menu.addAction(tr("Delete")); - QObject::connect(delete_action, SIGNAL(triggered(bool)), project_parent, SLOT(delete_selected_media())); - - if (items.size() == 1) { - Media* media_item = project_parent->item_to_media(items.at(0)); - - if (media_item->get_type() != MEDIA_TYPE_FOLDER) { - QAction* preview_in_media_viewer_action = menu.addAction(tr("Preview in Media Viewer"), - this, - SLOT(OpenSelectedMediaInMediaViewerFromAction())); - preview_in_media_viewer_action->setData(reinterpret_cast(media_item)); - } - - QAction* properties_action = menu.addAction(tr("Properties...")); - QObject::connect(properties_action, SIGNAL(triggered(bool)), project_parent, SLOT(open_properties())); - } - } - - 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(); -} - -void SourcesCommon::item_click(Media *m, const QModelIndex& index) { - if (editing_item == m) { - rename_timer.start(); - } else { - editing_item = m; - editing_index = index; - } -} - -void SourcesCommon::mouseDoubleClickEvent(const QModelIndexList& selected_items) { - stop_rename_timer(); - if (selected_items.size() == 0) { - 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) { - Timeline::OpenSequence(media->to_sequence()); - } else { - OpenSelectedMediaInMediaViewer(project_parent->item_to_media(selected_items.at(0))); - } - } -} - -void SourcesCommon::dropEvent(QWidget* parent, - QDropEvent *event, - const QModelIndex& drop_item, - const QModelIndexList& items) { - const QMimeData* mimeData = event->mimeData(); - MediaPtr m = project_parent->item_to_media_ptr(drop_item); - if (mimeData->hasUrls()) { - // drag files in from outside - QList urls = mimeData->urls(); - if (!urls.isEmpty()) { - QStringList paths; - for (int i=0;iget_type() == MEDIA_TYPE_FOOTAGE - && !QFileInfo(paths.at(0)).isDir() - && 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; - replace_media(m, paths.at(0)); - } - if (!replace) { - QModelIndex parent; - if (drop_item.isValid()) { - if (m->get_type() == MEDIA_TYPE_FOLDER) { - parent = drop_item; - } else { - parent = drop_item.parent(); - } - } - olive::project_model.process_file_list(paths, false, nullptr, project_parent->item_to_media(parent)); - } - } - event->acceptProposedAction(); - } else { - event->ignore(); - - // dragging files within project - // if we dragged to the root OR dragged to a folder - if (!drop_item.isValid() || m->get_type() == MEDIA_TYPE_FOLDER) { - QVector move_items; - for (int i=0;iitem_to_media_ptr(item); - if (parent != drop_item && item != drop_item) { - bool ignore = false; - if (parent.isValid()) { - // if child belongs to a selected parent, assume the user is just moving the parent and ignore the child - QModelIndex par = parent; - while (par.isValid() && !ignore) { - for (int j=0;j 0) { - MediaMove* mm = new MediaMove(); - mm->to = m.get(); - mm->items = move_items; - olive::undo_stack.push(mm); - } - } - } -} - -void SourcesCommon::reveal_in_browser() { - Media* media = project_parent->item_to_media(selected_items.at(0)); - Footage* m = media->to_footage(); - -#if defined(Q_OS_WIN) - QStringList args; - args << "/select," << QDir::toNativeSeparators(m->url); - QProcess::startDetached("explorer", args); -#elif defined(Q_OS_MAC) - QStringList args; - args << "-e"; - args << "tell application \"Finder\""; - args << "-e"; - args << "activate"; - args << "-e"; - args << "select POSIX file \""+m->url+"\""; - args << "-e"; - args << "end tell"; - QProcess::startDetached("osascript", args); -#else - QDesktopServices::openUrl(QUrl::fromLocalFile(m->url.left(m->url.lastIndexOf('/')))); -#endif -} - -void SourcesCommon::stop_rename_timer() { - rename_timer.stop(); -} - -void SourcesCommon::rename_interval() { - stop_rename_timer(); - if (view->hasFocus() && editing_item != nullptr) { - view->edit(editing_index); - } -} - -void SourcesCommon::item_renamed(Media* item) { - if (editing_item == item) { - MediaRename* mr = new MediaRename(item, "idk"); - olive::undo_stack.push(mr); - editing_item = nullptr; - } -} - -void SourcesCommon::OpenSelectedMediaInMediaViewerFromAction() -{ - OpenSelectedMediaInMediaViewer(reinterpret_cast(static_cast(sender())->data().value())); -} - -void SourcesCommon::OpenSelectedMediaInMediaViewer(Media* item) { - if (item->get_type() != MEDIA_TYPE_FOLDER) { - panel_footage_viewer->set_media(item); - panel_footage_viewer->setFocus(); - } -} - -void SourcesCommon::open_create_proxy_dialog() { - // open the proxy dialog and send it a list of currently selected footage - ProxyDialog pd(olive::MainWindow, cached_selected_footage); - pd.exec(); -} - -void SourcesCommon::clear_proxies_from_selected() { - QList delete_list; - - for (int i=0;ito_footage(); - - if (f->proxy && !f->proxy_path.isEmpty()) { - if (QFileInfo::exists(f->proxy_path)) { - if (QMessageBox::question(olive::MainWindow, - tr("Delete proxy"), - tr("Would you like to delete the proxy file \"%1\" as well?").arg(f->proxy_path), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - delete_list.append(f->proxy_path); - } - } - } - - f->proxy = false; - f->proxy_path.clear(); - } - - QVector all_sequences = olive::project_model.GetAllSequences(); - for (int i=0;ito_sequence()->Close(); - } - - // delete proxies requested to be deleted - for (int i=0;iseq != nullptr) { - // update viewer (will re-open active clips with original media) - panel_sequence_viewer->viewer_widget()->frame_update(); - } - - olive::Global->set_modified(true); -} +/*** + + 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 "sourcescommon.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ui/menuhelper.h" +#include "panels/panels.h" +#include "project/media.h" +#include "undo/undo.h" +#include "rendering/renderfunctions.h" +#include "panels/timeline.h" +#include "panels/project.h" +#include "project/footage.h" +#include "panels/viewer.h" +#include "project/projectfilter.h" +#include "timeline/sequence.h" +#include "global/config.h" +#include "global/global.h" +#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" + +SourcesCommon::SourcesCommon(Project* parent, ProjectFilter &sort_filter) : + editing_item(nullptr), + project_parent(parent), + sort_filter_(sort_filter) +{ + rename_timer.setInterval(1000); + connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); +} + +void SourcesCommon::create_seq_from_selected() { + if (!selected_items.isEmpty()) { + QVector media_list; + for (int i=0;iitem_to_media(selected_items.at(i))); + } + + ComboAction* ca = new ComboAction(); + SequencePtr s = olive::project::CreateSequenceFromMedia(media_list); + + // add clips to it + s->AddClipsFromGhosts(ca, olive::timeline::CreateGhostsFromMedia(s.get(), 0, media_list)); + + olive::project_model.CreateSequence(ca, s, true, nullptr); + olive::undo_stack.push(ca); + } +} + +void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& items) { + Menu menu(parent); + + selected_items = items; + + QAction* import_action = menu.addAction(tr("Import...")); + 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); + olive::MenuHelper.make_new_menu(new_menu); + + Menu* view_menu = new Menu(tr("View")); + menu.addMenu(view_menu); + + QAction* tree_view_action = view_menu->addAction(tr("Tree View")); + connect(tree_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_tree_view())); + + QAction* icon_view_action = view_menu->addAction(tr("Icon View")); + connect(icon_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_icon_view())); + + QAction* toolbar_action = view_menu->addAction(tr("Show Toolbar")); + toolbar_action->setCheckable(true); + toolbar_action->setChecked(project_parent->IsToolbarVisible()); + connect(toolbar_action, SIGNAL(triggered(bool)), project_parent, SLOT(SetToolbarVisible(bool))); + + QAction* show_sequences = view_menu->addAction(tr("Show Sequences")); + show_sequences->setCheckable(true); + show_sequences->setChecked(sort_filter_.get_show_sequences()); + connect(show_sequences, SIGNAL(triggered(bool)), &sort_filter_, SLOT(set_show_sequences(bool))); + + if (items.size() > 0) { + if (items.size() == 1) { + Media* first_media = project_parent->item_to_media(items.at(0)); + + // replace footage + int type = first_media->get_type(); + if (type == MEDIA_TYPE_FOOTAGE) { + QAction* replace_action = menu.addAction(tr("Replace/Relink Media")); + QObject::connect(replace_action, SIGNAL(triggered(bool)), project_parent, SLOT(replace_selected_file())); + +#if defined(Q_OS_WIN) + QAction* reveal_in_explorer = menu.addAction(tr("Reveal in Explorer")); +#elif defined(Q_OS_MAC) + QAction* reveal_in_explorer = menu.addAction(tr("Reveal in Finder")); +#else + QAction* reveal_in_explorer = menu.addAction(tr("Reveal in File Manager")); +#endif + QObject::connect(reveal_in_explorer, SIGNAL(triggered(bool)), this, SLOT(reveal_in_browser())); + } + if (type != MEDIA_TYPE_FOLDER) { + QAction* replace_clip_media = menu.addAction(tr("Replace Clips Using This Media")); + QObject::connect(replace_clip_media, SIGNAL(triggered(bool)), project_parent, SLOT(replace_clip_media())); + } + } + + // analyze selected footage types + bool all_sequences = true; + bool all_footage = true; + + cached_selected_footage.clear(); + for (int i=0;iitem_to_media(items.at(i)); + if (m->get_type() != MEDIA_TYPE_SEQUENCE) { + all_sequences = false; + } + if (m->get_type() == MEDIA_TYPE_FOOTAGE) { + cached_selected_footage.append(m); + } else { + all_footage = false; + } + } + + // create sequence from + QAction* create_seq_from = menu.addAction(tr("Create Sequence With This Media")); + QObject::connect(create_seq_from, SIGNAL(triggered(bool)), this, SLOT(create_seq_from_selected())); + + // ONLY sequences are selected + if (all_sequences) { + // ONLY sequences are selected + QAction* duplicate_action = menu.addAction(tr("Duplicate")); + QObject::connect(duplicate_action, SIGNAL(triggered(bool)), project_parent, SLOT(duplicate_selected())); + } + + // ONLY footage is selected + if (all_footage) { + QAction* delete_footage_from_sequences = menu.addAction(tr("Delete All Clips Using This Media")); + QObject::connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), project_parent, SLOT(delete_clips_using_selected_media())); + + Menu* proxies = new Menu(tr("Proxy")); + menu.addMenu(proxies); + + // special case if one footage item is selected and its proxy is currently being generated + if (cached_selected_footage.size() == 1 + && cached_selected_footage.at(0)->to_footage()->proxy + && cached_selected_footage.at(0)->to_footage()->proxy_path.isEmpty()) { + QAction* action = proxies->addAction(tr("Generating proxy: %1% complete").arg( + olive::proxy_generator.get_proxy_progress(cached_selected_footage.at(0)) + ) + ); + action->setEnabled(false); + } else { + // determine whether any selected footage has or doesn't have proxies + bool footage_without_proxies_exists = false; + bool footage_with_proxies_exists = false; + + for (int i=0;ito_footage()->proxy) { + footage_with_proxies_exists = true; + } else { + footage_without_proxies_exists = true; + } + } + + // if footage was selected WITHOUT proxies + if (footage_without_proxies_exists) { + QString create_proxy_text; + + if (footage_with_proxies_exists) { + // some of the footage already has proxies, so we use a different string + create_proxy_text = tr("Create/Modify Proxy"); + } else { + // none of the footage has proxies + create_proxy_text = tr("Create Proxy"); + } + + proxies->addAction(create_proxy_text, this, SLOT(open_create_proxy_dialog())); + } + + // if footage was selected WITH proxies + if (footage_with_proxies_exists) { + + if (!footage_without_proxies_exists) { + // if all the footage has proxies, we didn't make a "Create/Modify" above, so we create one here (but only "modify") + proxies->addAction(tr("Modify Proxy"), this, SLOT(open_create_proxy_dialog())); + } + + proxies->addAction(tr("Restore Original"), this, SLOT(clear_proxies_from_selected())); + } + } + } + + // delete media + QAction* delete_action = menu.addAction(tr("Delete")); + QObject::connect(delete_action, SIGNAL(triggered(bool)), project_parent, SLOT(delete_selected_media())); + + if (items.size() == 1) { + Media* media_item = project_parent->item_to_media(items.at(0)); + + if (media_item->get_type() != MEDIA_TYPE_FOLDER) { + QAction* preview_in_media_viewer_action = menu.addAction(tr("Preview in Media Viewer"), + this, + SLOT(OpenSelectedMediaInMediaViewerFromAction())); + preview_in_media_viewer_action->setData(reinterpret_cast(media_item)); + } + + QAction* properties_action = menu.addAction(tr("Properties...")); + QObject::connect(properties_action, SIGNAL(triggered(bool)), project_parent, SLOT(open_properties())); + } + } + + 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(); +} + +void SourcesCommon::item_click(Media *m, const QModelIndex& index) { + if (editing_item == m) { + rename_timer.start(); + } else { + editing_item = m; + editing_index = index; + } +} + +void SourcesCommon::mouseDoubleClickEvent(const QModelIndexList& selected_items) { + stop_rename_timer(); + if (selected_items.size() == 0) { + 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) { + Timeline::OpenSequence(media->to_sequence()); + } else { + OpenSelectedMediaInMediaViewer(project_parent->item_to_media(selected_items.at(0))); + } + } +} + +void SourcesCommon::dropEvent(QWidget* parent, + QDropEvent *event, + const QModelIndex& drop_item, + const QModelIndexList& items) { + const QMimeData* mimeData = event->mimeData(); + MediaPtr m = project_parent->item_to_media_ptr(drop_item); + if (mimeData->hasUrls()) { + // drag files in from outside + QList urls = mimeData->urls(); + if (!urls.isEmpty()) { + QStringList paths; + for (int i=0;iget_type() == MEDIA_TYPE_FOOTAGE + && !QFileInfo(paths.at(0)).isDir() + && 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; + replace_media(m, paths.at(0)); + } + if (!replace) { + QModelIndex parent; + if (drop_item.isValid()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + parent = drop_item; + } else { + parent = drop_item.parent(); + } + } + olive::project_model.process_file_list(paths, false, nullptr, project_parent->item_to_media(parent)); + } + } + event->acceptProposedAction(); + } else { + event->ignore(); + + // dragging files within project + // if we dragged to the root OR dragged to a folder + if (!drop_item.isValid() || m->get_type() == MEDIA_TYPE_FOLDER) { + QVector move_items; + for (int i=0;iitem_to_media_ptr(item); + if (parent != drop_item && item != drop_item) { + bool ignore = false; + if (parent.isValid()) { + // if child belongs to a selected parent, assume the user is just moving the parent and ignore the child + QModelIndex par = parent; + while (par.isValid() && !ignore) { + for (int j=0;j 0) { + MediaMove* mm = new MediaMove(); + mm->to = m.get(); + mm->items = move_items; + olive::undo_stack.push(mm); + } + } + } +} + +void SourcesCommon::reveal_in_browser() { + Media* media = project_parent->item_to_media(selected_items.at(0)); + Footage* m = media->to_footage(); + +#if defined(Q_OS_WIN) + QStringList args; + args << "/select," << QDir::toNativeSeparators(m->url); + QProcess::startDetached("explorer", args); +#elif defined(Q_OS_MAC) + QStringList args; + args << "-e"; + args << "tell application \"Finder\""; + args << "-e"; + args << "activate"; + args << "-e"; + args << "select POSIX file \""+m->url+"\""; + args << "-e"; + args << "end tell"; + QProcess::startDetached("osascript", args); +#else + QDesktopServices::openUrl(QUrl::fromLocalFile(m->url.left(m->url.lastIndexOf('/')))); +#endif +} + +void SourcesCommon::stop_rename_timer() { + rename_timer.stop(); +} + +void SourcesCommon::rename_interval() { + stop_rename_timer(); + if (view->hasFocus() && editing_item != nullptr) { + view->edit(editing_index); + } +} + +void SourcesCommon::item_renamed(Media* item) { + if (editing_item == item) { + MediaRename* mr = new MediaRename(item, "idk"); + olive::undo_stack.push(mr); + editing_item = nullptr; + } +} + +void SourcesCommon::OpenSelectedMediaInMediaViewerFromAction() +{ + OpenSelectedMediaInMediaViewer(reinterpret_cast(static_cast(sender())->data().value())); +} + +void SourcesCommon::OpenSelectedMediaInMediaViewer(Media* item) { + if (item->get_type() != MEDIA_TYPE_FOLDER) { + panel_footage_viewer->set_media(item); + panel_footage_viewer->setFocus(); + } +} + +void SourcesCommon::open_create_proxy_dialog() { + // open the proxy dialog and send it a list of currently selected footage + ProxyDialog pd(olive::MainWindow, cached_selected_footage); + pd.exec(); +} + +void SourcesCommon::clear_proxies_from_selected() { + QList delete_list; + + for (int i=0;ito_footage(); + + if (f->proxy && !f->proxy_path.isEmpty()) { + if (QFileInfo::exists(f->proxy_path)) { + if (QMessageBox::question(olive::MainWindow, + tr("Delete proxy"), + tr("Would you like to delete the proxy file \"%1\" as well?").arg(f->proxy_path), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + delete_list.append(f->proxy_path); + } + } + } + + f->proxy = false; + f->proxy_path.clear(); + } + + QVector all_sequences = olive::project_model.GetAllSequences(); + for (int i=0;ito_sequence()->Close(); + } + + // delete proxies requested to be deleted + for (int i=0;iseq != nullptr) { + // update viewer (will re-open active clips with original media) + panel_sequence_viewer->viewer_widget()->frame_update(); + } + + olive::Global->set_modified(true); +} diff --git a/project/sourcescommon.h b/project/sourcescommon.h index b74b1165b..06e572551 100644 --- a/project/sourcescommon.h +++ b/project/sourcescommon.h @@ -1,77 +1,77 @@ -/*** - - 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 SOURCESCOMMON_H -#define SOURCESCOMMON_H - -#include -#include -#include - -#include "project/footage.h" -#include "project/projectfilter.h" -#include "media.h" - -class Project; -class QMouseEvent; -class QAbstractItemView; -class QDropEvent; - -class SourcesCommon : public QObject { - Q_OBJECT -public: - SourcesCommon(Project *parent, ProjectFilter& sort_filter); - 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); - - void item_click(Media* m, const QModelIndex &index); -public slots: - void stop_rename_timer(); -private slots: - void create_seq_from_selected(); - void reveal_in_browser(); - void rename_interval(); - void item_renamed(Media *item); - void OpenSelectedMediaInMediaViewerFromAction(); - void OpenSelectedMediaInMediaViewer(Media* item); - - // proxy functions - void open_create_proxy_dialog(); - void clear_proxies_from_selected(); -private: - Media* editing_item; - QModelIndex editing_index; - QModelIndexList selected_items; - Project* project_parent; - QTimer rename_timer; - - // we cache the selected footage items for open_create_proxy_dialog() - QVector cached_selected_footage; - - ProjectFilter& sort_filter_; -}; - -#endif // SOURCESCOMMON_H +/*** + + 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 SOURCESCOMMON_H +#define SOURCESCOMMON_H + +#include +#include +#include + +#include "project/footage.h" +#include "project/projectfilter.h" +#include "media.h" + +class Project; +class QMouseEvent; +class QAbstractItemView; +class QDropEvent; + +class SourcesCommon : public QObject { + Q_OBJECT +public: + SourcesCommon(Project *parent, ProjectFilter& sort_filter); + 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); + + void item_click(Media* m, const QModelIndex &index); +public slots: + void stop_rename_timer(); +private slots: + void create_seq_from_selected(); + void reveal_in_browser(); + void rename_interval(); + void item_renamed(Media *item); + void OpenSelectedMediaInMediaViewerFromAction(); + void OpenSelectedMediaInMediaViewer(Media* item); + + // proxy functions + void open_create_proxy_dialog(); + void clear_proxies_from_selected(); +private: + Media* editing_item; + QModelIndex editing_index; + QModelIndexList selected_items; + Project* project_parent; + QTimer rename_timer; + + // we cache the selected footage items for open_create_proxy_dialog() + QVector cached_selected_footage; + + ProjectFilter& sort_filter_; +}; + +#endif // SOURCESCOMMON_H diff --git a/rendering/audio.cpp b/rendering/audio.cpp index 7664334ac..94eaf74af 100644 --- a/rendering/audio.cpp +++ b/rendering/audio.cpp @@ -1,426 +1,426 @@ -/*** - - 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 "audio.h" - -#include "global/global.h" - -#include "timeline/sequence.h" - -#include "panels/panels.h" - -#include "global/config.h" -#include "ui/audiomonitor.h" -#include "rendering/renderfunctions.h" -#include "global/debug.h" - -#include -#include -#include -#include -#include -#include -#include - -extern "C" { -#include -} - -QAudioOutput* audio_output; -QIODevice* audio_io_device; -bool audio_device_set = false; -bool audio_scrub = false; -QMutex audio_write_lock; -QAudioInput* audio_input = nullptr; -QFile output_recording; -bool recording = false; - -int audio_rendering_rate = 0; - -float audio_ibuffer[audio_ibuffer_size]; -qint64 audio_ibuffer_read = 0; -long audio_ibuffer_frame = 0; -double audio_ibuffer_timecode = 0; - -AudioSenderThread* audio_thread = nullptr; - -bool is_audio_device_set() { - return audio_device_set; -} - -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::config.preferred_audio_output : olive::config.preferred_audio_input; - if (!preferred_device.isEmpty()) { - for (int i=0;i 0) { - return devs.at(0); - } - - // couldn't find any audio devices, return null device - return QAudioDeviceInfo(); -} - -void init_audio() { - stop_audio(); - - QAudioFormat audio_format; - audio_format.setSampleRate(olive::config.audio_rate); - audio_format.setChannelCount(2); - audio_format.setSampleSize(32); - audio_format.setCodec("audio/pcm"); - audio_format.setByteOrder(QAudioFormat::LittleEndian); - audio_format.setSampleType(QAudioFormat::Float); - - QAudioDeviceInfo info = get_audio_device(QAudio::AudioOutput); - - // see if desired format can be used by the device, use nearest if not - if (!info.isFormatSupported(audio_format)) { - qWarning() << "Audio format is not supported by backend, using nearest"; - audio_format = info.nearestFormat(audio_format); - } - - audio_output = new QAudioOutput(info, audio_format); - audio_output->moveToThread(QApplication::instance()->thread()); - audio_output->setNotifyInterval(5); - - // connect - audio_io_device = audio_output->start(); - if (audio_io_device == nullptr) { - qWarning() << "Received nullptr audio device. No compatible audio output was found."; - } else { - audio_device_set = true; - - // start sender thread - audio_thread = new AudioSenderThread(); - QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver())); - audio_thread->start(QThread::TimeCriticalPriority); - - clear_audio_ibuffer(); - } -} - -void stop_audio() { - if (audio_device_set) { - audio_thread->stop(); - - audio_output->stop(); - delete audio_output; - audio_device_set = false; - } -} - -void clear_audio_ibuffer() { - if (audio_thread != nullptr) audio_thread->lock.lock(); - audio_write_lock.lock(); - 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 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_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; - return 0; - } -} - -AudioSenderThread::AudioSenderThread() : close(false) { - connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); -} - -void AudioSenderThread::stop() { - close = true; - cond.wakeAll(); - wait(); -} - -void AudioSenderThread::notifyReceiver() { - cond.wakeAll(); -} - -void AudioSenderThread::run() { - // start data loop - send_audio_to_output(0, audio_ibuffer_size); - - lock.lock(); - while (true) { - cond.wait(&lock); - if (close) { - break; - } else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) { - - 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); - if (actual_write == max_write) { - // got all the bytes, write again - send_audio_to_output(0, audio_ibuffer_size); - } - - audio_scrub = false; - } - } - lock.unlock(); -} - -int AudioSenderThread::send_audio_to_output(qint64 offset, int max) { - // send audio to device - audio_write_lock.lock(); - - 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/sizeof(float)); - QVector averages; - averages.resize(channels); - averages.fill(0.0); - - for (qint64 i=offset;iaudio_monitor->set_value(averages); - } - - memset(&audio_ibuffer[offset], 0, actual_write); - - 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.0f)/(M_E-1.0f); -} - -void int32_to_char_array(qint32 i, char* array) { - memcpy(array, &i, 4); -} - -void write_wave_header(QFile& f, const QAudioFormat& format) { - qint32 int32bit; - char arr[4]; - - // 4 byte riff header - f.write("RIFF"); - - // 4 byte file size, filled in later - for (int i=0;i<4;i++) f.putChar(0); - - // 4 byte file type header + 4 byte format chunk marker - f.write("WAVEfmt"); - f.putChar(0x20); - - // 4 byte length of the above format data (always 16 bytes) - f.putChar(16); - for (int i=0;i<3;i++) f.putChar(0); - - // 2 byte type format (1 is PCM) - f.putChar(1); - f.putChar(0); - - // 2 byte channel count - int32bit = format.channelCount(); - int32_to_char_array(int32bit, arr); - f.write(arr, 2); - - // 4 byte integer for sample rate - int32bit = format.sampleRate(); - int32_to_char_array(int32bit, arr); - f.write(arr, 4); - - // 4 byte integer for bytes per second - int32bit = (format.sampleRate() * format.sampleSize() * format.channelCount()) / 8; - int32_to_char_array(int32bit, arr); - f.write(arr, 4); - - // 2 byte integer for bytes per sample per channel - int32bit = (format.sampleSize() * format.channelCount()) / 8; - int32_to_char_array(int32bit, arr); - f.write(arr, 2); - - // 2 byte integer for bits per sample (16) - int32bit = format.sampleSize(); - int32_to_char_array(int32bit, arr); - f.write(arr, 2); - - // data chunk header - f.write("data"); - - // 4 byte integer for data chunk size (filled in later)? - for (int i=0;i<4;i++) f.putChar(0); -} - -void write_wave_trailer(QFile& f) { - char arr[4]; - - f.seek(4); - - // 4 bytes for total file size - 8 bytes - qint32 file_size = qint32(f.size()) - 8; - int32_to_char_array(file_size, arr); - f.write(arr, 4); - - f.seek(40); - - // 4 bytes for data chunk size (file size - header) - file_size = qint32(f.size()) - 44; - int32_to_char_array(file_size, arr); - f.write(arr, 4); -} - -bool start_recording() { - if (!olive::Global->CheckForActiveSequence(true)) { - return false; - } - - QString audio_path = QCoreApplication::translate("Audio", "%1 Audio").arg(olive::ActiveProjectFilename); - QDir audio_dir(audio_path); - if (!audio_dir.exists() && !audio_dir.mkpath(".")) { - qCritical() << "Failed to create audio directory"; - return false; - } - - QString audio_file_path; - int file_number = 0; - do { - file_number++; - - QString audio_filename = QString("%1.wav").arg( - QCoreApplication::translate("Audio", "Recording %1").arg(QString::number(file_number)) - ); - - audio_file_path = audio_dir.filePath(audio_filename); - } while (QFile(audio_file_path).exists()); - - output_recording.setFileName(audio_file_path); - if (!output_recording.open(QFile::WriteOnly)) { - qCritical() << "Failed to open output file. Does Olive have permission to write to this directory?"; - return false; - } - - QAudioFormat audio_format = audio_output->format(); - if (olive::config.recording_mode != audio_format.channelCount()) { - audio_format.setChannelCount(olive::config.recording_mode); - } - - QAudioDeviceInfo info = get_audio_device(QAudio::AudioInput); - - if (!info.isFormatSupported(audio_format)) { - qWarning() << "Default format not supported, using nearest"; - audio_format = info.nearestFormat(audio_format); - } - write_wave_header(output_recording, audio_format); - audio_input = new QAudioInput(info, audio_format); - audio_input->start(&output_recording); - recording = true; - - return true; -} - -void stop_recording() { - if (recording) { - audio_input->stop(); - - write_wave_trailer(output_recording); - - output_recording.close(); - - delete audio_input; - audio_input = nullptr; - recording = false; - } -} - -QString get_recorded_audio_filename() { - return output_recording.fileName(); -} - -void combobox_audio_sample_rates(QComboBox *combobox) { - combobox->addItem("22050 Hz", 22050); - combobox->addItem("24000 Hz", 24000); - combobox->addItem("32000 Hz", 32000); - combobox->addItem("44100 Hz", 44100); - combobox->addItem("48000 Hz", 48000); - combobox->addItem("88200 Hz", 88200); - combobox->addItem("96000 Hz", 96000); -} - -QObject* audio_wake_object = nullptr; -QMutex audio_wake_mutex; - -QObject* GetAudioWakeObject() -{ - audio_wake_mutex.lock(); - - QObject* wake_object = audio_wake_object; - audio_wake_object = nullptr; - - audio_wake_mutex.unlock(); - - return wake_object; -} - -void SetAudioWakeObject(QObject *o) -{ - audio_wake_mutex.lock(); - audio_wake_object = o; - audio_wake_mutex.unlock(); -} - -void WakeAudioWakeObject() { - QObject* audio_wake_object = GetAudioWakeObject(); - - if (audio_wake_object != nullptr) { - QMetaObject::invokeMethod(audio_wake_object, "play_wake", Qt::QueuedConnection); - } -} +/*** + + 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 "audio.h" + +#include "global/global.h" + +#include "timeline/sequence.h" + +#include "panels/panels.h" + +#include "global/config.h" +#include "ui/audiomonitor.h" +#include "rendering/renderfunctions.h" +#include "global/debug.h" + +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +QAudioOutput* audio_output; +QIODevice* audio_io_device; +bool audio_device_set = false; +bool audio_scrub = false; +QMutex audio_write_lock; +QAudioInput* audio_input = nullptr; +QFile output_recording; +bool recording = false; + +int audio_rendering_rate = 0; + +float audio_ibuffer[audio_ibuffer_size]; +qint64 audio_ibuffer_read = 0; +long audio_ibuffer_frame = 0; +double audio_ibuffer_timecode = 0; + +AudioSenderThread* audio_thread = nullptr; + +bool is_audio_device_set() { + return audio_device_set; +} + +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::config.preferred_audio_output : olive::config.preferred_audio_input; + if (!preferred_device.isEmpty()) { + for (int i=0;i 0) { + return devs.at(0); + } + + // couldn't find any audio devices, return null device + return QAudioDeviceInfo(); +} + +void init_audio() { + stop_audio(); + + QAudioFormat audio_format; + audio_format.setSampleRate(olive::config.audio_rate); + audio_format.setChannelCount(2); + audio_format.setSampleSize(32); + audio_format.setCodec("audio/pcm"); + audio_format.setByteOrder(QAudioFormat::LittleEndian); + audio_format.setSampleType(QAudioFormat::Float); + + QAudioDeviceInfo info = get_audio_device(QAudio::AudioOutput); + + // see if desired format can be used by the device, use nearest if not + if (!info.isFormatSupported(audio_format)) { + qWarning() << "Audio format is not supported by backend, using nearest"; + audio_format = info.nearestFormat(audio_format); + } + + audio_output = new QAudioOutput(info, audio_format); + audio_output->moveToThread(QApplication::instance()->thread()); + audio_output->setNotifyInterval(5); + + // connect + audio_io_device = audio_output->start(); + if (audio_io_device == nullptr) { + qWarning() << "Received nullptr audio device. No compatible audio output was found."; + } else { + audio_device_set = true; + + // start sender thread + audio_thread = new AudioSenderThread(); + QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver())); + audio_thread->start(QThread::TimeCriticalPriority); + + clear_audio_ibuffer(); + } +} + +void stop_audio() { + if (audio_device_set) { + audio_thread->stop(); + + audio_output->stop(); + delete audio_output; + audio_device_set = false; + } +} + +void clear_audio_ibuffer() { + if (audio_thread != nullptr) audio_thread->lock.lock(); + audio_write_lock.lock(); + 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 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_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; + return 0; + } +} + +AudioSenderThread::AudioSenderThread() : close(false) { + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); +} + +void AudioSenderThread::stop() { + close = true; + cond.wakeAll(); + wait(); +} + +void AudioSenderThread::notifyReceiver() { + cond.wakeAll(); +} + +void AudioSenderThread::run() { + // start data loop + send_audio_to_output(0, audio_ibuffer_size); + + lock.lock(); + while (true) { + cond.wait(&lock); + if (close) { + break; + } else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) { + + 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); + if (actual_write == max_write) { + // got all the bytes, write again + send_audio_to_output(0, audio_ibuffer_size); + } + + audio_scrub = false; + } + } + lock.unlock(); +} + +int AudioSenderThread::send_audio_to_output(qint64 offset, int max) { + // send audio to device + audio_write_lock.lock(); + + 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/sizeof(float)); + QVector averages; + averages.resize(channels); + averages.fill(0.0); + + for (qint64 i=offset;iaudio_monitor->set_value(averages); + } + + memset(&audio_ibuffer[offset], 0, actual_write); + + 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.0f)/(M_E-1.0f); +} + +void int32_to_char_array(qint32 i, char* array) { + memcpy(array, &i, 4); +} + +void write_wave_header(QFile& f, const QAudioFormat& format) { + qint32 int32bit; + char arr[4]; + + // 4 byte riff header + f.write("RIFF"); + + // 4 byte file size, filled in later + for (int i=0;i<4;i++) f.putChar(0); + + // 4 byte file type header + 4 byte format chunk marker + f.write("WAVEfmt"); + f.putChar(0x20); + + // 4 byte length of the above format data (always 16 bytes) + f.putChar(16); + for (int i=0;i<3;i++) f.putChar(0); + + // 2 byte type format (1 is PCM) + f.putChar(1); + f.putChar(0); + + // 2 byte channel count + int32bit = format.channelCount(); + int32_to_char_array(int32bit, arr); + f.write(arr, 2); + + // 4 byte integer for sample rate + int32bit = format.sampleRate(); + int32_to_char_array(int32bit, arr); + f.write(arr, 4); + + // 4 byte integer for bytes per second + int32bit = (format.sampleRate() * format.sampleSize() * format.channelCount()) / 8; + int32_to_char_array(int32bit, arr); + f.write(arr, 4); + + // 2 byte integer for bytes per sample per channel + int32bit = (format.sampleSize() * format.channelCount()) / 8; + int32_to_char_array(int32bit, arr); + f.write(arr, 2); + + // 2 byte integer for bits per sample (16) + int32bit = format.sampleSize(); + int32_to_char_array(int32bit, arr); + f.write(arr, 2); + + // data chunk header + f.write("data"); + + // 4 byte integer for data chunk size (filled in later)? + for (int i=0;i<4;i++) f.putChar(0); +} + +void write_wave_trailer(QFile& f) { + char arr[4]; + + f.seek(4); + + // 4 bytes for total file size - 8 bytes + qint32 file_size = qint32(f.size()) - 8; + int32_to_char_array(file_size, arr); + f.write(arr, 4); + + f.seek(40); + + // 4 bytes for data chunk size (file size - header) + file_size = qint32(f.size()) - 44; + int32_to_char_array(file_size, arr); + f.write(arr, 4); +} + +bool start_recording() { + if (!olive::Global->CheckForActiveSequence(true)) { + return false; + } + + QString audio_path = QCoreApplication::translate("Audio", "%1 Audio").arg(olive::ActiveProjectFilename); + QDir audio_dir(audio_path); + if (!audio_dir.exists() && !audio_dir.mkpath(".")) { + qCritical() << "Failed to create audio directory"; + return false; + } + + QString audio_file_path; + int file_number = 0; + do { + file_number++; + + QString audio_filename = QString("%1.wav").arg( + QCoreApplication::translate("Audio", "Recording %1").arg(QString::number(file_number)) + ); + + audio_file_path = audio_dir.filePath(audio_filename); + } while (QFile(audio_file_path).exists()); + + output_recording.setFileName(audio_file_path); + if (!output_recording.open(QFile::WriteOnly)) { + qCritical() << "Failed to open output file. Does Olive have permission to write to this directory?"; + return false; + } + + QAudioFormat audio_format = audio_output->format(); + if (olive::config.recording_mode != audio_format.channelCount()) { + audio_format.setChannelCount(olive::config.recording_mode); + } + + QAudioDeviceInfo info = get_audio_device(QAudio::AudioInput); + + if (!info.isFormatSupported(audio_format)) { + qWarning() << "Default format not supported, using nearest"; + audio_format = info.nearestFormat(audio_format); + } + write_wave_header(output_recording, audio_format); + audio_input = new QAudioInput(info, audio_format); + audio_input->start(&output_recording); + recording = true; + + return true; +} + +void stop_recording() { + if (recording) { + audio_input->stop(); + + write_wave_trailer(output_recording); + + output_recording.close(); + + delete audio_input; + audio_input = nullptr; + recording = false; + } +} + +QString get_recorded_audio_filename() { + return output_recording.fileName(); +} + +void combobox_audio_sample_rates(QComboBox *combobox) { + combobox->addItem("22050 Hz", 22050); + combobox->addItem("24000 Hz", 24000); + combobox->addItem("32000 Hz", 32000); + combobox->addItem("44100 Hz", 44100); + combobox->addItem("48000 Hz", 48000); + combobox->addItem("88200 Hz", 88200); + combobox->addItem("96000 Hz", 96000); +} + +QObject* audio_wake_object = nullptr; +QMutex audio_wake_mutex; + +QObject* GetAudioWakeObject() +{ + audio_wake_mutex.lock(); + + QObject* wake_object = audio_wake_object; + audio_wake_object = nullptr; + + audio_wake_mutex.unlock(); + + return wake_object; +} + +void SetAudioWakeObject(QObject *o) +{ + audio_wake_mutex.lock(); + audio_wake_object = o; + audio_wake_mutex.unlock(); +} + +void WakeAudioWakeObject() { + QObject* audio_wake_object = GetAudioWakeObject(); + + if (audio_wake_object != nullptr) { + QMetaObject::invokeMethod(audio_wake_object, "play_wake", Qt::QueuedConnection); + } +} diff --git a/rendering/audio.h b/rendering/audio.h index 19b768b26..b121a3572 100644 --- a/rendering/audio.h +++ b/rendering/audio.h @@ -1,85 +1,85 @@ -/*** - - 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 AUDIO_H -#define AUDIO_H - -#include -#include -#include -#include -#include -#include -#include - -#include "timeline/sequence.h" - -class AudioSenderThread : public QThread { - Q_OBJECT -public: - AudioSenderThread(); - void run(); - void stop(); - QWaitCondition cond; - bool close; - QMutex lock; -public slots: - void notifyReceiver(); -private: - QVector samples; - int send_audio_to_output(qint64 offset, int max); -}; - -double log_volume(double linear); - -extern QAudioOutput* audio_output; -extern QIODevice* audio_io_device; -extern AudioSenderThread* audio_thread; -extern QMutex audio_write_lock; - -#define audio_ibuffer_size 192000 -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 int audio_rendering_rate; -void clear_audio_ibuffer(); - -QObject *GetAudioWakeObject(); -void SetAudioWakeObject(QObject* o); -void WakeAudioWakeObject(); - -int current_audio_freq(); - -bool is_audio_device_set(); - -void init_audio(); -void stop_audio(); -qint64 get_buffer_offset_from_frame(double framerate, long frame); - -bool start_recording(); -void stop_recording(); -QString get_recorded_audio_filename(); - -void combobox_audio_sample_rates(QComboBox* combobox); - -#endif // AUDIO_H +/*** + + 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 AUDIO_H +#define AUDIO_H + +#include +#include +#include +#include +#include +#include +#include + +#include "timeline/sequence.h" + +class AudioSenderThread : public QThread { + Q_OBJECT +public: + AudioSenderThread(); + void run(); + void stop(); + QWaitCondition cond; + bool close; + QMutex lock; +public slots: + void notifyReceiver(); +private: + QVector samples; + int send_audio_to_output(qint64 offset, int max); +}; + +double log_volume(double linear); + +extern QAudioOutput* audio_output; +extern QIODevice* audio_io_device; +extern AudioSenderThread* audio_thread; +extern QMutex audio_write_lock; + +#define audio_ibuffer_size 192000 +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 int audio_rendering_rate; +void clear_audio_ibuffer(); + +QObject *GetAudioWakeObject(); +void SetAudioWakeObject(QObject* o); +void WakeAudioWakeObject(); + +int current_audio_freq(); + +bool is_audio_device_set(); + +void init_audio(); +void stop_audio(); +qint64 get_buffer_offset_from_frame(double framerate, long frame); + +bool start_recording(); +void stop_recording(); +QString get_recorded_audio_filename(); + +void combobox_audio_sample_rates(QComboBox* combobox); + +#endif // AUDIO_H diff --git a/rendering/cacher.cpp b/rendering/cacher.cpp index 878a51da4..d3fe1bba2 100644 --- a/rendering/cacher.cpp +++ b/rendering/cacher.cpp @@ -1,1459 +1,1459 @@ -/*** - - 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 "cacher.h" - -#ifndef __STDC_FORMAT_MACROS -// For some reason the Windows AppVeyor build fails to find PRIx64 without this definition and including -// Maybe something to do with the GCC version being used? Either way, that's why it's here. -#define __STDC_FORMAT_MACROS 1 -#endif - -#include - -#include -#include -#include -#include - -#include "panels/panels.h" -#include "project/projectelements.h" -#include "rendering/audio.h" -#include "rendering/renderfunctions.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 AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_FLTP; - -double samples_to_seconds(int nb_samples, int nb_channels, int sample_rate) { - return (double(nb_samples) / double(nb_channels) / double(sample_rate)); -} - -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 + samples_to_seconds(nb_samples, frame->channels, frame->sample_rate); - - for (int j=0;jeffects.size();j++) { - OldEffectNode* e = clip->effects.at(j).get(); - if (e->IsEnabled()) { - 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->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, 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->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, reinterpret_cast(frame->data), nb_samples, nb_channels, kTransitionClosing); - } - } - } - - if (!nests.isEmpty()) { - Clip* next_nest = nests.last(); - nests.removeLast(); - apply_audio_effects(next_nest, - timecode_start + (double(clip->timeline_in(true)-clip->clip_in(true))/clip->track()->sequence()->frame_rate()), - frame, - nb_samples, - nb_channels, - nests); - } -} - -#define AUDIO_BUFFER_PADDING 2048 -void Cacher::CacheAudioWorker() { - // main thread waits until cacher starts fully, wake it up here - WakeMainThread(); - - bool audio_just_reset = false; - - // for audio clips, something may have triggered an audio reset (common if the user seeked) - if (audio_reset_) { - Reset(); - audio_reset_ = false; - audio_just_reset = true; - } - - long timeline_in = clip->timeline_in(true); - long timeline_out = clip->timeline_out(true); - long target_frame = audio_target_frame; - - bool temp_reverse = (playback_speed_ < 0); - bool reverse_audio = IsReversed(); - - long frame_skip = 0; - 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)->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)->track()->sequence()->frame_rate()); - - long validator = nests_.at(i)->timeline_in(true) - timeline_in; - if (validator > 0) { - frame_skip += validator; - //timeline_in = nests_.at(i)->timeline_in(true); - } - - last_fr = nests_.at(i)->track()->sequence()->frame_rate(); - } - } - - if (temp_reverse) { - // 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; - - long temp = timeline_in; - timeline_in = timeline_out; - timeline_out = temp; - } - - while (true) { - AVFrame* frame; - int nb_samples = INT_MAX; - - if (clip->media() == nullptr) { - frame = frame_; - 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_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)); - } - int offset = audio_ibuffer_read - audio_buffer_write; - if (offset > 0) { - audio_buffer_write += offset; - frame_sample_index_ += offset; - } - } - } else if (clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - double timebase = av_q2d(stream->time_base); - - frame = queue_.at(0); - - // retrieve frame - bool new_frame = false; - 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) { - int loop = 0; - - if (reverse_audio && !audio_just_reset) { - avcodec_flush_buffers(codecCtx); - reached_end = false; - int64_t backtrack_seek = qMax(reverse_target_ - static_cast(av_q2d(av_inv_q(stream->time_base))), - static_cast(0)); - av_seek_frame(formatCtx, stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); -#ifdef AUDIOWARNINGS - if (backtrack_seek == 0) { - dout << "backtracked to 0"; - } -#endif - } - - do { - av_frame_unref(frame); - - int ret; - - while ((ret = av_buffersink_get_frame(buffersink_ctx, frame)) == AVERROR(EAGAIN)) { - ret = RetrieveFrameFromDecoder(frame_); - if (ret >= 0) { - if ((ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame_, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - qCritical() << "Could not feed filtergraph -" << ret; - break; - } - } else { - if (ret == AVERROR_EOF) { -#ifdef AUDIOWARNINGS - dout << "reached EOF while reading"; -#endif - // TODO revise usage of reached_end in audio - if (!reverse_audio) { - reached_end = true; - } else { - } - } else { - qWarning() << "Raw audio frame data could not be retrieved." << ret; - reached_end = true; - } - break; - } - } - - if (ret < 0) { - if (ret != AVERROR_EOF) { - qCritical() << "Could not pull from filtergraph"; - reached_end = true; - break; - } else { -#ifdef AUDIOWARNINGS - dout << "reached EOF while pulling from filtergraph"; -#endif - if (!reverse_audio) break; - } - } - - if (reverse_audio) { - if (loop > 1) { - AVFrame* rev_frame = queue_.at(1); - if (ret != AVERROR_EOF) { - if (loop == 2) { -#ifdef AUDIOWARNINGS - dout << "starting rev_frame"; -#endif - rev_frame->nb_samples = 0; - rev_frame->pts = frame_->pkt_pts; - } - int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels; -#ifdef AUDIOWARNINGS - dout << "offset 1:" << offset; - dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels); -#endif - memcpy( - rev_frame->data[0]+offset, - frame->data[0], - (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) - ); -#ifdef AUDIOWARNINGS - dout << "pts:" << frame_->pts << "dur:" << frame_->pkt_duration << "rev_target:" << reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; -#endif - } - - rev_frame->nb_samples += frame->nb_samples; - - if ((frame_->pts >= reverse_target_) || (ret == AVERROR_EOF)) { - /* -#ifdef AUDIOWARNINGS - dout << "time for the end of rev cache" << rev_frame->nb_samples << clip->rev_target << frame_->pts << frame_->pkt_duration << frame_->nb_samples; - dout << "diff:" << (frame_->pkt_pts + frame_->pkt_duration) - clip->rev_target; -#endif - int cutoff = qRound64((((frame_->pkt_pts + frame_->pkt_duration) - reverse_target) * timebase) * audio_output->format().sampleRate()); - if (cutoff > 0) { -#ifdef AUDIOWARNINGS - dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")"; -#endif - rev_frame->nb_samples -= cutoff; - } -*/ - -#ifdef AUDIOWARNINGS - dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << reverse_target; -#endif - double playback_speed_ = clip->speed().value * clip->media()->to_footage()->speed; - rev_frame->nb_samples = qRound64(double(reverse_target_ - rev_frame->pts) * timebase * (current_audio_freq() / playback_speed_)); -#ifdef AUDIOWARNINGS - dout << "post cutoff deets::" << rev_frame->nb_samples; -#endif - - int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format)); - int half_frame_size = frame_size >> 1; - - int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format)); - char* temp_chars = new char[sample_size]; - for (int i=0;idata[0][i], sample_size); - - memcpy(&rev_frame->data[0][i], &rev_frame->data[0][frame_size-i-sample_size], sample_size); - - memcpy(&rev_frame->data[0][frame_size-i-sample_size], temp_chars, sample_size); - } - delete [] temp_chars; - - reverse_target_ = rev_frame->pts; - frame = rev_frame; - break; - } - } - - loop++; - -#ifdef AUDIOWARNINGS - dout << "loop" << loop; -#endif - } else { - frame->pts = frame_->pts; - break; - } - } while (true); - } else { - // if there is no more data in the file, we flush the remainder out of swresample - break; - } - - new_frame = true; - - if (frame_sample_index_ < 0) { - frame_sample_index_ = 0; - } else { - frame_sample_index_ -= nb_samples; - } - - nb_samples = frame->nb_samples; - - if (audio_just_reset) { - // get precise sample offset for the elected clip_in from this audio frame - double target_sts = playhead_to_clip_seconds(clip, audio_target_frame); - - int64_t stream_start = qMax(static_cast(0), stream->start_time); - double frame_sts = ((frame->pts - stream_start) * timebase); - - int nb_samples = qRound64((target_sts - frame_sts)*current_audio_freq()); - frame_sample_index_ = nb_samples * 4; -#ifdef AUDIOWARNINGS - 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_samples - frame_sample_index_; - audio_just_reset = false; - } - -#ifdef AUDIOWARNINGS - dout << "fsi-post-post:" << frame_sample_index; -#endif - if (audio_buffer_write == 0) { - audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); - - if (frame_skip > 0) { - int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame)); - frame_sample_index_ += (target - audio_buffer_write); - audio_buffer_write = target; - } - } - - int offset = audio_ibuffer_read - audio_buffer_write; - if (offset > 0) { - audio_buffer_write += offset; - frame_sample_index_ += offset; - } - - // try to correct negative fsi - if (frame_sample_index_ < 0) { - audio_buffer_write -= frame_sample_index_; - frame_sample_index_ = 0; - } - } - - if (reverse_audio) frame = queue_.at(1); - -#ifdef AUDIOWARNINGS - dout << "j" << frame_sample_index << nb_bytes; -#endif - - // apply any audio effects to the data - if (nb_samples == INT_MAX) { - nb_samples = frame->nb_samples; - } - if (new_frame) { - 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_); - } - } - - // mix audio into internal buffer - if (frame->nb_samples == 0) { - break; - } else { - qint64 buffer_timeline_out = get_buffer_offset_from_frame(clip->track()->sequence()->frame_rate(), timeline_out); - - audio_write_lock.lock(); - - int sample_skip = qMax(0, qAbs(playback_speed_)-1); - - 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 buffer_index = audio_buffer_write%audio_ibuffer_size; - - audio_ibuffer[buffer_index] += reinterpret_cast(frame->data[i])[frame_sample_index_]; - - audio_buffer_write++; - } - - frame_sample_index_++; - - frame_sample_index_ += sample_skip; - - if (audio_reset_) break; - } - -#ifdef AUDIOWARNINGS - if (audio_buffer_write >= buffer_timeline_out) dout << "timeline out at fsi" << frame_sample_index << "of frame ts" << frame_->pts; -#endif - - audio_write_lock.unlock(); - - if (audio_reset_) return; - - if (scrubbing_) { - if (audio_thread != nullptr) audio_thread->notifyReceiver(); - } - - if (frame_sample_index_ >= nb_samples) { - frame_sample_index_ = -1; - } else { - // assume we have no more data to send - break; - } - - // dout << "ended" << frame_sample_index << nb_bytes; - } - if (reached_end) { - frame->nb_samples = 0; - } - if (scrubbing_) { - break; - } - } - - // If there's a QObject waiting for audio to be rendered, wake it now - WakeAudioWakeObject(); -} - -bool Cacher::IsReversed() -{ - // Here, the Clip reverse and reversed playback speed cancel each other out to produce normal playback - return (clip->reversed() != playback_speed_ < 0); -} - -void Cacher::CacheVideoWorker() { - - // is this media a still image? - if (clip->media_stream()->infinite_length) { - - // for efficiency, we do slightly different things for a still image - - // if we already queued a frame, we don't actually need to cache anything, so we only retrieve a frame if not - if (queue_.size() == 0) { - - // retrieve a single frame - - // main thread waits until cacher starts fully, wake it up here - WakeMainThread(); - - AVFrame* still_image_frame; - - if (RetrieveFrameAndProcess(&still_image_frame) >= 0) { - - queue_.lock(); - queue_.append(still_image_frame); - queue_.unlock(); - - SetRetrievedFrame(still_image_frame); - } - - } - - } else { - // this media is not a still image and will require more complex caching - - // main thread waits until cacher starts fully, wake it up here - WakeMainThread(); - - // determine if this media is reversed, which will affect how the queue is constructed - bool reversed = IsReversed(); - - // get the timestamp we want in terms of the media's timebase - int64_t target_pts = seconds_to_timestamp(clip, playhead_to_clip_seconds(clip, playhead_)); - - // get the value of one second in terms of the media's timebase - int64_t second_pts = seconds_to_timestamp(clip, 1); // FIXME: possibly magic number? - - // check which range of frames we have in the queue - int64_t earliest_pts = INT64_MAX; - int64_t latest_pts = INT64_MIN; - int frames_greater_than_target = 0; - - for (int i=0;ipts); - latest_pts = qMax(latest_pts, queue_.at(i)->pts); - - // count upcoming frames - if (queue_.at(i)->pts > target_pts) { - frames_greater_than_target++; - } - } - - // If we have to seek ahead, we may want to re-use the frame we retrieved later in the pipeline. - AVFrame* decoded_frame; - bool have_existing_frame_to_use = false; - bool seeked_to_zero = false; - - // check if the frame is within this queue or if we'll have to seek elsewhere to get it - // (we check for one second of time after latest_pts, because if it's within that range it'll likely be faster to - // play up to that frame than seek to it) - if (target_pts < earliest_pts || target_pts > latest_pts + second_pts || queue_.size() == 0) { - // we need to seek to retrieve this frame - - int retrieve_code; - int64_t seek_ts = target_pts; - int64_t zero = 0; - - // Some formats don't seek reliably to the last keyframe, as a result we need to seek in a loop to ensure we - // get a frame prior to the timestamp - do { - - // if we already allocated a frame here, we'd better free it - if (have_existing_frame_to_use) { - av_frame_free(&decoded_frame); - } - - // If we already seeked to a timestamp of zero, there's no further we can go, so we have to exit the loop if so - seeked_to_zero = (seek_ts == 0); - - avcodec_flush_buffers(codecCtx); - av_seek_frame(formatCtx, clip->media_stream_index(), seek_ts, AVSEEK_FLAG_BACKWARD); - - retrieve_code = RetrieveFrameAndProcess(&decoded_frame); - - //qDebug() << "Target:" << target_pts << "Seek:" << seek_ts << "Frame:" << decoded_frame->pts; - - seek_ts = qMax(zero, seek_ts - second_pts); - - have_existing_frame_to_use = true; - } while (retrieve_code >= 0 && decoded_frame->pts > target_pts && !seeked_to_zero); - - // also we assume none of the frames in the queue are usable - queue_.lock(); - queue_.clear(); - queue_.unlock(); - - // reset upcoming frame count and latest pts for later calculations - frames_greater_than_target = 0; - latest_pts = INT64_MIN; - } - - // get values on old frames to remove from the queue - - // for FRAME_QUEUE_TYPE_SECONDS, this is used to store the maximum timestamp - // for FRAME_QUEUE_TYPE_FRAMES, this is used to store the maximum number of frames that can be added - int64_t minimum_ts; - - // check if we can add more frames to this queue or not - - // for FRAME_QUEUE_TYPE_SECONDS, this is used to store the maximum timestamp - // for FRAME_QUEUE_TYPE_FRAMES, this is used to store the maximum number of frames that can be added - int64_t maximum_ts; - - // Get queue configuration - int previous_queue_type, upcoming_queue_type; - double previous_queue_size, upcoming_queue_size; - - // 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::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::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 - if (previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { - // get the maximum number of previous frames that can be in the queue - minimum_ts = qCeil(previous_queue_size); - } else { - // get the minimum frame timestamp that can be added to the queue - minimum_ts = qRound(target_pts - second_pts * previous_queue_size); - } - - // Determine "upcoming" queue statistics - if (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { - maximum_ts = qCeil(upcoming_queue_size); - } else { - // get the maximum frame timestamp that can be added to the queue - maximum_ts = qRound(target_pts + second_pts * upcoming_queue_size); - } - - // if we already have the maximum number of upcoming frames, don't bother running the retrieving any frames at all - bool start_loop = true; - if ((upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES && frames_greater_than_target >= maximum_ts) - || (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS && latest_pts > maximum_ts)) { - start_loop = false; - } - - - if (start_loop) { - - interrupt_ = false; - do { - - // retrieve raw RGBA frame from decoder + filter stack - int retrieve_code = 0; - - // if we retrieved a perfectly good frame earlier by checking the seek, use that here - if (!have_existing_frame_to_use) { - retrieve_code = RetrieveFrameAndProcess(&decoded_frame); - } else { - have_existing_frame_to_use = false; - } - - if (retrieve_code < 0 && retrieve_code != AVERROR_EOF) { - - // for some reason we were unable to retrieve a frame, likely a decoder error so we report it - // again, an EOF isn't an "error" but will how we add frames (see below) - - qCritical() << "Failed to retrieve frame from buffersink." << retrieve_code; - break; - - } else if (decoded_frame->pts != AV_NOPTS_VALUE) { - - // check if this frame exceeds the minimum timestamp - if (previous_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS - && decoded_frame->pts < minimum_ts) { - - // if so, we don't need it - av_frame_free(&decoded_frame); - - } else { - - if (retrieved_frame == nullptr) { - if (decoded_frame->pts == target_pts) { - - // We retrieved the exact frame we're looking for - - SetRetrievedFrame(decoded_frame); - - } else if (decoded_frame->pts > target_pts) { - - if (queue_.size() > 0) { - - SetRetrievedFrame(queue_.last()); - - } else if (seeked_to_zero) { - - // If this flag is set but we still got a frame after the target timestamp, it means this was somehow - // the earliest frame we could get - SetRetrievedFrame(decoded_frame); - seeked_to_zero = false; - - } - - } - } - - // add the frame to the queue - queue_.lock(); - queue_.append(decoded_frame); - queue_.unlock(); - - // check the amount of previous frames in the queue by using the current queue size for if we need to - // remove any old entries (assumes the queue is chronological) - if (previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { - - int previous_frame_count = 0; - - if (decoded_frame->pts < target_pts) { - // if this frame is before the target frame, make sure we don't add too many of them - previous_frame_count = queue_.size(); - } else { - // if this frame is after the target frame, clean up any previous frames before it - // TODO is there a faster way to do this? - - for (int i=0;ipts > target_pts) { - break; - } else { - previous_frame_count++; - } - } - - } - - // remove frames while the amount of previous frames exceeds the maximum - while (previous_frame_count > minimum_ts) { - queue_.lock(); - queue_.removeFirst(); - queue_.unlock(); - previous_frame_count--; - } - - } - - // check if the queue is full according to olive::CurrentConfig - if (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { - - // if this frame is later than the target, it's an "upcoming" frame - if (decoded_frame->pts > target_pts) { - - // we started a count of upcoming frames above, we can continue it here - frames_greater_than_target++; - - // compare upcoming frame count with maximum upcoming frames (maximum_ts) - if (frames_greater_than_target >= maximum_ts) { - break; - } - } - - } else if (decoded_frame->pts > maximum_ts) { // for `upcoming_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS` - break; - } - - } - - - - } else { - - // if a frame has no timestamp (pts == AV_NOPTS_VALUE), we assume it's an invalid frame and don't use it - - qWarning() << clip->name() << "frame had no PTS value"; - av_frame_free(&decoded_frame); - - if (retrieve_code == AVERROR_EOF && retrieved_frame == nullptr && !queue_.isEmpty()) { - // if we reached the end of the file, it's not an error but there are no more frames to retrieve - // some formats EOF before the end of the duration that Olive calculates. In this event, we simply - // return the last frame we retrieved - // - // TODO: Check duration formula - - SetRetrievedFrame(queue_.last()); - - } else { - - SetRetrievedFrame(nullptr); - - } - - break; - - } - } while (!interrupt_); - - } - - } - - // For some reason we couldn't get the frame, we should wake up the RenderThread anyway - if (retrieved_frame == nullptr) { - qCritical() << "Couldn't retrieve an appropriate frame. This is an error and may mean this media is corrupt."; - SetRetrievedFrame(nullptr); - } -} - -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->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_; - frame_sample_index_ = -1; - frame_->pts = 0; - } - } else { - - const FootageStream* ms = clip->media_stream(); - if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - // flush ffmpeg codecs - avcodec_flush_buffers(codecCtx); - reached_end = false; - - // seek (target_frame represents timeline timecode in frames, not clip timecode) - - int64_t timestamp = qRound64(playhead_to_clip_seconds(clip, playhead_) / av_q2d(stream->time_base)); - - bool temp_reverse = (playback_speed_ < 0); - if (clip->reversed() != temp_reverse) { - reverse_target_ = timestamp; - timestamp -= av_q2d(av_inv_q(stream->time_base)); -#ifdef AUDIOWARNINGS - dout << "seeking to" << timestamp << "(originally" << reverse_target << ")"; - } else { - dout << "reset called; seeking to" << timestamp; -#endif - } - av_seek_frame(formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD); - audio_target_frame = playhead_; - frame_sample_index_ = -1; - } - } -} - -void Cacher::SetRetrievedFrame(AVFrame *f) -{ - if (retrieved_frame == nullptr) { - retrieve_lock_.lock(); - retrieved_frame = f; - retrieve_wait_.wakeAll(); - retrieve_lock_.unlock(); - } -} - -void Cacher::WakeMainThread() -{ - main_thread_lock_.lock(); - main_thread_wait_.wakeAll(); - main_thread_lock_.unlock(); -} - -Cacher::Cacher(Clip* c) : - clip(c), - frame_(nullptr), - pkt(nullptr), - formatCtx(nullptr), - opts(nullptr), - filter_graph(nullptr), - codecCtx(nullptr), - is_valid_state_(false) -{} - -void Cacher::OpenWorker() { - // set some defaults for the audio cacher - if (clip->type() == olive::kTypeAudio) { - audio_reset_ = false; - frame_sample_index_ = -1; - audio_buffer_write = 0; - } - reached_end = false; - - if (clip->media() == nullptr) { - if (clip->type() == olive::kTypeAudio) { - frame_ = av_frame_alloc(); - frame_->format = kDestSampleFmt; - 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; - av_frame_make_writable(frame_); - if (av_frame_get_buffer(frame_, 0)) { - qCritical() << "Could not allocate buffer for tone clip"; - } - audio_reset_ = true; - } - } else if (clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - // opens file resource for FFmpeg and prepares Clip struct for playback - Footage* m = clip->media()->to_footage(); - - // byte array for retrieving raw bytes from QString URL - QByteArray ba; - - // do we have a 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(); - } else { - ba = m->url.toUtf8(); - } - - const char* filename = ba.constData(); - const FootageStream* ms = clip->media_stream(); - - // for image sequences that don't start at 0, set the index where it does start - AVDictionary* format_opts = nullptr; - if (m->start_number > 0) { - av_dict_set(&format_opts, "start_number", QString::number(m->start_number).toUtf8(), 0); - } - - formatCtx = nullptr; - int errCode = avformat_open_input( - &formatCtx, - filename, - nullptr, - &format_opts - ); - if (errCode != 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - qCritical() << "Could not open" << filename << "-" << err; - olive::MainWindow->statusBar()->showMessage(tr("Could not open %1 - %2").arg(filename, err)); - return; - } - - errCode = avformat_find_stream_info(formatCtx, nullptr); - if (errCode < 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - qCritical() << "Could not open" << filename << "-" << err; - olive::MainWindow->statusBar()->showMessage(tr("Could not open %1 - %2").arg(filename, err)); - return; - } - - av_dump_format(formatCtx, 0, filename, 0); - - stream = formatCtx->streams[ms->file_index]; - codec = avcodec_find_decoder(stream->codecpar->codec_id); - codecCtx = avcodec_alloc_context3(codec); - avcodec_parameters_to_context(codecCtx, stream->codecpar); - - opts = nullptr; - - // enable multithreading on decoding - av_dict_set(&opts, "threads", "auto", 0); - - // enable extra optimization code on h264 (not even sure if they help) - if (stream->codecpar->codec_id == AV_CODEC_ID_H264) { - av_dict_set(&opts, "tune", "fastdecode", 0); - av_dict_set(&opts, "tune", "zerolatency", 0); - } - - // Open codec - if (avcodec_open2(codecCtx, codec, &opts) < 0) { - qCritical() << "Could not open codec"; - } - - // allocate filtergraph - filter_graph = avfilter_graph_alloc(); - if (filter_graph == nullptr) { - qCritical() << "Could not create filtergraph"; - } - char filter_args[512]; - - if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - stream->codecpar->width, - stream->codecpar->height, - stream->codecpar->format, - stream->time_base.num, - stream->time_base.den, - stream->codecpar->sample_aspect_ratio.num, - stream->codecpar->sample_aspect_ratio.den - ); - - avfilter_graph_create_filter(&buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, filter_graph); - avfilter_graph_create_filter(&buffersink_ctx, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, filter_graph); - - AVFilterContext* last_filter = buffersrc_ctx; - - char filter_args[100]; - - if (ms->video_interlacing != VIDEO_PROGRESSIVE) { - AVFilterContext* yadif_filter; - snprintf(filter_args, sizeof(filter_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, filter_graph); - - avfilter_link(last_filter, 0, yadif_filter, 0); - last_filter = yadif_filter; - } - - 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; - avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", filter_args, nullptr, filter_graph); - avfilter_link(last_filter, 0, format_conv, 0); - - avfilter_link(format_conv, 0, buffersink_ctx, 0); - - avfilter_graph_config(filter_graph, nullptr); - - } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - if (codecCtx->channel_layout == 0) codecCtx->channel_layout = av_get_default_channel_layout(stream->codecpar->channels); - - // set up cache - queue_.append(av_frame_alloc()); - - if (true) { - AVFrame* reverse_frame = av_frame_alloc(); - - reverse_frame->format = kDestSampleFmt; - reverse_frame->nb_samples = current_audio_freq()*10; - 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); - } - - snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64, - stream->time_base.num, - stream->time_base.den, - stream->codecpar->sample_rate, - av_get_sample_fmt_name(codecCtx->sample_fmt), - codecCtx->channel_layout - ); - - avfilter_graph_create_filter(&buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph); - avfilter_graph_create_filter(&buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph); - - enum AVSampleFormat sample_fmts[] = { kDestSampleFmt, static_cast(-1) }; - if (av_opt_set_int_list(buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { - qCritical() << "Could not set output sample format"; - } - - int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) }; - if (av_opt_set_int_list(buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { - qCritical() << "Could not set output sample format"; - } - - int target_sample_rate = current_audio_freq(); - - double playback_speed_ = clip->speed().value * m->speed; - - if (qFuzzyCompare(playback_speed_, 1.0)) { - avfilter_link(buffersrc_ctx, 0, buffersink_ctx, 0); - } else if (clip->speed().maintain_audio_pitch) { - AVFilterContext* previous_filter = buffersrc_ctx; - AVFilterContext* last_filter = buffersrc_ctx; - - char speed_param[10]; - - double base = (playback_speed_ > 1.0) ? 2.0 : 0.5; - - double speedlog = log(playback_speed_) / log(base); - int whole2 = qFloor(speedlog); - speedlog -= whole2; - - if (whole2 > 0) { - snprintf(speed_param, sizeof(speed_param), "%f", base); - for (int i=0;itrack(); - - is_valid_state_ = true; -} - -void Cacher::CacheWorker() { - if (clip->type() == olive::kTypeVideo) { - // clip is a video track, start caching video - CacheVideoWorker(); - } else { - // clip is audio - CacheAudioWorker(); - } -} - -void Cacher::CloseWorker() { - retrieved_frame = nullptr; - queue_.lock(); - queue_.clear(); - queue_.unlock(); - - if (frame_ != nullptr) { - av_frame_free(&frame_); - frame_ = nullptr; - } - - if (pkt != nullptr) { - av_packet_free(&pkt); - pkt = nullptr; - } - - if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - if (filter_graph != nullptr) { - avfilter_graph_free(&filter_graph); - filter_graph = nullptr; - } - - if (codecCtx != nullptr) { - avcodec_close(codecCtx); - avcodec_free_context(&codecCtx); - codecCtx = nullptr; - } - - if (opts != nullptr) { - av_dict_free(&opts); - } - - // protection for get_timebase() - stream = nullptr; - - if (formatCtx != nullptr) { - avformat_close_input(&formatCtx); - } - } - - qInfo() << "Clip closed on track" << clip->track(); -} - -void Cacher::run() { - clip->cache_lock.lock(); - - OpenWorker(); - - clip->state_change_lock.unlock(); - - while (caching_) { - if (!queued_) { - wait_cond_.wait(&clip->cache_lock); - } - queued_ = false; - if (!caching_) { - break; - } else if (is_valid_state_) { - CacheWorker(); - } else { - // main thread waits until cacher starts fully, but the cacher can't run, so we just wake it up here - WakeMainThread(); - } - } - - is_valid_state_ = false; - - CloseWorker(); - - clip->state_change_lock.unlock(); - - clip->cache_lock.unlock(); -} - -void Cacher::Open() -{ - wait(); - - // set variable defaults for caching - caching_ = true; - queued_ = false; - - start((clip->type() == olive::kTypeVideo) ? QThread::HighPriority : QThread::TimeCriticalPriority); -} - -void Cacher::Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed) -{ - - if (!is_valid_state_) { - return; - } - - if (clip->media_stream() != nullptr - && queue_.size() > 0 - && clip->media_stream()->infinite_length) { - retrieved_frame = queue_.at(0); - return; - } - - playhead_ = playhead; - nests_ = nests; - scrubbing_ = scrubbing; - playback_speed_ = playback_speed; - queued_ = true; - - bool wait_for_cacher_to_respond = true; - - if (clip->media() != nullptr) { - // see if we already have this frame - retrieve_lock_.lock(); - queue_.lock(); - retrieved_frame = nullptr; - int64_t target_pts = seconds_to_timestamp(clip, playhead_to_clip_seconds(clip, playhead_)); - for (int i=0;ipts == target_pts) { - - // the queue has a frame with the exact timestamp - - retrieved_frame = queue_.at(i); - wait_for_cacher_to_respond = false; - break; - } else if (i > 0 && queue_.at(i-1)->pts < target_pts && queue_.at(i)->pts > target_pts) { - - // the queue has a frame with a close timestamp that we'll assume is different due to a rounding error - - retrieved_frame = queue_.at(i-1); - wait_for_cacher_to_respond = false; - break; - } - } - queue_.unlock(); - retrieve_lock_.unlock(); - } - - if (wait_for_cacher_to_respond) { - main_thread_lock_.lock(); - } - - // wake up cacher - wait_cond_.wakeAll(); - - // if not, wait for cacher to respond - if (wait_for_cacher_to_respond) { - interrupt_ = true; - main_thread_wait_.wait(&main_thread_lock_, 2000); - } - - if (wait_for_cacher_to_respond) { - main_thread_lock_.unlock(); - } -} - -AVFrame *Cacher::Retrieve() -{ - if (!caching_) { - return nullptr; - } - - // for thread-safety, we lock a mutex to ensure this thread is never woken by anything out of sync - - retrieve_lock_.lock(); - - // check if there's a frame ready to be shown by the cacher - - if (retrieved_frame == nullptr) { - // wait for cacher to finish caching - - - if (clip->cache_lock.tryLock()) { - - // If the queue could lock, the cacher isn't running which means no frame is coming. This is an error. - qCritical() << "Cacher frame was null while the cacher wasn't running on clip" << clip->name(); - clip->cache_lock.unlock(); - - } else { - - // cacher is running, wait for it to give a frame - retrieve_wait_.wait(&retrieve_lock_); - - } - - } - - retrieve_lock_.unlock(); - - return retrieved_frame; -} - -void Cacher::Close(bool wait_for_finish) -{ - caching_ = false; - wait_cond_.wakeAll(); - - if (wait_for_finish) { - wait(); - } -} - -void Cacher::ResetAudio() -{ - // using audio_write_lock seems like a good idea, but hasn't been tested yet. If there are audio issues when seeking, - // try uncommenting them - -// audio_write_lock.lock(); - audio_reset_ = true; - frame_sample_index_ = -1; - audio_buffer_write = 0; -// audio_write_lock.unlock(); -} - -int Cacher::media_width() -{ - return stream->codecpar->width; -} - -int Cacher::media_height() -{ - return stream->codecpar->height; -} - -AVRational Cacher::media_time_base() -{ - return stream->time_base; -} - -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; - - // do we need to retrieve a new packet for a new frame? - av_frame_unref(f); - while ((receive_ret = avcodec_receive_frame(codecCtx, f)) == AVERROR(EAGAIN)) { - int read_ret = 0; - do { - if (pkt->buf != nullptr) { - av_packet_unref(pkt); - } - read_ret = av_read_frame(formatCtx, pkt); - } while (read_ret >= 0 && pkt->stream_index != clip->media_stream_index()); - - if (read_ret >= 0) { - int send_ret = avcodec_send_packet(codecCtx, pkt); - if (send_ret < 0) { - qCritical() << "Failed to send packet to decoder." << send_ret; - return send_ret; - } - } else { - if (read_ret == AVERROR_EOF) { - int send_ret = avcodec_send_packet(codecCtx, nullptr); - if (send_ret < 0) { - qCritical() << "Failed to send packet to decoder." << send_ret; - return send_ret; - } - } else { - qCritical() << "Could not read frame." << read_ret; - return read_ret; // skips trying to find a frame at all - } - } - } - if (receive_ret < 0) { - if (receive_ret != AVERROR_EOF) qCritical() << "Failed to receive packet from decoder." << receive_ret; - result = receive_ret; - } - - return result; -} - -int Cacher::RetrieveFrameAndProcess(AVFrame **f) -{ - // error codes from FFmpeg - int retrieve_code, read_code, send_code; - - // frame for FFmpeg to decode into - *f = av_frame_alloc(); - - // loop to pull frames from the AVFilter stack - while ((retrieve_code = av_buffersink_get_frame(buffersink_ctx, *f)) == AVERROR(EAGAIN)) { - - // retrieve frame from decoder - read_code = RetrieveFrameFromDecoder(frame_); - - if (read_code >= 0) { - - // we retrieved a decoded video frame, which we will send to the AVFilter stack to convert to RGBA (with other - // adjustments if necessary) - - if ((send_code = av_buffersrc_add_frame_flags(buffersrc_ctx, frame_, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - qCritical() << "Failed to add frame to buffer source." << send_code; - break; - } - - // we don't need the original frame to we free it here - av_frame_unref(frame_); - - } else { - - // AVERROR_EOF means we've reached the end of the file, not technically an error, but it's useful to know that - // there are no more frames in this file - if (read_code != AVERROR_EOF) { - qCritical() << "Failed to read frame." << read_code; - } - break; - } - } - - if (read_code == AVERROR_EOF) { - return AVERROR_EOF; - } - return retrieve_code; -} +/*** + + 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 "cacher.h" + +#ifndef __STDC_FORMAT_MACROS +// For some reason the Windows AppVeyor build fails to find PRIx64 without this definition and including +// Maybe something to do with the GCC version being used? Either way, that's why it's here. +#define __STDC_FORMAT_MACROS 1 +#endif + +#include + +#include +#include +#include +#include + +#include "panels/panels.h" +#include "project/projectelements.h" +#include "rendering/audio.h" +#include "rendering/renderfunctions.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 AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_FLTP; + +double samples_to_seconds(int nb_samples, int nb_channels, int sample_rate) { + return (double(nb_samples) / double(nb_channels) / double(sample_rate)); +} + +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 + samples_to_seconds(nb_samples, frame->channels, frame->sample_rate); + + for (int j=0;jeffects.size();j++) { + OldEffectNode* e = clip->effects.at(j).get(); + if (e->IsEnabled()) { + 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->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, 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->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, reinterpret_cast(frame->data), nb_samples, nb_channels, kTransitionClosing); + } + } + } + + if (!nests.isEmpty()) { + Clip* next_nest = nests.last(); + nests.removeLast(); + apply_audio_effects(next_nest, + timecode_start + (double(clip->timeline_in(true)-clip->clip_in(true))/clip->track()->sequence()->frame_rate()), + frame, + nb_samples, + nb_channels, + nests); + } +} + +#define AUDIO_BUFFER_PADDING 2048 +void Cacher::CacheAudioWorker() { + // main thread waits until cacher starts fully, wake it up here + WakeMainThread(); + + bool audio_just_reset = false; + + // for audio clips, something may have triggered an audio reset (common if the user seeked) + if (audio_reset_) { + Reset(); + audio_reset_ = false; + audio_just_reset = true; + } + + long timeline_in = clip->timeline_in(true); + long timeline_out = clip->timeline_out(true); + long target_frame = audio_target_frame; + + bool temp_reverse = (playback_speed_ < 0); + bool reverse_audio = IsReversed(); + + long frame_skip = 0; + 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)->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)->track()->sequence()->frame_rate()); + + long validator = nests_.at(i)->timeline_in(true) - timeline_in; + if (validator > 0) { + frame_skip += validator; + //timeline_in = nests_.at(i)->timeline_in(true); + } + + last_fr = nests_.at(i)->track()->sequence()->frame_rate(); + } + } + + if (temp_reverse) { + // 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; + + long temp = timeline_in; + timeline_in = timeline_out; + timeline_out = temp; + } + + while (true) { + AVFrame* frame; + int nb_samples = INT_MAX; + + if (clip->media() == nullptr) { + frame = frame_; + 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_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)); + } + int offset = audio_ibuffer_read - audio_buffer_write; + if (offset > 0) { + audio_buffer_write += offset; + frame_sample_index_ += offset; + } + } + } else if (clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + double timebase = av_q2d(stream->time_base); + + frame = queue_.at(0); + + // retrieve frame + bool new_frame = false; + 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) { + int loop = 0; + + if (reverse_audio && !audio_just_reset) { + avcodec_flush_buffers(codecCtx); + reached_end = false; + int64_t backtrack_seek = qMax(reverse_target_ - static_cast(av_q2d(av_inv_q(stream->time_base))), + static_cast(0)); + av_seek_frame(formatCtx, stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); +#ifdef AUDIOWARNINGS + if (backtrack_seek == 0) { + dout << "backtracked to 0"; + } +#endif + } + + do { + av_frame_unref(frame); + + int ret; + + while ((ret = av_buffersink_get_frame(buffersink_ctx, frame)) == AVERROR(EAGAIN)) { + ret = RetrieveFrameFromDecoder(frame_); + if (ret >= 0) { + if ((ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame_, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + qCritical() << "Could not feed filtergraph -" << ret; + break; + } + } else { + if (ret == AVERROR_EOF) { +#ifdef AUDIOWARNINGS + dout << "reached EOF while reading"; +#endif + // TODO revise usage of reached_end in audio + if (!reverse_audio) { + reached_end = true; + } else { + } + } else { + qWarning() << "Raw audio frame data could not be retrieved." << ret; + reached_end = true; + } + break; + } + } + + if (ret < 0) { + if (ret != AVERROR_EOF) { + qCritical() << "Could not pull from filtergraph"; + reached_end = true; + break; + } else { +#ifdef AUDIOWARNINGS + dout << "reached EOF while pulling from filtergraph"; +#endif + if (!reverse_audio) break; + } + } + + if (reverse_audio) { + if (loop > 1) { + AVFrame* rev_frame = queue_.at(1); + if (ret != AVERROR_EOF) { + if (loop == 2) { +#ifdef AUDIOWARNINGS + dout << "starting rev_frame"; +#endif + rev_frame->nb_samples = 0; + rev_frame->pts = frame_->pkt_pts; + } + int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels; +#ifdef AUDIOWARNINGS + dout << "offset 1:" << offset; + dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels); +#endif + memcpy( + rev_frame->data[0]+offset, + frame->data[0], + (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) + ); +#ifdef AUDIOWARNINGS + dout << "pts:" << frame_->pts << "dur:" << frame_->pkt_duration << "rev_target:" << reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; +#endif + } + + rev_frame->nb_samples += frame->nb_samples; + + if ((frame_->pts >= reverse_target_) || (ret == AVERROR_EOF)) { + /* +#ifdef AUDIOWARNINGS + dout << "time for the end of rev cache" << rev_frame->nb_samples << clip->rev_target << frame_->pts << frame_->pkt_duration << frame_->nb_samples; + dout << "diff:" << (frame_->pkt_pts + frame_->pkt_duration) - clip->rev_target; +#endif + int cutoff = qRound64((((frame_->pkt_pts + frame_->pkt_duration) - reverse_target) * timebase) * audio_output->format().sampleRate()); + if (cutoff > 0) { +#ifdef AUDIOWARNINGS + dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")"; +#endif + rev_frame->nb_samples -= cutoff; + } +*/ + +#ifdef AUDIOWARNINGS + dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << reverse_target; +#endif + double playback_speed_ = clip->speed().value * clip->media()->to_footage()->speed; + rev_frame->nb_samples = qRound64(double(reverse_target_ - rev_frame->pts) * timebase * (current_audio_freq() / playback_speed_)); +#ifdef AUDIOWARNINGS + dout << "post cutoff deets::" << rev_frame->nb_samples; +#endif + + int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format)); + int half_frame_size = frame_size >> 1; + + int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format)); + char* temp_chars = new char[sample_size]; + for (int i=0;idata[0][i], sample_size); + + memcpy(&rev_frame->data[0][i], &rev_frame->data[0][frame_size-i-sample_size], sample_size); + + memcpy(&rev_frame->data[0][frame_size-i-sample_size], temp_chars, sample_size); + } + delete [] temp_chars; + + reverse_target_ = rev_frame->pts; + frame = rev_frame; + break; + } + } + + loop++; + +#ifdef AUDIOWARNINGS + dout << "loop" << loop; +#endif + } else { + frame->pts = frame_->pts; + break; + } + } while (true); + } else { + // if there is no more data in the file, we flush the remainder out of swresample + break; + } + + new_frame = true; + + if (frame_sample_index_ < 0) { + frame_sample_index_ = 0; + } else { + frame_sample_index_ -= nb_samples; + } + + nb_samples = frame->nb_samples; + + if (audio_just_reset) { + // get precise sample offset for the elected clip_in from this audio frame + double target_sts = playhead_to_clip_seconds(clip, audio_target_frame); + + int64_t stream_start = qMax(static_cast(0), stream->start_time); + double frame_sts = ((frame->pts - stream_start) * timebase); + + int nb_samples = qRound64((target_sts - frame_sts)*current_audio_freq()); + frame_sample_index_ = nb_samples * 4; +#ifdef AUDIOWARNINGS + 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_samples - frame_sample_index_; + audio_just_reset = false; + } + +#ifdef AUDIOWARNINGS + dout << "fsi-post-post:" << frame_sample_index; +#endif + if (audio_buffer_write == 0) { + audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); + + if (frame_skip > 0) { + int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame)); + frame_sample_index_ += (target - audio_buffer_write); + audio_buffer_write = target; + } + } + + int offset = audio_ibuffer_read - audio_buffer_write; + if (offset > 0) { + audio_buffer_write += offset; + frame_sample_index_ += offset; + } + + // try to correct negative fsi + if (frame_sample_index_ < 0) { + audio_buffer_write -= frame_sample_index_; + frame_sample_index_ = 0; + } + } + + if (reverse_audio) frame = queue_.at(1); + +#ifdef AUDIOWARNINGS + dout << "j" << frame_sample_index << nb_bytes; +#endif + + // apply any audio effects to the data + if (nb_samples == INT_MAX) { + nb_samples = frame->nb_samples; + } + if (new_frame) { + 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_); + } + } + + // mix audio into internal buffer + if (frame->nb_samples == 0) { + break; + } else { + qint64 buffer_timeline_out = get_buffer_offset_from_frame(clip->track()->sequence()->frame_rate(), timeline_out); + + audio_write_lock.lock(); + + int sample_skip = qMax(0, qAbs(playback_speed_)-1); + + 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 buffer_index = audio_buffer_write%audio_ibuffer_size; + + audio_ibuffer[buffer_index] += reinterpret_cast(frame->data[i])[frame_sample_index_]; + + audio_buffer_write++; + } + + frame_sample_index_++; + + frame_sample_index_ += sample_skip; + + if (audio_reset_) break; + } + +#ifdef AUDIOWARNINGS + if (audio_buffer_write >= buffer_timeline_out) dout << "timeline out at fsi" << frame_sample_index << "of frame ts" << frame_->pts; +#endif + + audio_write_lock.unlock(); + + if (audio_reset_) return; + + if (scrubbing_) { + if (audio_thread != nullptr) audio_thread->notifyReceiver(); + } + + if (frame_sample_index_ >= nb_samples) { + frame_sample_index_ = -1; + } else { + // assume we have no more data to send + break; + } + + // dout << "ended" << frame_sample_index << nb_bytes; + } + if (reached_end) { + frame->nb_samples = 0; + } + if (scrubbing_) { + break; + } + } + + // If there's a QObject waiting for audio to be rendered, wake it now + WakeAudioWakeObject(); +} + +bool Cacher::IsReversed() +{ + // Here, the Clip reverse and reversed playback speed cancel each other out to produce normal playback + return (clip->reversed() != playback_speed_ < 0); +} + +void Cacher::CacheVideoWorker() { + + // is this media a still image? + if (clip->media_stream()->infinite_length) { + + // for efficiency, we do slightly different things for a still image + + // if we already queued a frame, we don't actually need to cache anything, so we only retrieve a frame if not + if (queue_.size() == 0) { + + // retrieve a single frame + + // main thread waits until cacher starts fully, wake it up here + WakeMainThread(); + + AVFrame* still_image_frame; + + if (RetrieveFrameAndProcess(&still_image_frame) >= 0) { + + queue_.lock(); + queue_.append(still_image_frame); + queue_.unlock(); + + SetRetrievedFrame(still_image_frame); + } + + } + + } else { + // this media is not a still image and will require more complex caching + + // main thread waits until cacher starts fully, wake it up here + WakeMainThread(); + + // determine if this media is reversed, which will affect how the queue is constructed + bool reversed = IsReversed(); + + // get the timestamp we want in terms of the media's timebase + int64_t target_pts = seconds_to_timestamp(clip, playhead_to_clip_seconds(clip, playhead_)); + + // get the value of one second in terms of the media's timebase + int64_t second_pts = seconds_to_timestamp(clip, 1); // FIXME: possibly magic number? + + // check which range of frames we have in the queue + int64_t earliest_pts = INT64_MAX; + int64_t latest_pts = INT64_MIN; + int frames_greater_than_target = 0; + + for (int i=0;ipts); + latest_pts = qMax(latest_pts, queue_.at(i)->pts); + + // count upcoming frames + if (queue_.at(i)->pts > target_pts) { + frames_greater_than_target++; + } + } + + // If we have to seek ahead, we may want to re-use the frame we retrieved later in the pipeline. + AVFrame* decoded_frame; + bool have_existing_frame_to_use = false; + bool seeked_to_zero = false; + + // check if the frame is within this queue or if we'll have to seek elsewhere to get it + // (we check for one second of time after latest_pts, because if it's within that range it'll likely be faster to + // play up to that frame than seek to it) + if (target_pts < earliest_pts || target_pts > latest_pts + second_pts || queue_.size() == 0) { + // we need to seek to retrieve this frame + + int retrieve_code; + int64_t seek_ts = target_pts; + int64_t zero = 0; + + // Some formats don't seek reliably to the last keyframe, as a result we need to seek in a loop to ensure we + // get a frame prior to the timestamp + do { + + // if we already allocated a frame here, we'd better free it + if (have_existing_frame_to_use) { + av_frame_free(&decoded_frame); + } + + // If we already seeked to a timestamp of zero, there's no further we can go, so we have to exit the loop if so + seeked_to_zero = (seek_ts == 0); + + avcodec_flush_buffers(codecCtx); + av_seek_frame(formatCtx, clip->media_stream_index(), seek_ts, AVSEEK_FLAG_BACKWARD); + + retrieve_code = RetrieveFrameAndProcess(&decoded_frame); + + //qDebug() << "Target:" << target_pts << "Seek:" << seek_ts << "Frame:" << decoded_frame->pts; + + seek_ts = qMax(zero, seek_ts - second_pts); + + have_existing_frame_to_use = true; + } while (retrieve_code >= 0 && decoded_frame->pts > target_pts && !seeked_to_zero); + + // also we assume none of the frames in the queue are usable + queue_.lock(); + queue_.clear(); + queue_.unlock(); + + // reset upcoming frame count and latest pts for later calculations + frames_greater_than_target = 0; + latest_pts = INT64_MIN; + } + + // get values on old frames to remove from the queue + + // for FRAME_QUEUE_TYPE_SECONDS, this is used to store the maximum timestamp + // for FRAME_QUEUE_TYPE_FRAMES, this is used to store the maximum number of frames that can be added + int64_t minimum_ts; + + // check if we can add more frames to this queue or not + + // for FRAME_QUEUE_TYPE_SECONDS, this is used to store the maximum timestamp + // for FRAME_QUEUE_TYPE_FRAMES, this is used to store the maximum number of frames that can be added + int64_t maximum_ts; + + // Get queue configuration + int previous_queue_type, upcoming_queue_type; + double previous_queue_size, upcoming_queue_size; + + // 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::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::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 + if (previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + // get the maximum number of previous frames that can be in the queue + minimum_ts = qCeil(previous_queue_size); + } else { + // get the minimum frame timestamp that can be added to the queue + minimum_ts = qRound(target_pts - second_pts * previous_queue_size); + } + + // Determine "upcoming" queue statistics + if (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + maximum_ts = qCeil(upcoming_queue_size); + } else { + // get the maximum frame timestamp that can be added to the queue + maximum_ts = qRound(target_pts + second_pts * upcoming_queue_size); + } + + // if we already have the maximum number of upcoming frames, don't bother running the retrieving any frames at all + bool start_loop = true; + if ((upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES && frames_greater_than_target >= maximum_ts) + || (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS && latest_pts > maximum_ts)) { + start_loop = false; + } + + + if (start_loop) { + + interrupt_ = false; + do { + + // retrieve raw RGBA frame from decoder + filter stack + int retrieve_code = 0; + + // if we retrieved a perfectly good frame earlier by checking the seek, use that here + if (!have_existing_frame_to_use) { + retrieve_code = RetrieveFrameAndProcess(&decoded_frame); + } else { + have_existing_frame_to_use = false; + } + + if (retrieve_code < 0 && retrieve_code != AVERROR_EOF) { + + // for some reason we were unable to retrieve a frame, likely a decoder error so we report it + // again, an EOF isn't an "error" but will how we add frames (see below) + + qCritical() << "Failed to retrieve frame from buffersink." << retrieve_code; + break; + + } else if (decoded_frame->pts != AV_NOPTS_VALUE) { + + // check if this frame exceeds the minimum timestamp + if (previous_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS + && decoded_frame->pts < minimum_ts) { + + // if so, we don't need it + av_frame_free(&decoded_frame); + + } else { + + if (retrieved_frame == nullptr) { + if (decoded_frame->pts == target_pts) { + + // We retrieved the exact frame we're looking for + + SetRetrievedFrame(decoded_frame); + + } else if (decoded_frame->pts > target_pts) { + + if (queue_.size() > 0) { + + SetRetrievedFrame(queue_.last()); + + } else if (seeked_to_zero) { + + // If this flag is set but we still got a frame after the target timestamp, it means this was somehow + // the earliest frame we could get + SetRetrievedFrame(decoded_frame); + seeked_to_zero = false; + + } + + } + } + + // add the frame to the queue + queue_.lock(); + queue_.append(decoded_frame); + queue_.unlock(); + + // check the amount of previous frames in the queue by using the current queue size for if we need to + // remove any old entries (assumes the queue is chronological) + if (previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + + int previous_frame_count = 0; + + if (decoded_frame->pts < target_pts) { + // if this frame is before the target frame, make sure we don't add too many of them + previous_frame_count = queue_.size(); + } else { + // if this frame is after the target frame, clean up any previous frames before it + // TODO is there a faster way to do this? + + for (int i=0;ipts > target_pts) { + break; + } else { + previous_frame_count++; + } + } + + } + + // remove frames while the amount of previous frames exceeds the maximum + while (previous_frame_count > minimum_ts) { + queue_.lock(); + queue_.removeFirst(); + queue_.unlock(); + previous_frame_count--; + } + + } + + // check if the queue is full according to olive::CurrentConfig + if (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + + // if this frame is later than the target, it's an "upcoming" frame + if (decoded_frame->pts > target_pts) { + + // we started a count of upcoming frames above, we can continue it here + frames_greater_than_target++; + + // compare upcoming frame count with maximum upcoming frames (maximum_ts) + if (frames_greater_than_target >= maximum_ts) { + break; + } + } + + } else if (decoded_frame->pts > maximum_ts) { // for `upcoming_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS` + break; + } + + } + + + + } else { + + // if a frame has no timestamp (pts == AV_NOPTS_VALUE), we assume it's an invalid frame and don't use it + + qWarning() << clip->name() << "frame had no PTS value"; + av_frame_free(&decoded_frame); + + if (retrieve_code == AVERROR_EOF && retrieved_frame == nullptr && !queue_.isEmpty()) { + // if we reached the end of the file, it's not an error but there are no more frames to retrieve + // some formats EOF before the end of the duration that Olive calculates. In this event, we simply + // return the last frame we retrieved + // + // TODO: Check duration formula + + SetRetrievedFrame(queue_.last()); + + } else { + + SetRetrievedFrame(nullptr); + + } + + break; + + } + } while (!interrupt_); + + } + + } + + // For some reason we couldn't get the frame, we should wake up the RenderThread anyway + if (retrieved_frame == nullptr) { + qCritical() << "Couldn't retrieve an appropriate frame. This is an error and may mean this media is corrupt."; + SetRetrievedFrame(nullptr); + } +} + +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->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_; + frame_sample_index_ = -1; + frame_->pts = 0; + } + } else { + + const FootageStream* ms = clip->media_stream(); + if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + // flush ffmpeg codecs + avcodec_flush_buffers(codecCtx); + reached_end = false; + + // seek (target_frame represents timeline timecode in frames, not clip timecode) + + int64_t timestamp = qRound64(playhead_to_clip_seconds(clip, playhead_) / av_q2d(stream->time_base)); + + bool temp_reverse = (playback_speed_ < 0); + if (clip->reversed() != temp_reverse) { + reverse_target_ = timestamp; + timestamp -= av_q2d(av_inv_q(stream->time_base)); +#ifdef AUDIOWARNINGS + dout << "seeking to" << timestamp << "(originally" << reverse_target << ")"; + } else { + dout << "reset called; seeking to" << timestamp; +#endif + } + av_seek_frame(formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD); + audio_target_frame = playhead_; + frame_sample_index_ = -1; + } + } +} + +void Cacher::SetRetrievedFrame(AVFrame *f) +{ + if (retrieved_frame == nullptr) { + retrieve_lock_.lock(); + retrieved_frame = f; + retrieve_wait_.wakeAll(); + retrieve_lock_.unlock(); + } +} + +void Cacher::WakeMainThread() +{ + main_thread_lock_.lock(); + main_thread_wait_.wakeAll(); + main_thread_lock_.unlock(); +} + +Cacher::Cacher(Clip* c) : + clip(c), + frame_(nullptr), + pkt(nullptr), + formatCtx(nullptr), + opts(nullptr), + filter_graph(nullptr), + codecCtx(nullptr), + is_valid_state_(false) +{} + +void Cacher::OpenWorker() { + // set some defaults for the audio cacher + if (clip->type() == olive::kTypeAudio) { + audio_reset_ = false; + frame_sample_index_ = -1; + audio_buffer_write = 0; + } + reached_end = false; + + if (clip->media() == nullptr) { + if (clip->type() == olive::kTypeAudio) { + frame_ = av_frame_alloc(); + frame_->format = kDestSampleFmt; + 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; + av_frame_make_writable(frame_); + if (av_frame_get_buffer(frame_, 0)) { + qCritical() << "Could not allocate buffer for tone clip"; + } + audio_reset_ = true; + } + } else if (clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + // opens file resource for FFmpeg and prepares Clip struct for playback + Footage* m = clip->media()->to_footage(); + + // byte array for retrieving raw bytes from QString URL + QByteArray ba; + + // do we have a 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(); + } else { + ba = m->url.toUtf8(); + } + + const char* filename = ba.constData(); + const FootageStream* ms = clip->media_stream(); + + // for image sequences that don't start at 0, set the index where it does start + AVDictionary* format_opts = nullptr; + if (m->start_number > 0) { + av_dict_set(&format_opts, "start_number", QString::number(m->start_number).toUtf8(), 0); + } + + formatCtx = nullptr; + int errCode = avformat_open_input( + &formatCtx, + filename, + nullptr, + &format_opts + ); + if (errCode != 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + qCritical() << "Could not open" << filename << "-" << err; + olive::MainWindow->statusBar()->showMessage(tr("Could not open %1 - %2").arg(filename, err)); + return; + } + + errCode = avformat_find_stream_info(formatCtx, nullptr); + if (errCode < 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + qCritical() << "Could not open" << filename << "-" << err; + olive::MainWindow->statusBar()->showMessage(tr("Could not open %1 - %2").arg(filename, err)); + return; + } + + av_dump_format(formatCtx, 0, filename, 0); + + stream = formatCtx->streams[ms->file_index]; + codec = avcodec_find_decoder(stream->codecpar->codec_id); + codecCtx = avcodec_alloc_context3(codec); + avcodec_parameters_to_context(codecCtx, stream->codecpar); + + opts = nullptr; + + // enable multithreading on decoding + av_dict_set(&opts, "threads", "auto", 0); + + // enable extra optimization code on h264 (not even sure if they help) + if (stream->codecpar->codec_id == AV_CODEC_ID_H264) { + av_dict_set(&opts, "tune", "fastdecode", 0); + av_dict_set(&opts, "tune", "zerolatency", 0); + } + + // Open codec + if (avcodec_open2(codecCtx, codec, &opts) < 0) { + qCritical() << "Could not open codec"; + } + + // allocate filtergraph + filter_graph = avfilter_graph_alloc(); + if (filter_graph == nullptr) { + qCritical() << "Could not create filtergraph"; + } + char filter_args[512]; + + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + stream->codecpar->width, + stream->codecpar->height, + stream->codecpar->format, + stream->time_base.num, + stream->time_base.den, + stream->codecpar->sample_aspect_ratio.num, + stream->codecpar->sample_aspect_ratio.den + ); + + avfilter_graph_create_filter(&buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, filter_graph); + avfilter_graph_create_filter(&buffersink_ctx, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, filter_graph); + + AVFilterContext* last_filter = buffersrc_ctx; + + char filter_args[100]; + + if (ms->video_interlacing != VIDEO_PROGRESSIVE) { + AVFilterContext* yadif_filter; + snprintf(filter_args, sizeof(filter_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, filter_graph); + + avfilter_link(last_filter, 0, yadif_filter, 0); + last_filter = yadif_filter; + } + + 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; + avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", filter_args, nullptr, filter_graph); + avfilter_link(last_filter, 0, format_conv, 0); + + avfilter_link(format_conv, 0, buffersink_ctx, 0); + + avfilter_graph_config(filter_graph, nullptr); + + } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + if (codecCtx->channel_layout == 0) codecCtx->channel_layout = av_get_default_channel_layout(stream->codecpar->channels); + + // set up cache + queue_.append(av_frame_alloc()); + + if (true) { + AVFrame* reverse_frame = av_frame_alloc(); + + reverse_frame->format = kDestSampleFmt; + reverse_frame->nb_samples = current_audio_freq()*10; + 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); + } + + snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64, + stream->time_base.num, + stream->time_base.den, + stream->codecpar->sample_rate, + av_get_sample_fmt_name(codecCtx->sample_fmt), + codecCtx->channel_layout + ); + + avfilter_graph_create_filter(&buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph); + avfilter_graph_create_filter(&buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph); + + enum AVSampleFormat sample_fmts[] = { kDestSampleFmt, static_cast(-1) }; + if (av_opt_set_int_list(buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { + qCritical() << "Could not set output sample format"; + } + + int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) }; + if (av_opt_set_int_list(buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { + qCritical() << "Could not set output sample format"; + } + + int target_sample_rate = current_audio_freq(); + + double playback_speed_ = clip->speed().value * m->speed; + + if (qFuzzyCompare(playback_speed_, 1.0)) { + avfilter_link(buffersrc_ctx, 0, buffersink_ctx, 0); + } else if (clip->speed().maintain_audio_pitch) { + AVFilterContext* previous_filter = buffersrc_ctx; + AVFilterContext* last_filter = buffersrc_ctx; + + char speed_param[10]; + + double base = (playback_speed_ > 1.0) ? 2.0 : 0.5; + + double speedlog = log(playback_speed_) / log(base); + int whole2 = qFloor(speedlog); + speedlog -= whole2; + + if (whole2 > 0) { + snprintf(speed_param, sizeof(speed_param), "%f", base); + for (int i=0;itrack(); + + is_valid_state_ = true; +} + +void Cacher::CacheWorker() { + if (clip->type() == olive::kTypeVideo) { + // clip is a video track, start caching video + CacheVideoWorker(); + } else { + // clip is audio + CacheAudioWorker(); + } +} + +void Cacher::CloseWorker() { + retrieved_frame = nullptr; + queue_.lock(); + queue_.clear(); + queue_.unlock(); + + if (frame_ != nullptr) { + av_frame_free(&frame_); + frame_ = nullptr; + } + + if (pkt != nullptr) { + av_packet_free(&pkt); + pkt = nullptr; + } + + if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + if (filter_graph != nullptr) { + avfilter_graph_free(&filter_graph); + filter_graph = nullptr; + } + + if (codecCtx != nullptr) { + avcodec_close(codecCtx); + avcodec_free_context(&codecCtx); + codecCtx = nullptr; + } + + if (opts != nullptr) { + av_dict_free(&opts); + } + + // protection for get_timebase() + stream = nullptr; + + if (formatCtx != nullptr) { + avformat_close_input(&formatCtx); + } + } + + qInfo() << "Clip closed on track" << clip->track(); +} + +void Cacher::run() { + clip->cache_lock.lock(); + + OpenWorker(); + + clip->state_change_lock.unlock(); + + while (caching_) { + if (!queued_) { + wait_cond_.wait(&clip->cache_lock); + } + queued_ = false; + if (!caching_) { + break; + } else if (is_valid_state_) { + CacheWorker(); + } else { + // main thread waits until cacher starts fully, but the cacher can't run, so we just wake it up here + WakeMainThread(); + } + } + + is_valid_state_ = false; + + CloseWorker(); + + clip->state_change_lock.unlock(); + + clip->cache_lock.unlock(); +} + +void Cacher::Open() +{ + wait(); + + // set variable defaults for caching + caching_ = true; + queued_ = false; + + start((clip->type() == olive::kTypeVideo) ? QThread::HighPriority : QThread::TimeCriticalPriority); +} + +void Cacher::Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed) +{ + + if (!is_valid_state_) { + return; + } + + if (clip->media_stream() != nullptr + && queue_.size() > 0 + && clip->media_stream()->infinite_length) { + retrieved_frame = queue_.at(0); + return; + } + + playhead_ = playhead; + nests_ = nests; + scrubbing_ = scrubbing; + playback_speed_ = playback_speed; + queued_ = true; + + bool wait_for_cacher_to_respond = true; + + if (clip->media() != nullptr) { + // see if we already have this frame + retrieve_lock_.lock(); + queue_.lock(); + retrieved_frame = nullptr; + int64_t target_pts = seconds_to_timestamp(clip, playhead_to_clip_seconds(clip, playhead_)); + for (int i=0;ipts == target_pts) { + + // the queue has a frame with the exact timestamp + + retrieved_frame = queue_.at(i); + wait_for_cacher_to_respond = false; + break; + } else if (i > 0 && queue_.at(i-1)->pts < target_pts && queue_.at(i)->pts > target_pts) { + + // the queue has a frame with a close timestamp that we'll assume is different due to a rounding error + + retrieved_frame = queue_.at(i-1); + wait_for_cacher_to_respond = false; + break; + } + } + queue_.unlock(); + retrieve_lock_.unlock(); + } + + if (wait_for_cacher_to_respond) { + main_thread_lock_.lock(); + } + + // wake up cacher + wait_cond_.wakeAll(); + + // if not, wait for cacher to respond + if (wait_for_cacher_to_respond) { + interrupt_ = true; + main_thread_wait_.wait(&main_thread_lock_, 2000); + } + + if (wait_for_cacher_to_respond) { + main_thread_lock_.unlock(); + } +} + +AVFrame *Cacher::Retrieve() +{ + if (!caching_) { + return nullptr; + } + + // for thread-safety, we lock a mutex to ensure this thread is never woken by anything out of sync + + retrieve_lock_.lock(); + + // check if there's a frame ready to be shown by the cacher + + if (retrieved_frame == nullptr) { + // wait for cacher to finish caching + + + if (clip->cache_lock.tryLock()) { + + // If the queue could lock, the cacher isn't running which means no frame is coming. This is an error. + qCritical() << "Cacher frame was null while the cacher wasn't running on clip" << clip->name(); + clip->cache_lock.unlock(); + + } else { + + // cacher is running, wait for it to give a frame + retrieve_wait_.wait(&retrieve_lock_); + + } + + } + + retrieve_lock_.unlock(); + + return retrieved_frame; +} + +void Cacher::Close(bool wait_for_finish) +{ + caching_ = false; + wait_cond_.wakeAll(); + + if (wait_for_finish) { + wait(); + } +} + +void Cacher::ResetAudio() +{ + // using audio_write_lock seems like a good idea, but hasn't been tested yet. If there are audio issues when seeking, + // try uncommenting them + +// audio_write_lock.lock(); + audio_reset_ = true; + frame_sample_index_ = -1; + audio_buffer_write = 0; +// audio_write_lock.unlock(); +} + +int Cacher::media_width() +{ + return stream->codecpar->width; +} + +int Cacher::media_height() +{ + return stream->codecpar->height; +} + +AVRational Cacher::media_time_base() +{ + return stream->time_base; +} + +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; + + // do we need to retrieve a new packet for a new frame? + av_frame_unref(f); + while ((receive_ret = avcodec_receive_frame(codecCtx, f)) == AVERROR(EAGAIN)) { + int read_ret = 0; + do { + if (pkt->buf != nullptr) { + av_packet_unref(pkt); + } + read_ret = av_read_frame(formatCtx, pkt); + } while (read_ret >= 0 && pkt->stream_index != clip->media_stream_index()); + + if (read_ret >= 0) { + int send_ret = avcodec_send_packet(codecCtx, pkt); + if (send_ret < 0) { + qCritical() << "Failed to send packet to decoder." << send_ret; + return send_ret; + } + } else { + if (read_ret == AVERROR_EOF) { + int send_ret = avcodec_send_packet(codecCtx, nullptr); + if (send_ret < 0) { + qCritical() << "Failed to send packet to decoder." << send_ret; + return send_ret; + } + } else { + qCritical() << "Could not read frame." << read_ret; + return read_ret; // skips trying to find a frame at all + } + } + } + if (receive_ret < 0) { + if (receive_ret != AVERROR_EOF) qCritical() << "Failed to receive packet from decoder." << receive_ret; + result = receive_ret; + } + + return result; +} + +int Cacher::RetrieveFrameAndProcess(AVFrame **f) +{ + // error codes from FFmpeg + int retrieve_code, read_code, send_code; + + // frame for FFmpeg to decode into + *f = av_frame_alloc(); + + // loop to pull frames from the AVFilter stack + while ((retrieve_code = av_buffersink_get_frame(buffersink_ctx, *f)) == AVERROR(EAGAIN)) { + + // retrieve frame from decoder + read_code = RetrieveFrameFromDecoder(frame_); + + if (read_code >= 0) { + + // we retrieved a decoded video frame, which we will send to the AVFilter stack to convert to RGBA (with other + // adjustments if necessary) + + if ((send_code = av_buffersrc_add_frame_flags(buffersrc_ctx, frame_, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + qCritical() << "Failed to add frame to buffer source." << send_code; + break; + } + + // we don't need the original frame to we free it here + av_frame_unref(frame_); + + } else { + + // AVERROR_EOF means we've reached the end of the file, not technically an error, but it's useful to know that + // there are no more frames in this file + if (read_code != AVERROR_EOF) { + qCritical() << "Failed to read frame." << read_code; + } + break; + } + } + + if (read_code == AVERROR_EOF) { + return AVERROR_EOF; + } + return retrieve_code; +} diff --git a/rendering/cacher.h b/rendering/cacher.h index 0b215a673..c6e17424a 100644 --- a/rendering/cacher.h +++ b/rendering/cacher.h @@ -1,602 +1,602 @@ -/*** - - 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 CACHER_H -#define CACHER_H - -#define __STDC_FORMAT_MACROS 1 -#include - -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -#include -} - -#include -#include -#include -#include -#include - -#include "rendering/clipqueue.h" -#include "rendering/pixelformats.h" - -class Clip; - -/** - * @brief The Cacher class - * - * For footage clips - usually the majority of clips - decoding can be strenuous on CPU and inconsistent in timing. As - * a result, we keep a memory cache of upcoming frames that we fill in a background thread so they can be retrieved from - * a rendering thread later. This class is the background thread filling up a clip's frame cache (also called a "queue" - * since video files are usually stored with frames in linear chronological order). It involves decoding routines to - * retrieve raw frames from the file (using libavformat/libavcodec), conversion routines to conform the raw frames to - * RGBA/S16LE for the rest of the workflow (using libavfilter/libswscale/libswresample), and memory handling routines - * for keeping the cache within limits defined by the user (see Config::upcoming_queue_type). - * - * Generally the Cacher workflow starts by calling Open() which will start the thread, open a file handle, and create a - * decoding instance. Open() is usually called directly from the parent Clip's Clip::Open() and thus expects the - * Clip::state_change_lock to be locked. It will unlock it when it's finished opening and is ready to start caching, - * meaning Clip::state_change_lock can be used to synchronize threads. - * - * --- - * - * **For video:** - * - * After the Cacher has finished opening, request a frame by calling Cache(). Cache() will tell the Cacher - * information about the current playback state, most importantly the current place in time according to the Sequence's - * playhead. Cache() determines whether the requested frame is already in the queue, and then signals the Cacher thread - * to cache ahead if there's room in the queue (and also remove old frames that are no longer necessary). To retrieve - * the requested frame, call Retrieve(). - * - * If Cache() found the frame already in the queue, Retrieve() will return immediately with this frame. Otherwise - * Retrieve() may block while the cacher retrieves it. Therefore it is recommended never to call Retrieve() from - * the main thread. Retrieve() may also return `nullptr` if there was an issue, e.g. the cacher failed to retrieve - * the frame. - * - * **For audio:** - * - * After the Cacher has finished opening, calling Cache() will handle most of the work. It will decode the audio, - * convert to the correct sample rate and format, reverse or adjust speed if necessary, and send it to the audio - * buffer ready to be played by the output device. It is important to continually call Cache() as it doesn't get - * signalled when more samples are available in the audio buffer. Instead, it'll check every time it's called and - * fill as much of the buffer as it can. - * - * If the user seeks, ResetAudio() must be called to signal the Cacher to interrupt the current audio stream and - * move somewhere else before continuing. - * - * --- - * - * Finally, when the Cacher/parent Clip are no longer in use, call Close() to free all memory and file handling - * allocated for the cacher. You can choose whether to wait for Close() and all of its child processes to complete - - * e.g. if you need to change something with the Clip or attached Footage that changes how it opens and want to be - * thread-safe - or let the Close thread finish up on its own. - * - * Cacher expects to be multithreaded and all of its public functions are thread-safe. - */ -class Cacher : public QThread -{ - Q_OBJECT -public: - /** - * @brief Cacher Constructor - * - * Create Cacher object. The thread is not started here. To start it, call Open(). - * - * @param c - */ - Cacher(Clip* c); - - /** - * @brief The main QThread loop - * - * Once the thread has started, all Cacher functions will be called from here until the Cacher closes at which point - * it will close and exit gracefully. - */ - void run(); - - /** - * @brief Open the cacher - * - * Starts the thread and all file/decode handlers. Really just sets some default values and starts the thread, which - * will in turn call OpenWorker() at the start of its functions. - * - * Make sure Clip::state_change_lock is LOCKED before calling this function as the opening process will try to unlock - * it when it's finished (leading to a crash if it's not already locked). - */ - void Open(); - - /** - * @brief Request a frame to be cached - * - * For video, this function is part 1 of the Cache()/Retrieve() workflow. It signals the thread to start caching and - * provides a few other details about the playback state. For optimization it'll also check the frame queue if it - * already contains the requested frame and use it if so, potentially speeding up Retrieve() later on. Otherwise - * it'll interrupt any currently caching operation and signal it to start again. - * While Retrieve() will block until the correct frame is retrieved, this function will return fairly quickly (either - * immediately if the frame was found in the queue, or once the cacher has restarted caching if not). This means - * Cache() can be called from another thread and then that other thread can do other work while the cacher is - * retrieving the frame, finally calling Retrieve() once the frame is absolutely necessary. - * - * For audio, this function will do all the work of signalling the thread to start caching and sending samples to - * the output audio buffer. It's used in tandem with ResetAudio() when the Timeline header is changed abruptly. - * - * @param playhead - * - * The current Timeline played position in frames - * - * @param scrubbing - * - * **TRUE** if the user is currently scrubbing. **FALSE** if not. - * - * @param nests - * - * A hierarchy of nested sequences, if the playback traversed any to get to this clip. - * - * @param playback_speed - * - * The current playback speed (controlled by Shuttle Left/Stop/Right) - */ - void Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed); - - /** - * @brief Retrieve frame requested by Cache() - * - * Part 2 of the Cache()/Retrieve() workflow, only used for video. Whichever frame was requested by Cache(), this - * function will try to retrieve it. In most cases, this function will be pretty quick as the frame will be available - * immediately from Cache()'s optimization or the cacher thread will be close to retrieving the correct frame anyway. - * However it does block for however long it takes to retrieve the correct frame (if the cacher is running) so it's - * not recommended to call this from any main/GUI thread. - * - * @return - * - * The frame requested by Cache(), or `nullptr` if there was an error (e.g. the cacher wasn't running and no frame was - * available). - */ - AVFrame* Retrieve(); - - /** - * @brief Close the cacher and free any allocated memory - * - * When the Cacher thread is no longer needed, Close() should be called in order to free system resources. This will - * signal the thread to exit gracefully, but will not delete the thread object since the cacher may need to be - * re-opened later by Open(). - * - * @param wait_for_finish - * - * **TRUE** if this function should block the calling thread until the Clip has finished closing. Often necessary if - * the Clip is being closed specifically to make changes to it. - */ - void Close(bool wait_for_finish); - - /** - * @brief Interrupt and reset audio state - * - * Used in tandem with Cache(), only for audio clips. Cache() will decode and send audio continually as it's - * repeatedly called. If the audio stream needs to be interrupted and moved somewhere else for any reason - * (e.g. the user seeked somewhere else), then it's necessary to call ResetAudio() to signal the cacher to - * seek to the next place indicated by Cache(). - */ - void ResetAudio(); - - /** - * @brief Retrieve current media width - * - * In some situations, the actual media we're using may be a different resolution to how we're treating it (e.g. - * lower resolution proxies). While most functions will happily treat the media as its original resolution, some - * processes will need the absolute resolution from the file which can be acquired here. - * - * Only call after the thread has been opened by Open(). - * - * @return - * - * The true width of the current video file. - */ - int media_width(); - - /** - * @brief Retrieve current media height - * - * See media_width(). - * - * Only call after the thread has been opened by Open(). - * - * @return - * - * The true height of the current video file. - */ - int media_height(); - - /** - * @brief Retrieve media time base - * - * For some timing operations, it's necessary to use the source media's timebase. Similar to media_width() and - * media_height(), we need the accurate timebase from the file as a proxy's timebase may or may not be the same - * as the source file. - * - * Only call after the thread has been opened by Open(). - * - * @return - * - * The timebase of the file. - */ - AVRational media_time_base(); - - /** - * @brief Get cacher queue object - * - * @return - * - * A pointer to the cacher's internal frame queue - */ - 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. - */ - Clip* clip; - - /** - * @brief Frame queue - * - * Valid fames are cached into this, which also does memory handling when necessary. - */ - ClipQueue queue_; - - /** - * @brief Main wait condition - * - * Used with Clip::cache_lock as the main block while the the Cacher thread isn't running. Wake this condition - * to start caching. - */ - QWaitCondition wait_cond_; - - /** - * @brief Main thread wait condition - * - * Used with main_thread_lock_ to block Cache() while waiting for a response from the cacher thread. - */ - QWaitCondition main_thread_wait_; - - /** - * @brief Main thread mutex - * - * Used with main_thread_wait_ to block Cache() while waiting for a response from the cacher thread. - */ - QMutex main_thread_lock_; - - /** - * @brief Retrieve() wait condition - * - * Used with retrieve_lock_ to block Retrieve() if the cacher hasn't retrieved the correct frame yet. - */ - QWaitCondition retrieve_wait_; - - /** - * @brief Retrieve() mutex - * - * Used with retrieve_wait_ to block Retrieve() if the cacher hasn't retrieved the correct frame yet. - */ - QMutex retrieve_lock_; - - /** - * @brief Set and used by CacheAudioWorker if the decoder receives an EOF. - * - * Deprecated. CacheAudioWorker() is functional but probably should be rewritten. - */ - bool reached_end; - - /** - * @brief Current Sequence playhead set by Cache() - */ - long playhead_; - - /** - * @brief Current Sequence scrubbing state set by Cache() - */ - bool scrubbing_; - - /** - * @brief Current Sequence playback speed set by Cache() - */ - int playback_speed_; - - /** - * @brief Current nested Sequence hierarchy set by Cache() - */ - QVector nests_; - - /** - * @brief Signal cache to continue operation after one cycle rather than wait for another signal - * - * Each cycle of the cacher thread (see run()) will set this to false in the beginning. Each call of Cache() will set - * this to **TRUE**. If this variable is **TRUE**, the cacher won't wait for another signal before starting the next - * cache cycle, and will instead just start it. - * - * Used if Cache() is called and interrupts the cacher while it's already running so that the cacher will restart - * itself automatically rather than wait for the next cache signal. - */ - bool queued_; - - /** - * @brief Interrupt the current cache cycle - * - * A cache cycle will cache several frames at a time. Since decoding can be strenuous and time consuming, the - * cycle can be interrupted if it needs to abruptly start caching somewhere else. Best used in tandem with - * queued_ to automatically start the next cache cycle. - */ - bool interrupt_; - - // ffmpeg media handling - /** - * @brief FFmpeg format/file context - used for media decoding - */ - AVFormatContext* formatCtx; - - /** - * @brief FFmpeg decoder context - used for media decoding - */ - AVCodecContext* codecCtx; - - /** - * @brief FFmpeg stream - used for media decoding - */ - AVStream* stream; - - /** - * @brief FFmpeg packet - used for media decoding - */ - AVPacket* pkt; - - /** - * @brief FFmpeg frame - used for media decoding - * - * This is usually used as a raw decoded frame before the RGBA conversion/AVFilter stack. Converted/filtered frames go - * into Cacher::queue. - */ - AVFrame* frame_; - - /** - * @brief Retrieved frame reference for Retrieve() - * - * If a frame was found by either Cache() or CacheVideoWorker(), it's set here. If no frame is ready yet, this is set - * to `nullptr`. - */ - AVFrame* retrieved_frame = nullptr; - - // converters/filters - /** - * @brief FFmpeg filter stack - * - * Used for conversion from the media's pixel format to RGBA for OpenGL. Also any other FFmpeg filters are implemented - * here if necessary (e.g. yadif for deinterlacing). GLSL effects are preferred when available since FFmpeg filters - * aren't always fast enough for realtime playback. - */ - AVFilterGraph* filter_graph; - - /** - * @brief FFmpeg buffer source - * - * Raw decoded frames are added to this for conversion/filtering - */ - AVFilterContext* buffersrc_ctx; - - /** - * @brief FFmpeg buffer sink - * - * Converted/filtered frames are retrieved from here and sent to Cacher::queue. - */ - AVFilterContext* buffersink_ctx; - - /** - * @brief FFmpeg codec reference - */ - AVCodec* codec; - - /** - * @brief Options set by the cacher for FFmpeg's decoders (settings like multithreading or other optimizations) - */ - AVDictionary* opts; - - // audio playback variables - /** - * @brief Internal audio reset variable - * - * Set by AudioReset() and read by CacheAudioWorker() when the audio state needs to be interrupted and reset. - */ - bool audio_reset_; - - /** - * @brief Internal reverse target variable - * - * Used by CacheAudioWorker() to stitch audio frames together when reversing. Stores the current frame's timestamp - * so it knows how much to decode up to when it backtracks and decodes the next samples. - */ - int64_t reverse_target_; - - /** - * @brief Internal frame sample index variable - * - * Used by CacheAudioWorker() to mark which part of the audio frame to read from - */ - int frame_sample_index_; - - /** - * @brief Internal audio buffer write variable - * - * Used by CacheAudioWorker() to mark which part of the audio buffer to write to - */ - qint64 audio_buffer_write; - - /** - * @brief Internal variable that holds the playhead the last time the audio state was reset - */ - long audio_target_frame; - - /** - * @brief Main while loop condition to determine whether thread should continue looping - * - * Open() sets this to **TRUE**, Close() sets this to **FALSE**. If it's false, the main loop in run() will exit and - * the thread will exit cleanly. It's not recommended to set this variable directly, use Open() and Close() instead. - */ - bool caching_; - - /** - * @brief Internal variable for whether the current Cacher state is valid or not - * - * If there was an error opening the Cacher for any reason, this will be false. - */ - bool is_valid_state_; - - /** - * @brief Internal function for opening the file handles and decoder - * - * After the thread has started, it'll call this function to start all resources necessary for caching. Any - * FFmpeg decoding variables and filters are set up here. - * - * This is - * fundamentally different from Open(), this is only meant to be called within the cacher thread and never from - * outside and doesn't start the thread like Open() does. - */ - void OpenWorker(); - - /** - * @brief Internal function for starting a cache cycle - * - * This used to have more function, but now just differentiates between CacheVideoWorker() for video clips and - * CacheAudioWorker() for audio clips. - */ - void CacheWorker(); - - /** - * @brief Internal function for closing cacher - * - * Called if the main thread loop in run() exits by setting caching_ to **FALSE**. Free's up handles and memory - * allocated by OpenWorker(). - */ - void CloseWorker(); - - /** - * @brief Internal function for resetting audio state - * - * This used to be a common function, but is now simply a legacy function for CacheAudioWorker(). Resets and - * flushes decoders and seeks to the correct timestamp. - */ - void Reset(); - - /** - * @brief Internal function for setting retrieved_frame and waking up any threads waiting for it - * - * @param f - * - * The frame to set as the retrieved frame. - */ - void SetRetrievedFrame(AVFrame* f); - - /** - * @brief Internal function to wake an external calling thread - * - * In some situations, Cache() may wait for the cacher to respond before returning. This is to assist in thread - * synchronization, making sure the cacher has started working and has locked any resources it needs before any - * other threads can access them (e.g. with a function like Retrieve() ). This must be called at the start of - * any CacheVideoWorker() or CacheAudioWorker() control paths to ensure the render thread doesn't get stuck. - */ - void WakeMainThread(); - - /** - * @brief Retrieve frame from decoder - * - * Retrieves the next decoded frame from the decoder. Depending on the source media, this frame may or may not be - * suitable for usage later in the pipeline as it may or may not be the correct pixel/sample format. For a suitable - * frame for the pipeline, use RetrieveFrameAndProcess() instead (which in turn uses this function anyway). - * - * @param f - * - * Frame buffer to decode frame into - * - * @return - * - * FFmpeg error code (>= 0 on success, a negative error code on failure) - */ - int RetrieveFrameFromDecoder(AVFrame* f); - - /** - * @brief Retrieve frame from decoder and run it through filter stack - * - * Retrieves the next decoded frame and runs it through the AVFilter stack to create an RGBA frame compatible with - * the rest of the pipeline and OpenGL. Use this function if you need a ready-made frame. - * - * @param f - * - * A pointer to an AVFrame object. It does not need to be allocated, as this function allocates an AVFrame itself. - * You'll also need to free it later with av_frame_free() (though ClipQueue will do this automatically if the frame is - * added to it). - * - * @return - * - * FFmpeg error code (>= 0 on success, a negative error code on failure) - */ - int RetrieveFrameAndProcess(AVFrame **f); - - /** - * @brief Internal video caching function - * - * Performs one video cache cycle. Seeks the media and cleans old frames from the queue if necessary. Decodes frames - * and adds them to the queue (after calculating whether they're necessary). - */ - void CacheVideoWorker(); - - /** - * @brief Internal audio caching function - * - * Perform one audio cache cycle. Retrieves audio from decoder, reverses and changes speed if necessary, and sends - * audio to the audio buffer which will later be sent to the audio output device. - */ - void CacheAudioWorker(); - - /** - * @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 +/*** + + 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 CACHER_H +#define CACHER_H + +#define __STDC_FORMAT_MACROS 1 +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +#include +#include +#include +#include +#include + +#include "rendering/clipqueue.h" +#include "rendering/pixelformats.h" + +class Clip; + +/** + * @brief The Cacher class + * + * For footage clips - usually the majority of clips - decoding can be strenuous on CPU and inconsistent in timing. As + * a result, we keep a memory cache of upcoming frames that we fill in a background thread so they can be retrieved from + * a rendering thread later. This class is the background thread filling up a clip's frame cache (also called a "queue" + * since video files are usually stored with frames in linear chronological order). It involves decoding routines to + * retrieve raw frames from the file (using libavformat/libavcodec), conversion routines to conform the raw frames to + * RGBA/S16LE for the rest of the workflow (using libavfilter/libswscale/libswresample), and memory handling routines + * for keeping the cache within limits defined by the user (see Config::upcoming_queue_type). + * + * Generally the Cacher workflow starts by calling Open() which will start the thread, open a file handle, and create a + * decoding instance. Open() is usually called directly from the parent Clip's Clip::Open() and thus expects the + * Clip::state_change_lock to be locked. It will unlock it when it's finished opening and is ready to start caching, + * meaning Clip::state_change_lock can be used to synchronize threads. + * + * --- + * + * **For video:** + * + * After the Cacher has finished opening, request a frame by calling Cache(). Cache() will tell the Cacher + * information about the current playback state, most importantly the current place in time according to the Sequence's + * playhead. Cache() determines whether the requested frame is already in the queue, and then signals the Cacher thread + * to cache ahead if there's room in the queue (and also remove old frames that are no longer necessary). To retrieve + * the requested frame, call Retrieve(). + * + * If Cache() found the frame already in the queue, Retrieve() will return immediately with this frame. Otherwise + * Retrieve() may block while the cacher retrieves it. Therefore it is recommended never to call Retrieve() from + * the main thread. Retrieve() may also return `nullptr` if there was an issue, e.g. the cacher failed to retrieve + * the frame. + * + * **For audio:** + * + * After the Cacher has finished opening, calling Cache() will handle most of the work. It will decode the audio, + * convert to the correct sample rate and format, reverse or adjust speed if necessary, and send it to the audio + * buffer ready to be played by the output device. It is important to continually call Cache() as it doesn't get + * signalled when more samples are available in the audio buffer. Instead, it'll check every time it's called and + * fill as much of the buffer as it can. + * + * If the user seeks, ResetAudio() must be called to signal the Cacher to interrupt the current audio stream and + * move somewhere else before continuing. + * + * --- + * + * Finally, when the Cacher/parent Clip are no longer in use, call Close() to free all memory and file handling + * allocated for the cacher. You can choose whether to wait for Close() and all of its child processes to complete - + * e.g. if you need to change something with the Clip or attached Footage that changes how it opens and want to be + * thread-safe - or let the Close thread finish up on its own. + * + * Cacher expects to be multithreaded and all of its public functions are thread-safe. + */ +class Cacher : public QThread +{ + Q_OBJECT +public: + /** + * @brief Cacher Constructor + * + * Create Cacher object. The thread is not started here. To start it, call Open(). + * + * @param c + */ + Cacher(Clip* c); + + /** + * @brief The main QThread loop + * + * Once the thread has started, all Cacher functions will be called from here until the Cacher closes at which point + * it will close and exit gracefully. + */ + void run(); + + /** + * @brief Open the cacher + * + * Starts the thread and all file/decode handlers. Really just sets some default values and starts the thread, which + * will in turn call OpenWorker() at the start of its functions. + * + * Make sure Clip::state_change_lock is LOCKED before calling this function as the opening process will try to unlock + * it when it's finished (leading to a crash if it's not already locked). + */ + void Open(); + + /** + * @brief Request a frame to be cached + * + * For video, this function is part 1 of the Cache()/Retrieve() workflow. It signals the thread to start caching and + * provides a few other details about the playback state. For optimization it'll also check the frame queue if it + * already contains the requested frame and use it if so, potentially speeding up Retrieve() later on. Otherwise + * it'll interrupt any currently caching operation and signal it to start again. + * While Retrieve() will block until the correct frame is retrieved, this function will return fairly quickly (either + * immediately if the frame was found in the queue, or once the cacher has restarted caching if not). This means + * Cache() can be called from another thread and then that other thread can do other work while the cacher is + * retrieving the frame, finally calling Retrieve() once the frame is absolutely necessary. + * + * For audio, this function will do all the work of signalling the thread to start caching and sending samples to + * the output audio buffer. It's used in tandem with ResetAudio() when the Timeline header is changed abruptly. + * + * @param playhead + * + * The current Timeline played position in frames + * + * @param scrubbing + * + * **TRUE** if the user is currently scrubbing. **FALSE** if not. + * + * @param nests + * + * A hierarchy of nested sequences, if the playback traversed any to get to this clip. + * + * @param playback_speed + * + * The current playback speed (controlled by Shuttle Left/Stop/Right) + */ + void Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed); + + /** + * @brief Retrieve frame requested by Cache() + * + * Part 2 of the Cache()/Retrieve() workflow, only used for video. Whichever frame was requested by Cache(), this + * function will try to retrieve it. In most cases, this function will be pretty quick as the frame will be available + * immediately from Cache()'s optimization or the cacher thread will be close to retrieving the correct frame anyway. + * However it does block for however long it takes to retrieve the correct frame (if the cacher is running) so it's + * not recommended to call this from any main/GUI thread. + * + * @return + * + * The frame requested by Cache(), or `nullptr` if there was an error (e.g. the cacher wasn't running and no frame was + * available). + */ + AVFrame* Retrieve(); + + /** + * @brief Close the cacher and free any allocated memory + * + * When the Cacher thread is no longer needed, Close() should be called in order to free system resources. This will + * signal the thread to exit gracefully, but will not delete the thread object since the cacher may need to be + * re-opened later by Open(). + * + * @param wait_for_finish + * + * **TRUE** if this function should block the calling thread until the Clip has finished closing. Often necessary if + * the Clip is being closed specifically to make changes to it. + */ + void Close(bool wait_for_finish); + + /** + * @brief Interrupt and reset audio state + * + * Used in tandem with Cache(), only for audio clips. Cache() will decode and send audio continually as it's + * repeatedly called. If the audio stream needs to be interrupted and moved somewhere else for any reason + * (e.g. the user seeked somewhere else), then it's necessary to call ResetAudio() to signal the cacher to + * seek to the next place indicated by Cache(). + */ + void ResetAudio(); + + /** + * @brief Retrieve current media width + * + * In some situations, the actual media we're using may be a different resolution to how we're treating it (e.g. + * lower resolution proxies). While most functions will happily treat the media as its original resolution, some + * processes will need the absolute resolution from the file which can be acquired here. + * + * Only call after the thread has been opened by Open(). + * + * @return + * + * The true width of the current video file. + */ + int media_width(); + + /** + * @brief Retrieve current media height + * + * See media_width(). + * + * Only call after the thread has been opened by Open(). + * + * @return + * + * The true height of the current video file. + */ + int media_height(); + + /** + * @brief Retrieve media time base + * + * For some timing operations, it's necessary to use the source media's timebase. Similar to media_width() and + * media_height(), we need the accurate timebase from the file as a proxy's timebase may or may not be the same + * as the source file. + * + * Only call after the thread has been opened by Open(). + * + * @return + * + * The timebase of the file. + */ + AVRational media_time_base(); + + /** + * @brief Get cacher queue object + * + * @return + * + * A pointer to the cacher's internal frame queue + */ + 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. + */ + Clip* clip; + + /** + * @brief Frame queue + * + * Valid fames are cached into this, which also does memory handling when necessary. + */ + ClipQueue queue_; + + /** + * @brief Main wait condition + * + * Used with Clip::cache_lock as the main block while the the Cacher thread isn't running. Wake this condition + * to start caching. + */ + QWaitCondition wait_cond_; + + /** + * @brief Main thread wait condition + * + * Used with main_thread_lock_ to block Cache() while waiting for a response from the cacher thread. + */ + QWaitCondition main_thread_wait_; + + /** + * @brief Main thread mutex + * + * Used with main_thread_wait_ to block Cache() while waiting for a response from the cacher thread. + */ + QMutex main_thread_lock_; + + /** + * @brief Retrieve() wait condition + * + * Used with retrieve_lock_ to block Retrieve() if the cacher hasn't retrieved the correct frame yet. + */ + QWaitCondition retrieve_wait_; + + /** + * @brief Retrieve() mutex + * + * Used with retrieve_wait_ to block Retrieve() if the cacher hasn't retrieved the correct frame yet. + */ + QMutex retrieve_lock_; + + /** + * @brief Set and used by CacheAudioWorker if the decoder receives an EOF. + * + * Deprecated. CacheAudioWorker() is functional but probably should be rewritten. + */ + bool reached_end; + + /** + * @brief Current Sequence playhead set by Cache() + */ + long playhead_; + + /** + * @brief Current Sequence scrubbing state set by Cache() + */ + bool scrubbing_; + + /** + * @brief Current Sequence playback speed set by Cache() + */ + int playback_speed_; + + /** + * @brief Current nested Sequence hierarchy set by Cache() + */ + QVector nests_; + + /** + * @brief Signal cache to continue operation after one cycle rather than wait for another signal + * + * Each cycle of the cacher thread (see run()) will set this to false in the beginning. Each call of Cache() will set + * this to **TRUE**. If this variable is **TRUE**, the cacher won't wait for another signal before starting the next + * cache cycle, and will instead just start it. + * + * Used if Cache() is called and interrupts the cacher while it's already running so that the cacher will restart + * itself automatically rather than wait for the next cache signal. + */ + bool queued_; + + /** + * @brief Interrupt the current cache cycle + * + * A cache cycle will cache several frames at a time. Since decoding can be strenuous and time consuming, the + * cycle can be interrupted if it needs to abruptly start caching somewhere else. Best used in tandem with + * queued_ to automatically start the next cache cycle. + */ + bool interrupt_; + + // ffmpeg media handling + /** + * @brief FFmpeg format/file context - used for media decoding + */ + AVFormatContext* formatCtx; + + /** + * @brief FFmpeg decoder context - used for media decoding + */ + AVCodecContext* codecCtx; + + /** + * @brief FFmpeg stream - used for media decoding + */ + AVStream* stream; + + /** + * @brief FFmpeg packet - used for media decoding + */ + AVPacket* pkt; + + /** + * @brief FFmpeg frame - used for media decoding + * + * This is usually used as a raw decoded frame before the RGBA conversion/AVFilter stack. Converted/filtered frames go + * into Cacher::queue. + */ + AVFrame* frame_; + + /** + * @brief Retrieved frame reference for Retrieve() + * + * If a frame was found by either Cache() or CacheVideoWorker(), it's set here. If no frame is ready yet, this is set + * to `nullptr`. + */ + AVFrame* retrieved_frame = nullptr; + + // converters/filters + /** + * @brief FFmpeg filter stack + * + * Used for conversion from the media's pixel format to RGBA for OpenGL. Also any other FFmpeg filters are implemented + * here if necessary (e.g. yadif for deinterlacing). GLSL effects are preferred when available since FFmpeg filters + * aren't always fast enough for realtime playback. + */ + AVFilterGraph* filter_graph; + + /** + * @brief FFmpeg buffer source + * + * Raw decoded frames are added to this for conversion/filtering + */ + AVFilterContext* buffersrc_ctx; + + /** + * @brief FFmpeg buffer sink + * + * Converted/filtered frames are retrieved from here and sent to Cacher::queue. + */ + AVFilterContext* buffersink_ctx; + + /** + * @brief FFmpeg codec reference + */ + AVCodec* codec; + + /** + * @brief Options set by the cacher for FFmpeg's decoders (settings like multithreading or other optimizations) + */ + AVDictionary* opts; + + // audio playback variables + /** + * @brief Internal audio reset variable + * + * Set by AudioReset() and read by CacheAudioWorker() when the audio state needs to be interrupted and reset. + */ + bool audio_reset_; + + /** + * @brief Internal reverse target variable + * + * Used by CacheAudioWorker() to stitch audio frames together when reversing. Stores the current frame's timestamp + * so it knows how much to decode up to when it backtracks and decodes the next samples. + */ + int64_t reverse_target_; + + /** + * @brief Internal frame sample index variable + * + * Used by CacheAudioWorker() to mark which part of the audio frame to read from + */ + int frame_sample_index_; + + /** + * @brief Internal audio buffer write variable + * + * Used by CacheAudioWorker() to mark which part of the audio buffer to write to + */ + qint64 audio_buffer_write; + + /** + * @brief Internal variable that holds the playhead the last time the audio state was reset + */ + long audio_target_frame; + + /** + * @brief Main while loop condition to determine whether thread should continue looping + * + * Open() sets this to **TRUE**, Close() sets this to **FALSE**. If it's false, the main loop in run() will exit and + * the thread will exit cleanly. It's not recommended to set this variable directly, use Open() and Close() instead. + */ + bool caching_; + + /** + * @brief Internal variable for whether the current Cacher state is valid or not + * + * If there was an error opening the Cacher for any reason, this will be false. + */ + bool is_valid_state_; + + /** + * @brief Internal function for opening the file handles and decoder + * + * After the thread has started, it'll call this function to start all resources necessary for caching. Any + * FFmpeg decoding variables and filters are set up here. + * + * This is + * fundamentally different from Open(), this is only meant to be called within the cacher thread and never from + * outside and doesn't start the thread like Open() does. + */ + void OpenWorker(); + + /** + * @brief Internal function for starting a cache cycle + * + * This used to have more function, but now just differentiates between CacheVideoWorker() for video clips and + * CacheAudioWorker() for audio clips. + */ + void CacheWorker(); + + /** + * @brief Internal function for closing cacher + * + * Called if the main thread loop in run() exits by setting caching_ to **FALSE**. Free's up handles and memory + * allocated by OpenWorker(). + */ + void CloseWorker(); + + /** + * @brief Internal function for resetting audio state + * + * This used to be a common function, but is now simply a legacy function for CacheAudioWorker(). Resets and + * flushes decoders and seeks to the correct timestamp. + */ + void Reset(); + + /** + * @brief Internal function for setting retrieved_frame and waking up any threads waiting for it + * + * @param f + * + * The frame to set as the retrieved frame. + */ + void SetRetrievedFrame(AVFrame* f); + + /** + * @brief Internal function to wake an external calling thread + * + * In some situations, Cache() may wait for the cacher to respond before returning. This is to assist in thread + * synchronization, making sure the cacher has started working and has locked any resources it needs before any + * other threads can access them (e.g. with a function like Retrieve() ). This must be called at the start of + * any CacheVideoWorker() or CacheAudioWorker() control paths to ensure the render thread doesn't get stuck. + */ + void WakeMainThread(); + + /** + * @brief Retrieve frame from decoder + * + * Retrieves the next decoded frame from the decoder. Depending on the source media, this frame may or may not be + * suitable for usage later in the pipeline as it may or may not be the correct pixel/sample format. For a suitable + * frame for the pipeline, use RetrieveFrameAndProcess() instead (which in turn uses this function anyway). + * + * @param f + * + * Frame buffer to decode frame into + * + * @return + * + * FFmpeg error code (>= 0 on success, a negative error code on failure) + */ + int RetrieveFrameFromDecoder(AVFrame* f); + + /** + * @brief Retrieve frame from decoder and run it through filter stack + * + * Retrieves the next decoded frame and runs it through the AVFilter stack to create an RGBA frame compatible with + * the rest of the pipeline and OpenGL. Use this function if you need a ready-made frame. + * + * @param f + * + * A pointer to an AVFrame object. It does not need to be allocated, as this function allocates an AVFrame itself. + * You'll also need to free it later with av_frame_free() (though ClipQueue will do this automatically if the frame is + * added to it). + * + * @return + * + * FFmpeg error code (>= 0 on success, a negative error code on failure) + */ + int RetrieveFrameAndProcess(AVFrame **f); + + /** + * @brief Internal video caching function + * + * Performs one video cache cycle. Seeks the media and cleans old frames from the queue if necessary. Decodes frames + * and adds them to the queue (after calculating whether they're necessary). + */ + void CacheVideoWorker(); + + /** + * @brief Internal audio caching function + * + * Perform one audio cache cycle. Retrieves audio from decoder, reverses and changes speed if necessary, and sends + * audio to the audio buffer which will later be sent to the audio output device. + */ + void CacheAudioWorker(); + + /** + * @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 ea9278a5b..031ac402d 100644 --- a/rendering/clipqueue.cpp +++ b/rendering/clipqueue.cpp @@ -1,105 +1,105 @@ -/*** - - 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 "clipqueue.h" - - -ClipQueue::ClipQueue() -{ - -} - -ClipQueue::~ClipQueue() -{ - clear(); -} - -void ClipQueue::lock() -{ - queue_lock.lock(); -} - -bool ClipQueue::tryLock() -{ - return queue_lock.tryLock(); -} - -void ClipQueue::unlock() -{ - queue_lock.unlock(); -} - -void ClipQueue::append(AVFrame *frame) -{ - queue.append(frame); -} - -AVFrame *ClipQueue::at(int i) -{ - return queue.at(i); -} - -AVFrame *ClipQueue::first() -{ - return queue.first(); -} - -AVFrame *ClipQueue::last() -{ - return queue.last(); -} - -void ClipQueue::removeFirst() -{ - removeAt(0); -} - -void ClipQueue::removeLast() -{ - removeAt(queue.size()-1); -} - -void ClipQueue::removeAt(int i) -{ - av_frame_free(&queue[i]); - queue.removeAt(i); -} - -void ClipQueue::clear() -{ - while (queue.size() > 0) { - removeAt(0); - } -} - -int ClipQueue::size() -{ - return queue.size(); -} - -bool ClipQueue::isEmpty() -{ - return queue.isEmpty(); -} - -bool ClipQueue::contains(AVFrame *frame) -{ - return queue.contains(frame); -} +/*** + + 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 "clipqueue.h" + + +ClipQueue::ClipQueue() +{ + +} + +ClipQueue::~ClipQueue() +{ + clear(); +} + +void ClipQueue::lock() +{ + queue_lock.lock(); +} + +bool ClipQueue::tryLock() +{ + return queue_lock.tryLock(); +} + +void ClipQueue::unlock() +{ + queue_lock.unlock(); +} + +void ClipQueue::append(AVFrame *frame) +{ + queue.append(frame); +} + +AVFrame *ClipQueue::at(int i) +{ + return queue.at(i); +} + +AVFrame *ClipQueue::first() +{ + return queue.first(); +} + +AVFrame *ClipQueue::last() +{ + return queue.last(); +} + +void ClipQueue::removeFirst() +{ + removeAt(0); +} + +void ClipQueue::removeLast() +{ + removeAt(queue.size()-1); +} + +void ClipQueue::removeAt(int i) +{ + av_frame_free(&queue[i]); + queue.removeAt(i); +} + +void ClipQueue::clear() +{ + while (queue.size() > 0) { + removeAt(0); + } +} + +int ClipQueue::size() +{ + return queue.size(); +} + +bool ClipQueue::isEmpty() +{ + return queue.isEmpty(); +} + +bool ClipQueue::contains(AVFrame *frame) +{ + return queue.contains(frame); +} diff --git a/rendering/clipqueue.h b/rendering/clipqueue.h index 7534fb09f..91d3649e7 100644 --- a/rendering/clipqueue.h +++ b/rendering/clipqueue.h @@ -1,185 +1,185 @@ -/*** - - 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 CLIPQUEUE_H -#define CLIPQUEUE_H - -extern "C" { -#include -} - -#include -#include - -/** - * @brief The ClipQueue class - * - * A fairly simple wrapper for a QVector and QMutex that cleans up AVFrames automatically when removing them. - */ -class ClipQueue { -public: - /** - * @brief ClipQueue Constructor - */ - ClipQueue(); - - /** - * @brief ClipQueue Destructor - * - * Automatically clears queue freeing any memory consumed by any AVFrames - */ - ~ClipQueue(); - - // Thread safety (QMutex compatible) - /** - * @brief Lock queue mutex - * - * Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::lock() for more - * information. - */ - void lock(); - - /** - * @brief Try to lock queue mutex - * - * Used for multithreading to ensure queue is only accessed by one thread at a time. Tries to lock, but doesn't block - * the calling thread and wait if it can't lock it. See QMutex::tryLock() for more information. - * - * @return - * - * **TRUE** if the lock succeeded, **FALSE** if not. - */ - bool tryLock(); - - /** - * @brief Unlock queue mutex - * - * Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::unlock() for more - * information. - */ - void unlock(); - - // Array handling (QVector compatible) - /** - * @brief Add a frame to the end of the queue - * - * @param frame - * - * The frame to add - */ - void append(AVFrame* frame); - - /** - * @brief Retrieve a frame at a certain index - * - * @param i - * - * Index to retrieve frame from - * - * @return - * - * AVFrame at this index - */ - AVFrame* at(int i); - - /** - * @brief Retrieve first frame in the queue - * - * @return - * - * The first AVFrame in the queue - */ - AVFrame* first(); - - /** - * @brief Retrieve last frame in the queue - * - * @return - * - * The last AVFrame in the queue - */ - AVFrame* last(); - - /** - * @brief Remove first frame in the queue - * - * Frees all memory occupied by this frame and removes it from the queue - */ - void removeFirst(); - - /** - * @brief Remove last frame in the queue - * - * Frees all memory occupied by this frame and removes it from the queue - */ - void removeLast(); - - /** - * @brief Remove frame in the queue at a certain index - * - * Frees all memory occupied by this frame and removes it from the queue - * - * @param i - * - * Index to remove a frame at - */ - void removeAt(int i); - - /** - * @brief Clear entire queue - * - * Frees all memory occupied by all frames and clears the entire queue - */ - void clear(); - - /** - * @brief Retrieve current size of the queue - * - * @return - * - * Current the current size of the queue. All indexes in the queue are guaranteed to be valid references to an - * AVFrame. - */ - int size(); - - /** - * @brief Returns whether the queue is empty of not. - * - * @return - * - * **TRUE** if the queue is empty and contains no frames, **FALSE** if not. - */ - bool isEmpty(); - - /** - * @brief Returns whether the queue contains a frame or not - * - * @return - * - * **TRUE** if the queue contains the specified frame, **FALSE** if not. - */ - bool contains(AVFrame* frame); - -private: - QVector queue; - QMutex queue_lock; -}; - -#endif // CLIPQUEUE_H +/*** + + 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 CLIPQUEUE_H +#define CLIPQUEUE_H + +extern "C" { +#include +} + +#include +#include + +/** + * @brief The ClipQueue class + * + * A fairly simple wrapper for a QVector and QMutex that cleans up AVFrames automatically when removing them. + */ +class ClipQueue { +public: + /** + * @brief ClipQueue Constructor + */ + ClipQueue(); + + /** + * @brief ClipQueue Destructor + * + * Automatically clears queue freeing any memory consumed by any AVFrames + */ + ~ClipQueue(); + + // Thread safety (QMutex compatible) + /** + * @brief Lock queue mutex + * + * Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::lock() for more + * information. + */ + void lock(); + + /** + * @brief Try to lock queue mutex + * + * Used for multithreading to ensure queue is only accessed by one thread at a time. Tries to lock, but doesn't block + * the calling thread and wait if it can't lock it. See QMutex::tryLock() for more information. + * + * @return + * + * **TRUE** if the lock succeeded, **FALSE** if not. + */ + bool tryLock(); + + /** + * @brief Unlock queue mutex + * + * Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::unlock() for more + * information. + */ + void unlock(); + + // Array handling (QVector compatible) + /** + * @brief Add a frame to the end of the queue + * + * @param frame + * + * The frame to add + */ + void append(AVFrame* frame); + + /** + * @brief Retrieve a frame at a certain index + * + * @param i + * + * Index to retrieve frame from + * + * @return + * + * AVFrame at this index + */ + AVFrame* at(int i); + + /** + * @brief Retrieve first frame in the queue + * + * @return + * + * The first AVFrame in the queue + */ + AVFrame* first(); + + /** + * @brief Retrieve last frame in the queue + * + * @return + * + * The last AVFrame in the queue + */ + AVFrame* last(); + + /** + * @brief Remove first frame in the queue + * + * Frees all memory occupied by this frame and removes it from the queue + */ + void removeFirst(); + + /** + * @brief Remove last frame in the queue + * + * Frees all memory occupied by this frame and removes it from the queue + */ + void removeLast(); + + /** + * @brief Remove frame in the queue at a certain index + * + * Frees all memory occupied by this frame and removes it from the queue + * + * @param i + * + * Index to remove a frame at + */ + void removeAt(int i); + + /** + * @brief Clear entire queue + * + * Frees all memory occupied by all frames and clears the entire queue + */ + void clear(); + + /** + * @brief Retrieve current size of the queue + * + * @return + * + * Current the current size of the queue. All indexes in the queue are guaranteed to be valid references to an + * AVFrame. + */ + int size(); + + /** + * @brief Returns whether the queue is empty of not. + * + * @return + * + * **TRUE** if the queue is empty and contains no frames, **FALSE** if not. + */ + bool isEmpty(); + + /** + * @brief Returns whether the queue contains a frame or not + * + * @return + * + * **TRUE** if the queue contains the specified frame, **FALSE** if not. + */ + bool contains(AVFrame* frame); + +private: + QVector queue; + QMutex queue_lock; +}; + +#endif // CLIPQUEUE_H diff --git a/rendering/exportthread.cpp b/rendering/exportthread.cpp index b859f7876..bd8d3edcd 100644 --- a/rendering/exportthread.cpp +++ b/rendering/exportthread.cpp @@ -1,705 +1,705 @@ -/*** - - 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 "exportthread.h" - -extern "C" { -#include -#include -#include -#include -} - -#include -#include -#include -#include -#include - -#include "global/global.h" -#include "panels/panels.h" -#include "ui/viewerwidget.h" -#include "rendering/renderthread.h" -#include "rendering/renderfunctions.h" -#include "rendering/audio.h" -#include "ui/mainwindow.h" -#include "global/debug.h" - -ExportThread::ExportThread(const ExportParams ¶ms, - const VideoCodecParams& vparams, - QObject *parent) : - QThread(parent), - params_(params), - vcodec_params_(vparams), - interrupt_(false), - fmt_ctx(nullptr), - video_stream(nullptr), - vcodec(nullptr), - vcodec_ctx(nullptr), - video_frame(nullptr), - sws_ctx(nullptr), - audio_stream(nullptr), - acodec(nullptr), - audio_frame(nullptr), - sws_frame(nullptr), - swr_frame(nullptr), - acodec_ctx(nullptr), - swr_ctx(nullptr), - vpkt_alloc(false), - apkt_alloc(false), - c_filename(nullptr) -{ - // Create offscreen surface for rendering while exporting - surface.create(); -} - -bool ExportThread::Encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream) { - ret = avcodec_send_frame(codec_ctx, frame); - if (ret < 0) { - qCritical() << "Failed to send frame to encoder." << ret; - export_error = tr("failed to send frame to encoder (%1)").arg(QString::number(ret)); - return false; - } - - while (ret >= 0) { - ret = avcodec_receive_packet(codec_ctx, packet); - if (ret == AVERROR(EAGAIN)) { - return true; - } else if (ret < 0) { - if (ret != AVERROR_EOF) { - qCritical() << "Failed to receive packet from encoder." << ret; - export_error = tr("failed to receive packet from encoder (%1)").arg(QString::number(ret)); - } - return false; - } - - packet->stream_index = stream->index; - - av_packet_rescale_ts(packet, codec_ctx->time_base, stream->time_base); - - av_interleaved_write_frame(ofmt_ctx, packet); - av_packet_unref(packet); - } - return true; -} - -bool ExportThread::SetupVideo() { - // if video is disabled, no setup necessary - if (!params_.video_enabled) return true; - - // find video encoder - vcodec = avcodec_find_encoder(static_cast(params_.video_codec)); - if (!vcodec) { - qCritical() << "Could not find video encoder"; - export_error = tr("could not video encoder for %1").arg(QString::number(params_.video_codec)); - return false; - } - - // create video stream - video_stream = avformat_new_stream(fmt_ctx, vcodec); - video_stream->id = 0; - if (!video_stream) { - qCritical() << "Could not allocate video stream"; - export_error = tr("could not allocate video stream"); - return false; - } - - // allocate context - // vcodec_ctx = video_stream->codec; - vcodec_ctx = avcodec_alloc_context3(vcodec); - if (!vcodec_ctx) { - qCritical() << "Could not allocate video encoding context"; - export_error = tr("could not allocate video encoding context"); - return false; - } - - // setup context - vcodec_ctx->codec_id = static_cast(params_.video_codec); - vcodec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; - vcodec_ctx->width = params_.video_width; - vcodec_ctx->height = params_.video_height; - vcodec_ctx->sample_aspect_ratio = {1, 1}; - vcodec_ctx->pix_fmt = static_cast(vcodec_params_.pix_fmt); - vcodec_ctx->framerate = av_d2q(params_.video_frame_rate, INT_MAX); - if (params_.video_compression_type == COMPRESSION_TYPE_CBR) { - vcodec_ctx->bit_rate = qRound(params_.video_bitrate * 1000000); - } - vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate); - video_stream->time_base = vcodec_ctx->time_base; - - if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { - vcodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - } - - // Some codecs require special settings so we set that up here - switch (vcodec_ctx->codec_id) { - - /// H.264 specific settings - case AV_CODEC_ID_H264: - case AV_CODEC_ID_H265: - switch (params_.video_compression_type) { - case COMPRESSION_TYPE_CFR: - av_opt_set(vcodec_ctx->priv_data, "crf", QString::number(static_cast(params_.video_bitrate)).toUtf8(), AV_OPT_SEARCH_CHILDREN); - break; - } - break; - default: - break; - } - - // Set export to be multithreaded - AVDictionary* opts = nullptr; - if (vcodec_params_.threads == 0) { - av_dict_set(&opts, "threads", "auto", 0); - } else { - av_dict_set(&opts, "threads", QString::number(vcodec_params_.threads).toUtf8(), 0); - } - - // Open video encoder - ret = avcodec_open2(vcodec_ctx, vcodec, &opts); - if (ret < 0) { - qCritical() << "Could not open output video encoder." << ret; - export_error = tr("could not open output video encoder (%1)").arg(QString::number(ret)); - return false; - } - - // Copy video encoder parameters to output stream - ret = avcodec_parameters_from_context(video_stream->codecpar, vcodec_ctx); - if (ret < 0) { - qCritical() << "Could not copy video encoder parameters to output stream." << ret; - export_error = tr("could not copy video encoder parameters to output stream (%1)").arg(QString::number(ret)); - return false; - } - - // Create raw AVFrame that will contain the RGBA buffer straight from compositing - video_frame = av_frame_alloc(); - av_frame_make_writable(video_frame); - video_frame->format = AV_PIX_FMT_RGBA; - 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( - params_.sequence->width(), - params_.sequence->height(), - AV_PIX_FMT_RGBA, - params_.video_width, - params_.video_height, - vcodec_ctx->pix_fmt, - SWS_BILINEAR, - nullptr, - nullptr, - nullptr - ); - - return true; -} - -bool ExportThread::SetupAudio() { - // if video is disabled, no setup necessary - if (!params_.audio_enabled) return true; - - // Find encoder for this codec - acodec = avcodec_find_encoder(static_cast(params_.audio_codec)); - if (!acodec) { - qCritical() << "Could not find audio encoder"; - export_error = tr("could not audio encoder for %1").arg(QString::number(params_.audio_codec)); - return false; - } - - // Allocate audio stream - audio_stream = avformat_new_stream(fmt_ctx, acodec); - if (audio_stream == nullptr) { - qCritical() << "Could not allocate audio stream"; - export_error = tr("could not allocate audio stream"); - return false; - } - - // Set audio stream's ID to 1 - audio_stream->id = 1; - - // set sample rate to use for project - audio_rendering_rate = params_.audio_sampling_rate; - - // Allocate encoding context - acodec_ctx = avcodec_alloc_context3(acodec); - if (!acodec_ctx) { - qCritical() << "Could not find allocate audio encoding context"; - export_error = tr("could not allocate audio encoding context"); - return false; - } - - // Set up encoding context - acodec_ctx->codec_id = static_cast(params_.audio_codec); - acodec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; - acodec_ctx->sample_rate = params_.audio_sampling_rate; - acodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is what the user sets the output audio to) - acodec_ctx->channels = av_get_channel_layout_nb_channels(acodec_ctx->channel_layout); - acodec_ctx->sample_fmt = acodec->sample_fmts[0]; - acodec_ctx->bit_rate = params_.audio_bitrate * 1000; - - acodec_ctx->time_base.num = 1; - acodec_ctx->time_base.den = params_.audio_sampling_rate; - audio_stream->time_base = acodec_ctx->time_base; - - if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { - acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - } - - // Open encoder - ret = avcodec_open2(acodec_ctx, acodec, nullptr); - if (ret < 0) { - qCritical() << "Could not open output audio encoder." << ret; - export_error = tr("could not open output audio encoder (%1)").arg(QString::number(ret)); - return false; - } - - // Copy parameters from the codec context (set up above) to the output stream - ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx); - if (ret < 0) { - qCritical() << "Could not copy audio encoder parameters to output stream." << ret; - export_error = tr("could not copy audio encoder parameters to output stream (%1)").arg(QString::number(ret)); - return false; - } - - // init audio resampler context - swr_ctx = swr_alloc_set_opts( - nullptr, - acodec_ctx->channel_layout, - acodec_ctx->sample_fmt, - acodec_ctx->sample_rate, - params_.sequence->audio_layout(), - AV_SAMPLE_FMT_S16, - acodec_ctx->sample_rate, - 0, - nullptr - ); - swr_init(swr_ctx); - - // initialize raw audio frame - audio_frame = av_frame_alloc(); - audio_frame->sample_rate = acodec_ctx->sample_rate; - audio_frame->nb_samples = acodec_ctx->frame_size; - - if (audio_frame->nb_samples == 0) { - // FIXME: Magic number. I don't know what to put here and truthfully I don't even know if it matters. - audio_frame->nb_samples = 256; - } - - // TODO change this to support surround/mono sound in the future (this is whatever format they're held in the internal buffer) - audio_frame->channel_layout = AV_CH_LAYOUT_STEREO; - - audio_frame->format = AV_SAMPLE_FMT_S16; - audio_frame->channels = av_get_channel_layout_nb_channels(audio_frame->channel_layout); - av_frame_make_writable(audio_frame); - ret = av_frame_get_buffer(audio_frame, 0); - if (ret < 0) { - qCritical() << "Could not allocate audio buffer." << ret; - export_error = tr("could not allocate audio buffer (%1)").arg(QString::number(ret)); - return false; - } - aframe_bytes = av_samples_get_buffer_size(nullptr, audio_frame->channels, audio_frame->nb_samples, static_cast(audio_frame->format), 0); - - av_init_packet(&audio_pkt); - - // init converted audio frame - swr_frame = av_frame_alloc(); - swr_frame->channel_layout = acodec_ctx->channel_layout; - swr_frame->channels = acodec_ctx->channels; - swr_frame->sample_rate = acodec_ctx->sample_rate; - swr_frame->format = acodec_ctx->sample_fmt; - swr_frame->nb_samples = acodec_ctx->frame_size; - av_frame_get_buffer(swr_frame, 0); - - av_frame_make_writable(swr_frame); - - return true; -} - -bool ExportThread::SetupContainer() { - - // Set up output context (using the filename as the format specification) - - avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, c_filename); - if (fmt_ctx == nullptr) { - - // Failed to create the output format context. Exit the export and throw an error. - - qCritical() << "Could not create output context"; - export_error = tr("could not create output format context"); - return false; - } - - ret = avio_open(&fmt_ctx->pb, c_filename, AVIO_FLAG_WRITE); - if (ret < 0) { - - // Failed to get a valid write handle for the exported file. Exit the export and throw an error. - - qCritical() << "Could not open output file." << ret; - export_error = tr("could not open output file (%1)").arg(QString::number(ret)); - return false; - } - - return true; -} - -void ExportThread::Export() -{ - // Copy filename from QString to const char - QByteArray ba = params_.filename.toUtf8(); - c_filename = new char[ba.size()+1]; - strcpy(c_filename, ba.data()); - - // Set up file container - if (!SetupContainer()) { - return; - } - - // If video is enabled, set it up in the container now - if (!SetupVideo()) { - return; - } - - // If audio is enabled, set it up in the container now - if (!SetupAudio()) { - return; - } - - // Write the container header based on what's been set up above - ret = avformat_write_header(fmt_ctx, nullptr); - if (ret < 0) { - - // FFmpeg failed to write the header, so cancel the export and throw an error - - qCritical() << "Could not write output file header." << ret; - export_error = tr("could not write output file header (%1)").arg(QString::number(ret)); - - return; - } - - // Count audio samples in file (used for calculating PTS) - long file_audio_samples = 0; - - // Set up timing variables, used for determining rendering ETA - qint64 frame_start_time, frame_time, avg_time, eta, total_time = 0; - - // Frame counters - used for generating encoding statistics (e.g. average frame time, ETA, etc.) - 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(); - - // Override connection from RenderThread - 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 (params_.sequence->playhead <= params_.end_frame && !interrupt_) { - - // Start timing how long this frame will take - frame_start_time = QDateTime::currentMSecsSinceEpoch(); - - // If we're exporting audio, run compose_audio() which will write mixed audio to the internal audio buffer - if (params_.audio_enabled) { - waiting_for_audio_ = true; - SetAudioWakeObject(this); - 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, params_.sequence, 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4); - - // Wait for RenderThread to return - waitCond.wait(&mutex); - - if (interrupt_) { - return; - } - - // If the RenderThread failed, do another render - } while (renderer->did_texture_fail()); - - if (interrupt_) { - return; - } - - } - - // Get the current sequence playhead in seconds (used for timestamp calculations later on) - 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 - if (params_.video_enabled) { - - // - // - I'm not sure why, but we have to alloc/free sws_frame every frame, or it breaks GIF exporting. - // - (i.e. GIFs get stuck on the first frame) - // - The same problem/solution can be seen here: https://stackoverflow.com/a/38997739 - // - Perhaps this is the intended way to use swscale, but it seems inefficient. - // - Anyway, here we are. - // - - // Construct destination pixel format frame - sws_frame = av_frame_alloc(); - sws_frame->format = vcodec_ctx->pix_fmt; - sws_frame->width = params_.video_width; - sws_frame->height = params_.video_height; - av_frame_get_buffer(sws_frame, 0); - - // Convert raw RGBA buffer to format expected by the encoder - sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize); - sws_frame->pts = qRound(timecode_secs/av_q2d(vcodec_ctx->time_base)); - - // Send frame to encoder - if (!Encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) { - return; - } - - av_frame_free(&sws_frame); - sws_frame = nullptr; - } - - // If we're exporting audio, copy audio from the buffer into an AVFrame for encoding - if (params_.audio_enabled) { - - if (waiting_for_audio_ && !interrupt_) { - waitCond.wait(&mutex); - } - - // Make sure nothing is writing while we're retrieving - audio_write_lock.lock(); - - // Check if the count of encoded samples exceeds the current Sequence playhead, in which case we don't need to - // encode any audio at this moment - while (!interrupt_ && file_audio_samples <= (timecode_secs*params_.audio_sampling_rate)) { - - // Copy samples from audio buffer to AVFrame - int adjusted_read = audio_ibuffer_read%audio_ibuffer_size; - int copylen = qMin(aframe_bytes, audio_ibuffer_size-adjusted_read); - memcpy(audio_frame->data[0], audio_ibuffer+adjusted_read, copylen); - memset(audio_ibuffer+adjusted_read, 0, copylen); - audio_ibuffer_read += copylen; - - // If we reached the end of the buffer without reaching the end of the frame, do another copy from the start - // of the buffer - if (copylen < aframe_bytes) { - int remainder_len = aframe_bytes-copylen; - memcpy(audio_frame->data[0]+copylen, audio_ibuffer, remainder_len); - memset(audio_ibuffer, 0, remainder_len); - audio_ibuffer_read += remainder_len; - } - - // Convert raw audio samples to the destination codec's sample format - swr_convert_frame(swr_ctx, swr_frame, audio_frame); - - // The timestamp is set to the current count of audio samples (since the audio stream's timebase is - swr_frame->pts = file_audio_samples; - - // Send frame to encoder - if (!Encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) { - return; - } - - // Increment by the frame's number of samples - file_audio_samples += swr_frame->nb_samples; - } - - audio_write_lock.unlock(); - - } - - // 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 - 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(params_.sequence->playhead - params_.start_frame) / double(params_.end_frame - params_.start_frame)) * 100.0), eta); - - // Increment sequence playhead - params_.sequence->playhead++; - - // Increment frame count (used for generating encoding statistics above) - frame_count++; - } - - // Restore original connection from RenderThread - disconnect(renderer, SIGNAL(ready()), this, SLOT(wake())); - connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint())); - - if (interrupt_) { - return; - } - - if (params_.video_enabled) vpkt_alloc = true; - if (params_.audio_enabled) apkt_alloc = true; - - olive::Global->set_export_state(false); - - // If audio is enabled, flush the rest of the audio out of swresample - if (params_.audio_enabled) { - - do { - - swr_convert_frame(swr_ctx, swr_frame, nullptr); - if (swr_frame->nb_samples == 0) break; - swr_frame->pts = file_audio_samples; - if (!Encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) { - return; - } - file_audio_samples += swr_frame->nb_samples; - - } while (swr_frame->nb_samples > 0); - - } - - if (interrupt_) { - return; - } - - // Flush remaining packets out of video and audio encoders by sending a null frame - if (params_.video_enabled) { - Encode(fmt_ctx, vcodec_ctx, nullptr, &video_pkt, video_stream); - } - if (params_.audio_enabled) { - Encode(fmt_ctx, acodec_ctx, nullptr, &audio_pkt, audio_stream); - } - - // Write container trailer - ret = av_write_trailer(fmt_ctx); - if (ret < 0) { - qCritical() << "Could not write output file trailer." << ret; - export_error = tr("could not write output file trailer (%1)").arg(QString::number(ret)); - return; - } - - emit ProgressChanged(100, 0); -} - -void ExportThread::Cleanup() -{ - if (fmt_ctx != nullptr) { - avio_closep(&fmt_ctx->pb); - avformat_free_context(fmt_ctx); - } - - if (acodec_ctx != nullptr) { - avcodec_close(acodec_ctx); - avcodec_free_context(&acodec_ctx); - } - - if (audio_frame != nullptr) { - av_frame_free(&audio_frame); - } - - if (apkt_alloc) { - av_packet_unref(&audio_pkt); - } - - if (vcodec_ctx != nullptr) { - avcodec_close(vcodec_ctx); - avcodec_free_context(&vcodec_ctx); - } - - if (video_frame != nullptr) { - av_frame_free(&video_frame); - } - - if (vpkt_alloc) { - av_packet_unref(&video_pkt); - } - - if (sws_ctx != nullptr) { - sws_freeContext(sws_ctx); - } - - if (swr_ctx != nullptr) { - swr_free(&swr_ctx); - } - - if (swr_frame != nullptr) { - av_frame_free(&swr_frame); - } - - if (sws_frame != nullptr) { - av_frame_free(&sws_frame); - } - - delete [] c_filename; -} - -void ExportThread::run() { - // Ensure sequence isn't currently playing - panel_sequence_viewer->pause(); - - // Seek to the first frame we're exporting - panel_sequence_viewer->seek(params_.start_frame); - - // Lock mutex (used for thread synchronizations) - mutex.lock(); - - // Run export function (which will return if there's a failure) - Export(); - - mutex.unlock(); - - // Clean up anything that was allocated in Export() (whether it succeeded or not) - Cleanup(); -} - -const QString &ExportThread::GetError() { - return export_error; -} - -bool ExportThread::WasInterrupted() -{ - return interrupt_; -} - -void ExportThread::Interrupt() -{ - mutex.lock(); - interrupt_ = true; - waitCond.wakeAll(); - mutex.unlock(); -} - -void ExportThread::play_wake() -{ - mutex.lock(); - waiting_for_audio_ = false; - waitCond.wakeAll(); - mutex.unlock(); -} - -void ExportThread::wake() { - mutex.lock(); - waitCond.wakeAll(); - mutex.unlock(); -} +/*** + + 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 "exportthread.h" + +extern "C" { +#include +#include +#include +#include +} + +#include +#include +#include +#include +#include + +#include "global/global.h" +#include "panels/panels.h" +#include "ui/viewerwidget.h" +#include "rendering/renderthread.h" +#include "rendering/renderfunctions.h" +#include "rendering/audio.h" +#include "ui/mainwindow.h" +#include "global/debug.h" + +ExportThread::ExportThread(const ExportParams ¶ms, + const VideoCodecParams& vparams, + QObject *parent) : + QThread(parent), + params_(params), + vcodec_params_(vparams), + interrupt_(false), + fmt_ctx(nullptr), + video_stream(nullptr), + vcodec(nullptr), + vcodec_ctx(nullptr), + video_frame(nullptr), + sws_ctx(nullptr), + audio_stream(nullptr), + acodec(nullptr), + audio_frame(nullptr), + sws_frame(nullptr), + swr_frame(nullptr), + acodec_ctx(nullptr), + swr_ctx(nullptr), + vpkt_alloc(false), + apkt_alloc(false), + c_filename(nullptr) +{ + // Create offscreen surface for rendering while exporting + surface.create(); +} + +bool ExportThread::Encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream) { + ret = avcodec_send_frame(codec_ctx, frame); + if (ret < 0) { + qCritical() << "Failed to send frame to encoder." << ret; + export_error = tr("failed to send frame to encoder (%1)").arg(QString::number(ret)); + return false; + } + + while (ret >= 0) { + ret = avcodec_receive_packet(codec_ctx, packet); + if (ret == AVERROR(EAGAIN)) { + return true; + } else if (ret < 0) { + if (ret != AVERROR_EOF) { + qCritical() << "Failed to receive packet from encoder." << ret; + export_error = tr("failed to receive packet from encoder (%1)").arg(QString::number(ret)); + } + return false; + } + + packet->stream_index = stream->index; + + av_packet_rescale_ts(packet, codec_ctx->time_base, stream->time_base); + + av_interleaved_write_frame(ofmt_ctx, packet); + av_packet_unref(packet); + } + return true; +} + +bool ExportThread::SetupVideo() { + // if video is disabled, no setup necessary + if (!params_.video_enabled) return true; + + // find video encoder + vcodec = avcodec_find_encoder(static_cast(params_.video_codec)); + if (!vcodec) { + qCritical() << "Could not find video encoder"; + export_error = tr("could not video encoder for %1").arg(QString::number(params_.video_codec)); + return false; + } + + // create video stream + video_stream = avformat_new_stream(fmt_ctx, vcodec); + video_stream->id = 0; + if (!video_stream) { + qCritical() << "Could not allocate video stream"; + export_error = tr("could not allocate video stream"); + return false; + } + + // allocate context + // vcodec_ctx = video_stream->codec; + vcodec_ctx = avcodec_alloc_context3(vcodec); + if (!vcodec_ctx) { + qCritical() << "Could not allocate video encoding context"; + export_error = tr("could not allocate video encoding context"); + return false; + } + + // setup context + vcodec_ctx->codec_id = static_cast(params_.video_codec); + vcodec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; + vcodec_ctx->width = params_.video_width; + vcodec_ctx->height = params_.video_height; + vcodec_ctx->sample_aspect_ratio = {1, 1}; + vcodec_ctx->pix_fmt = static_cast(vcodec_params_.pix_fmt); + vcodec_ctx->framerate = av_d2q(params_.video_frame_rate, INT_MAX); + if (params_.video_compression_type == COMPRESSION_TYPE_CBR) { + vcodec_ctx->bit_rate = qRound(params_.video_bitrate * 1000000); + } + vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate); + video_stream->time_base = vcodec_ctx->time_base; + + if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { + vcodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + // Some codecs require special settings so we set that up here + switch (vcodec_ctx->codec_id) { + + /// H.264 specific settings + case AV_CODEC_ID_H264: + case AV_CODEC_ID_H265: + switch (params_.video_compression_type) { + case COMPRESSION_TYPE_CFR: + av_opt_set(vcodec_ctx->priv_data, "crf", QString::number(static_cast(params_.video_bitrate)).toUtf8(), AV_OPT_SEARCH_CHILDREN); + break; + } + break; + default: + break; + } + + // Set export to be multithreaded + AVDictionary* opts = nullptr; + if (vcodec_params_.threads == 0) { + av_dict_set(&opts, "threads", "auto", 0); + } else { + av_dict_set(&opts, "threads", QString::number(vcodec_params_.threads).toUtf8(), 0); + } + + // Open video encoder + ret = avcodec_open2(vcodec_ctx, vcodec, &opts); + if (ret < 0) { + qCritical() << "Could not open output video encoder." << ret; + export_error = tr("could not open output video encoder (%1)").arg(QString::number(ret)); + return false; + } + + // Copy video encoder parameters to output stream + ret = avcodec_parameters_from_context(video_stream->codecpar, vcodec_ctx); + if (ret < 0) { + qCritical() << "Could not copy video encoder parameters to output stream." << ret; + export_error = tr("could not copy video encoder parameters to output stream (%1)").arg(QString::number(ret)); + return false; + } + + // Create raw AVFrame that will contain the RGBA buffer straight from compositing + video_frame = av_frame_alloc(); + av_frame_make_writable(video_frame); + video_frame->format = AV_PIX_FMT_RGBA; + 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( + params_.sequence->width(), + params_.sequence->height(), + AV_PIX_FMT_RGBA, + params_.video_width, + params_.video_height, + vcodec_ctx->pix_fmt, + SWS_BILINEAR, + nullptr, + nullptr, + nullptr + ); + + return true; +} + +bool ExportThread::SetupAudio() { + // if video is disabled, no setup necessary + if (!params_.audio_enabled) return true; + + // Find encoder for this codec + acodec = avcodec_find_encoder(static_cast(params_.audio_codec)); + if (!acodec) { + qCritical() << "Could not find audio encoder"; + export_error = tr("could not audio encoder for %1").arg(QString::number(params_.audio_codec)); + return false; + } + + // Allocate audio stream + audio_stream = avformat_new_stream(fmt_ctx, acodec); + if (audio_stream == nullptr) { + qCritical() << "Could not allocate audio stream"; + export_error = tr("could not allocate audio stream"); + return false; + } + + // Set audio stream's ID to 1 + audio_stream->id = 1; + + // set sample rate to use for project + audio_rendering_rate = params_.audio_sampling_rate; + + // Allocate encoding context + acodec_ctx = avcodec_alloc_context3(acodec); + if (!acodec_ctx) { + qCritical() << "Could not find allocate audio encoding context"; + export_error = tr("could not allocate audio encoding context"); + return false; + } + + // Set up encoding context + acodec_ctx->codec_id = static_cast(params_.audio_codec); + acodec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; + acodec_ctx->sample_rate = params_.audio_sampling_rate; + acodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is what the user sets the output audio to) + acodec_ctx->channels = av_get_channel_layout_nb_channels(acodec_ctx->channel_layout); + acodec_ctx->sample_fmt = acodec->sample_fmts[0]; + acodec_ctx->bit_rate = params_.audio_bitrate * 1000; + + acodec_ctx->time_base.num = 1; + acodec_ctx->time_base.den = params_.audio_sampling_rate; + audio_stream->time_base = acodec_ctx->time_base; + + if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { + acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + // Open encoder + ret = avcodec_open2(acodec_ctx, acodec, nullptr); + if (ret < 0) { + qCritical() << "Could not open output audio encoder." << ret; + export_error = tr("could not open output audio encoder (%1)").arg(QString::number(ret)); + return false; + } + + // Copy parameters from the codec context (set up above) to the output stream + ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx); + if (ret < 0) { + qCritical() << "Could not copy audio encoder parameters to output stream." << ret; + export_error = tr("could not copy audio encoder parameters to output stream (%1)").arg(QString::number(ret)); + return false; + } + + // init audio resampler context + swr_ctx = swr_alloc_set_opts( + nullptr, + acodec_ctx->channel_layout, + acodec_ctx->sample_fmt, + acodec_ctx->sample_rate, + params_.sequence->audio_layout(), + AV_SAMPLE_FMT_S16, + acodec_ctx->sample_rate, + 0, + nullptr + ); + swr_init(swr_ctx); + + // initialize raw audio frame + audio_frame = av_frame_alloc(); + audio_frame->sample_rate = acodec_ctx->sample_rate; + audio_frame->nb_samples = acodec_ctx->frame_size; + + if (audio_frame->nb_samples == 0) { + // FIXME: Magic number. I don't know what to put here and truthfully I don't even know if it matters. + audio_frame->nb_samples = 256; + } + + // TODO change this to support surround/mono sound in the future (this is whatever format they're held in the internal buffer) + audio_frame->channel_layout = AV_CH_LAYOUT_STEREO; + + audio_frame->format = AV_SAMPLE_FMT_S16; + audio_frame->channels = av_get_channel_layout_nb_channels(audio_frame->channel_layout); + av_frame_make_writable(audio_frame); + ret = av_frame_get_buffer(audio_frame, 0); + if (ret < 0) { + qCritical() << "Could not allocate audio buffer." << ret; + export_error = tr("could not allocate audio buffer (%1)").arg(QString::number(ret)); + return false; + } + aframe_bytes = av_samples_get_buffer_size(nullptr, audio_frame->channels, audio_frame->nb_samples, static_cast(audio_frame->format), 0); + + av_init_packet(&audio_pkt); + + // init converted audio frame + swr_frame = av_frame_alloc(); + swr_frame->channel_layout = acodec_ctx->channel_layout; + swr_frame->channels = acodec_ctx->channels; + swr_frame->sample_rate = acodec_ctx->sample_rate; + swr_frame->format = acodec_ctx->sample_fmt; + swr_frame->nb_samples = acodec_ctx->frame_size; + av_frame_get_buffer(swr_frame, 0); + + av_frame_make_writable(swr_frame); + + return true; +} + +bool ExportThread::SetupContainer() { + + // Set up output context (using the filename as the format specification) + + avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, c_filename); + if (fmt_ctx == nullptr) { + + // Failed to create the output format context. Exit the export and throw an error. + + qCritical() << "Could not create output context"; + export_error = tr("could not create output format context"); + return false; + } + + ret = avio_open(&fmt_ctx->pb, c_filename, AVIO_FLAG_WRITE); + if (ret < 0) { + + // Failed to get a valid write handle for the exported file. Exit the export and throw an error. + + qCritical() << "Could not open output file." << ret; + export_error = tr("could not open output file (%1)").arg(QString::number(ret)); + return false; + } + + return true; +} + +void ExportThread::Export() +{ + // Copy filename from QString to const char + QByteArray ba = params_.filename.toUtf8(); + c_filename = new char[ba.size()+1]; + strcpy(c_filename, ba.data()); + + // Set up file container + if (!SetupContainer()) { + return; + } + + // If video is enabled, set it up in the container now + if (!SetupVideo()) { + return; + } + + // If audio is enabled, set it up in the container now + if (!SetupAudio()) { + return; + } + + // Write the container header based on what's been set up above + ret = avformat_write_header(fmt_ctx, nullptr); + if (ret < 0) { + + // FFmpeg failed to write the header, so cancel the export and throw an error + + qCritical() << "Could not write output file header." << ret; + export_error = tr("could not write output file header (%1)").arg(QString::number(ret)); + + return; + } + + // Count audio samples in file (used for calculating PTS) + long file_audio_samples = 0; + + // Set up timing variables, used for determining rendering ETA + qint64 frame_start_time, frame_time, avg_time, eta, total_time = 0; + + // Frame counters - used for generating encoding statistics (e.g. average frame time, ETA, etc.) + 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(); + + // Override connection from RenderThread + 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 (params_.sequence->playhead <= params_.end_frame && !interrupt_) { + + // Start timing how long this frame will take + frame_start_time = QDateTime::currentMSecsSinceEpoch(); + + // If we're exporting audio, run compose_audio() which will write mixed audio to the internal audio buffer + if (params_.audio_enabled) { + waiting_for_audio_ = true; + SetAudioWakeObject(this); + 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, params_.sequence, 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4); + + // Wait for RenderThread to return + waitCond.wait(&mutex); + + if (interrupt_) { + return; + } + + // If the RenderThread failed, do another render + } while (renderer->did_texture_fail()); + + if (interrupt_) { + return; + } + + } + + // Get the current sequence playhead in seconds (used for timestamp calculations later on) + 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 + if (params_.video_enabled) { + + // + // - I'm not sure why, but we have to alloc/free sws_frame every frame, or it breaks GIF exporting. + // - (i.e. GIFs get stuck on the first frame) + // - The same problem/solution can be seen here: https://stackoverflow.com/a/38997739 + // - Perhaps this is the intended way to use swscale, but it seems inefficient. + // - Anyway, here we are. + // + + // Construct destination pixel format frame + sws_frame = av_frame_alloc(); + sws_frame->format = vcodec_ctx->pix_fmt; + sws_frame->width = params_.video_width; + sws_frame->height = params_.video_height; + av_frame_get_buffer(sws_frame, 0); + + // Convert raw RGBA buffer to format expected by the encoder + sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize); + sws_frame->pts = qRound(timecode_secs/av_q2d(vcodec_ctx->time_base)); + + // Send frame to encoder + if (!Encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) { + return; + } + + av_frame_free(&sws_frame); + sws_frame = nullptr; + } + + // If we're exporting audio, copy audio from the buffer into an AVFrame for encoding + if (params_.audio_enabled) { + + if (waiting_for_audio_ && !interrupt_) { + waitCond.wait(&mutex); + } + + // Make sure nothing is writing while we're retrieving + audio_write_lock.lock(); + + // Check if the count of encoded samples exceeds the current Sequence playhead, in which case we don't need to + // encode any audio at this moment + while (!interrupt_ && file_audio_samples <= (timecode_secs*params_.audio_sampling_rate)) { + + // Copy samples from audio buffer to AVFrame + int adjusted_read = audio_ibuffer_read%audio_ibuffer_size; + int copylen = qMin(aframe_bytes, audio_ibuffer_size-adjusted_read); + memcpy(audio_frame->data[0], audio_ibuffer+adjusted_read, copylen); + memset(audio_ibuffer+adjusted_read, 0, copylen); + audio_ibuffer_read += copylen; + + // If we reached the end of the buffer without reaching the end of the frame, do another copy from the start + // of the buffer + if (copylen < aframe_bytes) { + int remainder_len = aframe_bytes-copylen; + memcpy(audio_frame->data[0]+copylen, audio_ibuffer, remainder_len); + memset(audio_ibuffer, 0, remainder_len); + audio_ibuffer_read += remainder_len; + } + + // Convert raw audio samples to the destination codec's sample format + swr_convert_frame(swr_ctx, swr_frame, audio_frame); + + // The timestamp is set to the current count of audio samples (since the audio stream's timebase is + swr_frame->pts = file_audio_samples; + + // Send frame to encoder + if (!Encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) { + return; + } + + // Increment by the frame's number of samples + file_audio_samples += swr_frame->nb_samples; + } + + audio_write_lock.unlock(); + + } + + // 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 - 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(params_.sequence->playhead - params_.start_frame) / double(params_.end_frame - params_.start_frame)) * 100.0), eta); + + // Increment sequence playhead + params_.sequence->playhead++; + + // Increment frame count (used for generating encoding statistics above) + frame_count++; + } + + // Restore original connection from RenderThread + disconnect(renderer, SIGNAL(ready()), this, SLOT(wake())); + connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint())); + + if (interrupt_) { + return; + } + + if (params_.video_enabled) vpkt_alloc = true; + if (params_.audio_enabled) apkt_alloc = true; + + olive::Global->set_export_state(false); + + // If audio is enabled, flush the rest of the audio out of swresample + if (params_.audio_enabled) { + + do { + + swr_convert_frame(swr_ctx, swr_frame, nullptr); + if (swr_frame->nb_samples == 0) break; + swr_frame->pts = file_audio_samples; + if (!Encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) { + return; + } + file_audio_samples += swr_frame->nb_samples; + + } while (swr_frame->nb_samples > 0); + + } + + if (interrupt_) { + return; + } + + // Flush remaining packets out of video and audio encoders by sending a null frame + if (params_.video_enabled) { + Encode(fmt_ctx, vcodec_ctx, nullptr, &video_pkt, video_stream); + } + if (params_.audio_enabled) { + Encode(fmt_ctx, acodec_ctx, nullptr, &audio_pkt, audio_stream); + } + + // Write container trailer + ret = av_write_trailer(fmt_ctx); + if (ret < 0) { + qCritical() << "Could not write output file trailer." << ret; + export_error = tr("could not write output file trailer (%1)").arg(QString::number(ret)); + return; + } + + emit ProgressChanged(100, 0); +} + +void ExportThread::Cleanup() +{ + if (fmt_ctx != nullptr) { + avio_closep(&fmt_ctx->pb); + avformat_free_context(fmt_ctx); + } + + if (acodec_ctx != nullptr) { + avcodec_close(acodec_ctx); + avcodec_free_context(&acodec_ctx); + } + + if (audio_frame != nullptr) { + av_frame_free(&audio_frame); + } + + if (apkt_alloc) { + av_packet_unref(&audio_pkt); + } + + if (vcodec_ctx != nullptr) { + avcodec_close(vcodec_ctx); + avcodec_free_context(&vcodec_ctx); + } + + if (video_frame != nullptr) { + av_frame_free(&video_frame); + } + + if (vpkt_alloc) { + av_packet_unref(&video_pkt); + } + + if (sws_ctx != nullptr) { + sws_freeContext(sws_ctx); + } + + if (swr_ctx != nullptr) { + swr_free(&swr_ctx); + } + + if (swr_frame != nullptr) { + av_frame_free(&swr_frame); + } + + if (sws_frame != nullptr) { + av_frame_free(&sws_frame); + } + + delete [] c_filename; +} + +void ExportThread::run() { + // Ensure sequence isn't currently playing + panel_sequence_viewer->pause(); + + // Seek to the first frame we're exporting + panel_sequence_viewer->seek(params_.start_frame); + + // Lock mutex (used for thread synchronizations) + mutex.lock(); + + // Run export function (which will return if there's a failure) + Export(); + + mutex.unlock(); + + // Clean up anything that was allocated in Export() (whether it succeeded or not) + Cleanup(); +} + +const QString &ExportThread::GetError() { + return export_error; +} + +bool ExportThread::WasInterrupted() +{ + return interrupt_; +} + +void ExportThread::Interrupt() +{ + mutex.lock(); + interrupt_ = true; + waitCond.wakeAll(); + mutex.unlock(); +} + +void ExportThread::play_wake() +{ + mutex.lock(); + waiting_for_audio_ = false; + waitCond.wakeAll(); + mutex.unlock(); +} + +void ExportThread::wake() { + mutex.lock(); + waitCond.wakeAll(); + mutex.unlock(); +} diff --git a/rendering/exportthread.h b/rendering/exportthread.h index 6180ec4e6..170107188 100644 --- a/rendering/exportthread.h +++ b/rendering/exportthread.h @@ -1,141 +1,141 @@ -/*** - - 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 EXPORTTHREAD_H -#define EXPORTTHREAD_H - -extern "C" { -#include -} - -#include -#include -#include -#include - -#include "timeline/sequence.h" - -struct AVFormatContext; -struct AVCodecContext; -struct AVFrame; -struct AVPacket; -struct AVStream; -struct AVCodec; -struct SwsContext; -struct SwrContext; - -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; - int video_width; - int video_height; - double video_frame_rate; - int video_compression_type; - double video_bitrate; - bool audio_enabled; - int audio_codec; - int audio_sampling_rate; - int audio_bitrate; - long start_frame; - long end_frame; -}; - -struct VideoCodecParams { - int pix_fmt; - int threads; -}; - -class ExportThread : public QThread { - Q_OBJECT -public: - ExportThread(const ExportParams& params, const VideoCodecParams& vparams, QObject* parent = nullptr); - virtual void run() override; - - const QString& GetError(); - - bool WasInterrupted(); -signals: - void ProgressChanged(int value, qint64 remaining_ms); -public slots: - void Interrupt(); - - void play_wake(); -private: - bool Encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream); - bool SetupVideo(); - bool SetupAudio(); - bool SetupContainer(); - void Export(); - void Cleanup(); - - QOffscreenSurface surface; - bool interrupt_; - - // params imported from dialogs - ExportParams params_; - VideoCodecParams vcodec_params_; - - AVFormatContext* fmt_ctx; - AVStream* video_stream; - AVCodec* vcodec; - AVCodecContext* vcodec_ctx; - AVFrame* video_frame; - AVFrame* sws_frame; - SwsContext* sws_ctx; - AVStream* audio_stream; - AVCodec* acodec; - AVFrame* audio_frame; - AVFrame* swr_frame; - AVCodecContext* acodec_ctx; - AVPacket video_pkt; - AVPacket audio_pkt; - SwrContext* swr_ctx; - - bool vpkt_alloc; - bool apkt_alloc; - - int aframe_bytes; - int ret; - char* c_filename; - - QMutex mutex; - QWaitCondition waitCond; - - QString export_error; - - bool waiting_for_audio_; -private slots: - void wake(); -}; - -#endif // EXPORTTHREAD_H +/*** + + 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 EXPORTTHREAD_H +#define EXPORTTHREAD_H + +extern "C" { +#include +} + +#include +#include +#include +#include + +#include "timeline/sequence.h" + +struct AVFormatContext; +struct AVCodecContext; +struct AVFrame; +struct AVPacket; +struct AVStream; +struct AVCodec; +struct SwsContext; +struct SwrContext; + +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; + int video_width; + int video_height; + double video_frame_rate; + int video_compression_type; + double video_bitrate; + bool audio_enabled; + int audio_codec; + int audio_sampling_rate; + int audio_bitrate; + long start_frame; + long end_frame; +}; + +struct VideoCodecParams { + int pix_fmt; + int threads; +}; + +class ExportThread : public QThread { + Q_OBJECT +public: + ExportThread(const ExportParams& params, const VideoCodecParams& vparams, QObject* parent = nullptr); + virtual void run() override; + + const QString& GetError(); + + bool WasInterrupted(); +signals: + void ProgressChanged(int value, qint64 remaining_ms); +public slots: + void Interrupt(); + + void play_wake(); +private: + bool Encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream); + bool SetupVideo(); + bool SetupAudio(); + bool SetupContainer(); + void Export(); + void Cleanup(); + + QOffscreenSurface surface; + bool interrupt_; + + // params imported from dialogs + ExportParams params_; + VideoCodecParams vcodec_params_; + + AVFormatContext* fmt_ctx; + AVStream* video_stream; + AVCodec* vcodec; + AVCodecContext* vcodec_ctx; + AVFrame* video_frame; + AVFrame* sws_frame; + SwsContext* sws_ctx; + AVStream* audio_stream; + AVCodec* acodec; + AVFrame* audio_frame; + AVFrame* swr_frame; + AVCodecContext* acodec_ctx; + AVPacket video_pkt; + AVPacket audio_pkt; + SwrContext* swr_ctx; + + bool vpkt_alloc; + bool apkt_alloc; + + int aframe_bytes; + int ret; + char* c_filename; + + QMutex mutex; + QWaitCondition waitCond; + + QString export_error; + + bool waiting_for_audio_; +private slots: + void wake(); +}; + +#endif // EXPORTTHREAD_H diff --git a/rendering/framebuffercollection.cpp b/rendering/framebuffercollection.cpp index f8f3ed2b0..502cb549b 100644 --- a/rendering/framebuffercollection.cpp +++ b/rendering/framebuffercollection.cpp @@ -1,68 +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 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 +#ifndef FRAMEBUFFERCOLLECTION_H +#define FRAMEBUFFERCOLLECTION_H + +#include + +#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 bfc9ed70a..9ab25bf07 100644 --- a/rendering/framebufferobject.cpp +++ b/rendering/framebufferobject.cpp @@ -1,155 +1,155 @@ -/*** - - 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), - texture_(0), - ctx_(nullptr) -{} - -FramebufferObject::~FramebufferObject() -{ - Destroy(); -} - -bool FramebufferObject::IsCreated() -{ - return ctx_ != nullptr; -} - -void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height) -{ - // free any previous textures - Destroy(); - - // set context to new context provided - ctx_ = ctx; - - QOpenGLFunctions* f = ctx->functions(); - - // create framebuffer object - f->glGenFramebuffers(1, &buffer_); - - // create texture - f->glGenTextures(1, &texture_); - - // bind framebuffer for attaching - f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_); - - // bind texture - f->glBindTexture(GL_TEXTURE_2D, texture_); - - // allocate storage for texture - const olive::PixelFormatInfo& bit_depth = olive::pixel_formats.at(olive::Global->effective_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 - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // attach texture to framebuffer - 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); - - // release framebuffer - f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); -} - -void FramebufferObject::Destroy() -{ - if (ctx_ != nullptr) { - ctx_->functions()->glDeleteFramebuffers(1, &buffer_); - - ctx_->functions()->glDeleteTextures(1, &texture_); - } - - ctx_ = nullptr; -} - -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 -{ - return texture_; -} +/*** + + 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), + texture_(0), + ctx_(nullptr) +{} + +FramebufferObject::~FramebufferObject() +{ + Destroy(); +} + +bool FramebufferObject::IsCreated() +{ + return ctx_ != nullptr; +} + +void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height) +{ + // free any previous textures + Destroy(); + + // set context to new context provided + ctx_ = ctx; + + QOpenGLFunctions* f = ctx->functions(); + + // create framebuffer object + f->glGenFramebuffers(1, &buffer_); + + // create texture + f->glGenTextures(1, &texture_); + + // bind framebuffer for attaching + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_); + + // bind texture + f->glBindTexture(GL_TEXTURE_2D, texture_); + + // allocate storage for texture + const olive::PixelFormatInfo& bit_depth = olive::pixel_formats.at(olive::Global->effective_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 + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // attach texture to framebuffer + 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); + + // release framebuffer + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void FramebufferObject::Destroy() +{ + if (ctx_ != nullptr) { + ctx_->functions()->glDeleteFramebuffers(1, &buffer_); + + ctx_->functions()->glDeleteTextures(1, &texture_); + } + + ctx_ = nullptr; +} + +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 +{ + return texture_; +} diff --git a/rendering/framebufferobject.h b/rendering/framebufferobject.h index 0636dc64a..fda654de9 100644 --- a/rendering/framebufferobject.h +++ b/rendering/framebufferobject.h @@ -1,50 +1,50 @@ -/*** - - 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 - -#include - -class FramebufferObject -{ -public: - FramebufferObject(); - ~FramebufferObject(); - - bool IsCreated(); - void Create(QOpenGLContext* ctx, int width, int height); - void Destroy(); - - 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_; - GLuint texture_; -}; - -#endif // FRAMEBUFFEROBJECT_H +/*** + + 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 + +#include + +class FramebufferObject +{ +public: + FramebufferObject(); + ~FramebufferObject(); + + bool IsCreated(); + void Create(QOpenGLContext* ctx, int width, int height); + void Destroy(); + + 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_; + GLuint texture_; +}; + +#endif // FRAMEBUFFEROBJECT_H diff --git a/rendering/memorybuffer.cpp b/rendering/memorybuffer.cpp index 6e496372a..9a54f4662 100644 --- a/rendering/memorybuffer.cpp +++ b/rendering/memorybuffer.cpp @@ -1,43 +1,43 @@ -#include "memorybuffer.h" - -#include - -#include "decoders/pixelformatconverter.h" - -MemoryBuffer::MemoryBuffer() -{ -} - -void MemoryBuffer::Create(int width, int height, const olive::PixelFormat &format) -{ - width_ = width; - height_ = height; - format_ = format; - - data_.resize(olive::pix_fmt_conv->GetBufferSize(format, width, height)); -} - -const int &MemoryBuffer::width() const -{ - return width_; -} - -const int &MemoryBuffer::height() const -{ - return height_; -} - -const olive::PixelFormat &MemoryBuffer::format() const -{ - return format_; -} - -uint8_t *MemoryBuffer::data() -{ - return data_.data(); -} - -const uint8_t *MemoryBuffer::const_data() const -{ - return data_.constData(); -} +#include "memorybuffer.h" + +#include + +#include "decoders/pixelformatconverter.h" + +MemoryBuffer::MemoryBuffer() +{ +} + +void MemoryBuffer::Create(int width, int height, const olive::PixelFormat &format) +{ + width_ = width; + height_ = height; + format_ = format; + + data_.resize(olive::pix_fmt_conv->GetBufferSize(format, width, height)); +} + +const int &MemoryBuffer::width() const +{ + return width_; +} + +const int &MemoryBuffer::height() const +{ + return height_; +} + +const olive::PixelFormat &MemoryBuffer::format() const +{ + return format_; +} + +uint8_t *MemoryBuffer::data() +{ + return data_.data(); +} + +const uint8_t *MemoryBuffer::const_data() const +{ + return data_.constData(); +} diff --git a/rendering/memorybuffer.h b/rendering/memorybuffer.h index 423f60b98..9d6db5fe3 100644 --- a/rendering/memorybuffer.h +++ b/rendering/memorybuffer.h @@ -1,28 +1,28 @@ -#ifndef MEMORYBUFFER_H -#define MEMORYBUFFER_H - -#include - -#include "pixelformats.h" - -class MemoryBuffer -{ -public: - MemoryBuffer(); - - void Create(int width, int height, const olive::PixelFormat& format); - - const int& width() const; - const int& height() const; - const olive::PixelFormat& format() const; - uint8_t* data(); - const uint8_t* const_data() const; - -private: - QVector data_; - int width_; - int height_; - olive::PixelFormat format_; -}; - -#endif // MEMORYBUFFER_H +#ifndef MEMORYBUFFER_H +#define MEMORYBUFFER_H + +#include + +#include "pixelformats.h" + +class MemoryBuffer +{ +public: + MemoryBuffer(); + + void Create(int width, int height, const olive::PixelFormat& format); + + const int& width() const; + const int& height() const; + const olive::PixelFormat& format() const; + uint8_t* data(); + const uint8_t* const_data() const; + +private: + QVector data_; + int width_; + int height_; + olive::PixelFormat format_; +}; + +#endif // MEMORYBUFFER_H diff --git a/rendering/pixelformats.cpp b/rendering/pixelformats.cpp index 6f970e5d6..2bca7b024 100644 --- a/rendering/pixelformats.cpp +++ b/rendering/pixelformats.cpp @@ -1,60 +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; - -} - -} - +/*** + + 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 index 37fe34850..b9086b6f7 100644 --- a/rendering/pixelformats.h +++ b/rendering/pixelformats.h @@ -1,65 +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 +/*** + + 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 index e75bab495..a89993ffd 100644 --- a/rendering/qopenglshaderprogramptr.h +++ b/rendering/qopenglshaderprogramptr.h @@ -1,29 +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 +/*** + + 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 365471a86..df5cf94be 100644 --- a/rendering/renderfunctions.cpp +++ b/rendering/renderfunctions.cpp @@ -1,850 +1,850 @@ -/*** - - 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 "renderfunctions.h" - -extern "C" { -#include -} - -#include -#include -#include -#include -#include -#include - -#include "timeline/clip.h" -#include "timeline/sequence.h" -#include "project/media.h" -#include "nodes/oldeffectnode.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 "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); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); -} - -void olive::rendering::Blit(QOpenGLShaderProgram* pipeline, bool flipped, QMatrix4x4 matrix) { - - QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions(); - PrepareToDraw(func); - - 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(); - -} - -void draw_clip(QOpenGLContext* ctx, - QOpenGLShaderProgram* pipeline, - GLuint fbo, - GLuint texture, - bool clear) { - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - - if (clear) { - ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); - } - - ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture); - - olive::rendering::Blit(pipeline); - - ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); -} - -GLuint draw_clip(QOpenGLContext* ctx, - QOpenGLShaderProgram* pipeline, - const FramebufferObject& fbo, - GLuint texture, - bool clear) { - - fbo.BindBuffer(); - - if (clear) { - ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); - } - - ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture); - - olive::rendering::Blit(pipeline); - - ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - - fbo.ReleaseBuffer(); - - return fbo.texture(); - -} - -void process_effect(QOpenGLContext* ctx, - QOpenGLShaderProgram* pipeline, - Clip* c, - OldEffectNode* e, - double timecode, - GLTextureCoords& coords, - GLuint& composite_texture, - bool& fbo_switcher, - bool& texture_failed, - int data) { - if (e->IsEnabled()) { - if (e->Flags() & OldEffectNode::CoordsFlag) { - e->process_coords(timecode, coords, data); - } - bool can_process_shaders = ((e->Flags() & OldEffectNode::ShaderFlag) && olive::runtime_config.shaders_are_enabled); - if (can_process_shaders || (e->Flags() & OldEffectNode::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(ctx, e->GetShaderPipeline(), c->fbo.at(fbo_switcher), composite_texture, true); - fbo_switcher = !fbo_switcher; - } - } - if (e->Flags() & OldEffectNode::SuperimposeFlag) { - GLuint superimpose_texture = e->process_superimpose(ctx, timecode); - - if (superimpose_texture == 0) { - qWarning() << "Superimpose texture was nullptr, retrying..."; - texture_failed = true; - } else if (composite_texture == 0) { - // if there is no previous texture, just return the superimposes texture - // UNLESS this is a shader-extended superimpose effect in which case, - // we'll need to draw it below - composite_texture = superimpose_texture; - } 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.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(ctx, pipeline, c->fbo.at(!fbo_switcher), superimpose_texture, false); - } - } - } - } -} - -GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { - 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)->track()->sequence()->frame_rate(), s->frame_rate()); - } - - 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; - - QVector current_clips; - - // loop through clips, find currently active, and sort by track - QVector sequence_clips = s->GetAllClips(); - for (int i=0;itype() == params.type) { - - bool clip_is_active = false; - - // is the clip a "footage" clip? - if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* m = c->media()->to_footage(); - - // does the clip have a valid media source? - if (!m->invalid && !(c->type() == olive::kTypeAudio && !is_audio_device_set())) { - - // is the media process and ready? - if (m->ready) { - const FootageStream* ms = c->media_stream(); - - // does the media have a valid media stream source and is it active? - if (ms != nullptr && c->IsActiveAt(playhead)) { - - // open if not open - if (!c->IsOpen()) { - c->Open(); - } - - clip_is_active = true; - - // increment audio track count - if (c->type() == olive::kTypeAudio) audio_track_count++; - - } else if (c->IsOpen()) { - - // close the clip if it isn't active anymore - c->Close(false); - - } - } else { - - // media wasn't ready, schedule a redraw - params.texture_failed = true; - - } - } - } else { - // if the clip is a nested sequence or null clip, just open it - - if (c->IsActiveAt(playhead)) { - if (!c->IsOpen()) { - c->Open(); - } - clip_is_active = true; - } else if (c->IsOpen()) { - c->Close(false); - } - } - - // if the clip is active, added it to "current_clips", sorted by track - if (clip_is_active) { - bool added = false; - - // track sorting is only necessary for video clips - // audio clips are mixed equally, so we skip sorting for those - if (params.type == olive::kTypeVideo) { - - // insertion sort by track - for (int j=0;jtrack() < c->track()) { - current_clips.insert(j, c); - added = true; - break; - } - } - - } - - if (!added) { - current_clips.append(c); - } - } - } - } - } - - QMatrix4x4 projection; - - if (params.type == olive::kTypeVideo) { - // set default coordinates based on the sequence, with 0 in the direct center - - params.ctx->functions()->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - int half_width = s->width()/2; - int half_height = s->height()/2; - - projection.ortho(-half_width, half_width, -half_height, half_height, -1, 1); - } - - // loop through current clips - - for (int i=0;istate_change_lock.lock(); - } else { - got_mutex = c->state_change_lock.tryLock(); - } - - if (got_mutex && c->IsOpen()) { - // if clip is a video clip - if (c->type() == olive::kTypeVideo) { - - // textureID variable contains texture to be drawn on screen at the end - GLuint textureID = 0; - - // store video source dimensions - 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(true)), false, params.nests, params.playback_speed); - if (!c->Retrieve()) { - params.texture_failed = true; - } else { - // retrieve ID from c->texture - textureID = c->texture; - } - - if (textureID == 0) { - - qWarning() << "Failed to create texture"; - - } else { - - convert_frame_to_internal = true; - - } - } - - // if clip should actually be shown on screen in this frame - if (playhead >= c->timeline_in(true) - && playhead < c->timeline_out(true)) { - - // simple bool for switching between the two framebuffers - bool fbo_switcher = false; - - 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 - params.nests.append(c); - - // compose sequence - textureID = compose_sequence(params); - - // remove sequence from nest list - params.nests.removeLast(); - - // compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1] - fbo_switcher = !fbo_switcher; - - } else if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - - // Convert frame from source to linear colorspace - if (olive::config.enable_color_management) - { - - // 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.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 == - - // get current sequence time in seconds (used for effects) - double timecode = get_timecode(c, playhead); - - // run through all of the clip's effects - for (int j=0;jeffects.size();j++) { - - OldEffectNode* e = c->effects.at(j).get(); - process_effect(params.ctx, params.pipeline, c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone); - - } - - // if the clip has an opening transition, process that now - if (c->opening_transition != nullptr) { - int transition_progress = playhead - c->timeline_in(true); - if (transition_progress < c->opening_transition->get_length()) { - 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); - } - } - - // if the clip has a closing transition, process that now - 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(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-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); - - coords.matrix.scale(scale_multiplier, scale_multiplier); - } - - // Configure effect gizmos if they exist - if (params.gizmos != nullptr) { - // 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()); - - - - // == START RENDER CLIP IN CONTEXT OF SEQUENCE == - - - - // 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].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->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); - - params.ctx->functions()->glClearColor(0.0, 0.0, 0.0, 0.0); - params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); - - // bind final clip texture - 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 - params.pipeline->bind(); - - params.pipeline->setUniformValue("mvp_matrix", projection * coords.matrix); - params.pipeline->setUniformValue("texture", 0); - params.pipeline->setUniformValue("opacity", coords.opacity); - - 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 - params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - - params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - - - // == END RENDER CLIP IN CONTEXT OF SEQUENCE == - - - - // - // - // PROCESS POST-SHADERS - // - // - - - - // 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); - } - */ - - - - // == START FINAL DRAW ON SEQUENCE BUFFER == - - - - - // bind front buffer as draw buffer - params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo); - - // 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); - - olive::rendering::Blit(params.pipeline); - - params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - - //} 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); - - // load foreground texture into texture unit 1 - params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1 - params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1); - - // bind and configure blending mode shader - params.blend_mode_program->bind(); - params.blend_mode_program->setUniformValue("blendmode", coords.blendmode); - params.blend_mode_program->setUniformValue("opacity", coords.opacity); - params.blend_mode_program->setUniformValue("background", 0); - params.blend_mode_program->setUniformValue("foreground", 1); - - params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); - - olive::rendering::Blit(params.pipeline); - - // release blend mode shader - params.blend_mode_program->release(); - - // unbind texture from texture unit 1 - params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); - - // 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); - - - - - // == END FINAL DRAW ON SEQUENCE BUFFER == - } - } - } else if (c->type() == olive::kTypeAudio) { - if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { - params.nests.append(c); - compose_sequence(params); - params.nests.removeLast(); - } else { - // Check whether cacher is currently active, if not activate it now - - bool got_mutex2 = false; - - if (params.wait_for_mutexes) { - c->cache_lock.lock(); - got_mutex2 = true; - } else { - got_mutex2 = c->cache_lock.tryLock(got_mutex2); - } - - if (got_mutex2) { - - c->cache_lock.unlock(); - - c->Cache(playhead, - (params.viewer != nullptr && !params.viewer->playing), - params.nests, - params.playback_speed); - - } - } - } - } else { - params.texture_failed = true; - } - - if (got_mutex) { - c->state_change_lock.unlock(); - } - } - - if (audio_track_count == 0) { - WakeAudioWakeObject(); - } - - if (!params.nests.isEmpty() && !params.nests.last()->fbo.isEmpty()) { - // returns nested clip's texture - return params.nests.last()->fbo[0].texture(); - } - - return 0; -} - -void olive::rendering::compose_audio(Viewer* viewer, Sequence* seq, int playback_speed, bool wait_for_mutexes) { - ComposeSequenceParams params; - params.viewer = viewer; - params.ctx = nullptr; - params.seq = seq; - params.type = olive::kTypeAudio; - params.gizmos = nullptr; - params.wait_for_mutexes = wait_for_mutexes; - params.playback_speed = playback_speed; - compose_sequence(params); -} - -GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline, - GLuint lut, - const FramebufferObject& fbo, - GLuint texture) -{ - if (pipeline == nullptr) { - return 0; - } - - QOpenGLContext* ctx = QOpenGLContext::currentContext(); - QOpenGLExtraFunctions* xf = ctx->extraFunctions(); - - xf->glActiveTexture(GL_TEXTURE2); - xf->glBindTexture(GL_TEXTURE_3D, lut); - xf->glActiveTexture(GL_TEXTURE0); - - pipeline->bind(); - - pipeline->setUniformValue("tex2", 2); - - //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; -} +/*** + + 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 "renderfunctions.h" + +extern "C" { +#include +} + +#include +#include +#include +#include +#include +#include + +#include "timeline/clip.h" +#include "timeline/sequence.h" +#include "project/media.h" +#include "nodes/oldeffectnode.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 "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); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); +} + +void olive::rendering::Blit(QOpenGLShaderProgram* pipeline, bool flipped, QMatrix4x4 matrix) { + + QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions(); + PrepareToDraw(func); + + 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(); + +} + +void draw_clip(QOpenGLContext* ctx, + QOpenGLShaderProgram* pipeline, + GLuint fbo, + GLuint texture, + bool clear) { + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + + if (clear) { + ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); + } + + ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture); + + olive::rendering::Blit(pipeline); + + ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); + + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +GLuint draw_clip(QOpenGLContext* ctx, + QOpenGLShaderProgram* pipeline, + const FramebufferObject& fbo, + GLuint texture, + bool clear) { + + fbo.BindBuffer(); + + if (clear) { + ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); + } + + ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture); + + olive::rendering::Blit(pipeline); + + ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); + + fbo.ReleaseBuffer(); + + return fbo.texture(); + +} + +void process_effect(QOpenGLContext* ctx, + QOpenGLShaderProgram* pipeline, + Clip* c, + OldEffectNode* e, + double timecode, + GLTextureCoords& coords, + GLuint& composite_texture, + bool& fbo_switcher, + bool& texture_failed, + int data) { + if (e->IsEnabled()) { + if (e->Flags() & OldEffectNode::CoordsFlag) { + e->process_coords(timecode, coords, data); + } + bool can_process_shaders = ((e->Flags() & OldEffectNode::ShaderFlag) && olive::runtime_config.shaders_are_enabled); + if (can_process_shaders || (e->Flags() & OldEffectNode::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(ctx, e->GetShaderPipeline(), c->fbo.at(fbo_switcher), composite_texture, true); + fbo_switcher = !fbo_switcher; + } + } + if (e->Flags() & OldEffectNode::SuperimposeFlag) { + GLuint superimpose_texture = e->process_superimpose(ctx, timecode); + + if (superimpose_texture == 0) { + qWarning() << "Superimpose texture was nullptr, retrying..."; + texture_failed = true; + } else if (composite_texture == 0) { + // if there is no previous texture, just return the superimposes texture + // UNLESS this is a shader-extended superimpose effect in which case, + // we'll need to draw it below + composite_texture = superimpose_texture; + } 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.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(ctx, pipeline, c->fbo.at(!fbo_switcher), superimpose_texture, false); + } + } + } + } +} + +GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { + 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)->track()->sequence()->frame_rate(), s->frame_rate()); + } + + 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; + + QVector current_clips; + + // loop through clips, find currently active, and sort by track + QVector sequence_clips = s->GetAllClips(); + for (int i=0;itype() == params.type) { + + bool clip_is_active = false; + + // is the clip a "footage" clip? + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media()->to_footage(); + + // does the clip have a valid media source? + if (!m->invalid && !(c->type() == olive::kTypeAudio && !is_audio_device_set())) { + + // is the media process and ready? + if (m->ready) { + const FootageStream* ms = c->media_stream(); + + // does the media have a valid media stream source and is it active? + if (ms != nullptr && c->IsActiveAt(playhead)) { + + // open if not open + if (!c->IsOpen()) { + c->Open(); + } + + clip_is_active = true; + + // increment audio track count + if (c->type() == olive::kTypeAudio) audio_track_count++; + + } else if (c->IsOpen()) { + + // close the clip if it isn't active anymore + c->Close(false); + + } + } else { + + // media wasn't ready, schedule a redraw + params.texture_failed = true; + + } + } + } else { + // if the clip is a nested sequence or null clip, just open it + + if (c->IsActiveAt(playhead)) { + if (!c->IsOpen()) { + c->Open(); + } + clip_is_active = true; + } else if (c->IsOpen()) { + c->Close(false); + } + } + + // if the clip is active, added it to "current_clips", sorted by track + if (clip_is_active) { + bool added = false; + + // track sorting is only necessary for video clips + // audio clips are mixed equally, so we skip sorting for those + if (params.type == olive::kTypeVideo) { + + // insertion sort by track + for (int j=0;jtrack() < c->track()) { + current_clips.insert(j, c); + added = true; + break; + } + } + + } + + if (!added) { + current_clips.append(c); + } + } + } + } + } + + QMatrix4x4 projection; + + if (params.type == olive::kTypeVideo) { + // set default coordinates based on the sequence, with 0 in the direct center + + params.ctx->functions()->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + int half_width = s->width()/2; + int half_height = s->height()/2; + + projection.ortho(-half_width, half_width, -half_height, half_height, -1, 1); + } + + // loop through current clips + + for (int i=0;istate_change_lock.lock(); + } else { + got_mutex = c->state_change_lock.tryLock(); + } + + if (got_mutex && c->IsOpen()) { + // if clip is a video clip + if (c->type() == olive::kTypeVideo) { + + // textureID variable contains texture to be drawn on screen at the end + GLuint textureID = 0; + + // store video source dimensions + 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(true)), false, params.nests, params.playback_speed); + if (!c->Retrieve()) { + params.texture_failed = true; + } else { + // retrieve ID from c->texture + textureID = c->texture; + } + + if (textureID == 0) { + + qWarning() << "Failed to create texture"; + + } else { + + convert_frame_to_internal = true; + + } + } + + // if clip should actually be shown on screen in this frame + if (playhead >= c->timeline_in(true) + && playhead < c->timeline_out(true)) { + + // simple bool for switching between the two framebuffers + bool fbo_switcher = false; + + 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 + params.nests.append(c); + + // compose sequence + textureID = compose_sequence(params); + + // remove sequence from nest list + params.nests.removeLast(); + + // compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1] + fbo_switcher = !fbo_switcher; + + } else if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + + // Convert frame from source to linear colorspace + if (olive::config.enable_color_management) + { + + // 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.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 == + + // get current sequence time in seconds (used for effects) + double timecode = get_timecode(c, playhead); + + // run through all of the clip's effects + for (int j=0;jeffects.size();j++) { + + OldEffectNode* e = c->effects.at(j).get(); + process_effect(params.ctx, params.pipeline, c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone); + + } + + // if the clip has an opening transition, process that now + if (c->opening_transition != nullptr) { + int transition_progress = playhead - c->timeline_in(true); + if (transition_progress < c->opening_transition->get_length()) { + 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); + } + } + + // if the clip has a closing transition, process that now + 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(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-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); + + coords.matrix.scale(scale_multiplier, scale_multiplier); + } + + // Configure effect gizmos if they exist + if (params.gizmos != nullptr) { + // 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()); + + + + // == START RENDER CLIP IN CONTEXT OF SEQUENCE == + + + + // 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].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->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); + + params.ctx->functions()->glClearColor(0.0, 0.0, 0.0, 0.0); + params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); + + // bind final clip texture + 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 + params.pipeline->bind(); + + params.pipeline->setUniformValue("mvp_matrix", projection * coords.matrix); + params.pipeline->setUniformValue("texture", 0); + params.pipeline->setUniformValue("opacity", coords.opacity); + + 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 + params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); + + params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + + + // == END RENDER CLIP IN CONTEXT OF SEQUENCE == + + + + // + // + // PROCESS POST-SHADERS + // + // + + + + // 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); + } + */ + + + + // == START FINAL DRAW ON SEQUENCE BUFFER == + + + + + // bind front buffer as draw buffer + params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo); + + // 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); + + olive::rendering::Blit(params.pipeline); + + params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); + + //} 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); + + // load foreground texture into texture unit 1 + params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1 + params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1); + + // bind and configure blending mode shader + params.blend_mode_program->bind(); + params.blend_mode_program->setUniformValue("blendmode", coords.blendmode); + params.blend_mode_program->setUniformValue("opacity", coords.opacity); + params.blend_mode_program->setUniformValue("background", 0); + params.blend_mode_program->setUniformValue("foreground", 1); + + params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); + + olive::rendering::Blit(params.pipeline); + + // release blend mode shader + params.blend_mode_program->release(); + + // unbind texture from texture unit 1 + params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0); + + // 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); + + + + + // == END FINAL DRAW ON SEQUENCE BUFFER == + } + } + } else if (c->type() == olive::kTypeAudio) { + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { + params.nests.append(c); + compose_sequence(params); + params.nests.removeLast(); + } else { + // Check whether cacher is currently active, if not activate it now + + bool got_mutex2 = false; + + if (params.wait_for_mutexes) { + c->cache_lock.lock(); + got_mutex2 = true; + } else { + got_mutex2 = c->cache_lock.tryLock(got_mutex2); + } + + if (got_mutex2) { + + c->cache_lock.unlock(); + + c->Cache(playhead, + (params.viewer != nullptr && !params.viewer->playing), + params.nests, + params.playback_speed); + + } + } + } + } else { + params.texture_failed = true; + } + + if (got_mutex) { + c->state_change_lock.unlock(); + } + } + + if (audio_track_count == 0) { + WakeAudioWakeObject(); + } + + if (!params.nests.isEmpty() && !params.nests.last()->fbo.isEmpty()) { + // returns nested clip's texture + return params.nests.last()->fbo[0].texture(); + } + + return 0; +} + +void olive::rendering::compose_audio(Viewer* viewer, Sequence* seq, int playback_speed, bool wait_for_mutexes) { + ComposeSequenceParams params; + params.viewer = viewer; + params.ctx = nullptr; + params.seq = seq; + params.type = olive::kTypeAudio; + params.gizmos = nullptr; + params.wait_for_mutexes = wait_for_mutexes; + params.playback_speed = playback_speed; + compose_sequence(params); +} + +GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline, + GLuint lut, + const FramebufferObject& fbo, + GLuint texture) +{ + if (pipeline == nullptr) { + return 0; + } + + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + QOpenGLExtraFunctions* xf = ctx->extraFunctions(); + + xf->glActiveTexture(GL_TEXTURE2); + xf->glBindTexture(GL_TEXTURE_3D, lut); + xf->glActiveTexture(GL_TEXTURE0); + + pipeline->bind(); + + pipeline->setUniformValue("tex2", 2); + + //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 0c5d0426a..09a8082d6 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -1,247 +1,247 @@ -/*** - - 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 RENDERFUNCTIONS_H -#define RENDERFUNCTIONS_H - -#include -#include -#include -#include -namespace OCIO = OCIO_NAMESPACE::v1; - -#include "timeline/sequence.h" -#include "nodes/oldeffectnode.h" -#include "panels/viewer.h" - -/** - * @brief The ComposeSequenceParams struct - * - * Struct sent to the compose_sequence() function. - */ -struct ComposeSequenceParams { - - /** - * @brief Reference to the Viewer class that's calling compose_sequence() - * - * Primarily used for calling Viewer::play_wake() when appropriate. - */ - Viewer* viewer; - - /** - * @brief The OpenGL context to use while rendering. - * - * For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed. - * - * \see ComposeSequenceParams::video - */ - QOpenGLContext* ctx; - - /** - * @brief The OpenGL pipeline used for rendering - * - * \see olive::rendering::GetPipeline(). - */ - QOpenGLShaderProgram* pipeline; - - /** - * @brief The sequence to compose - * - * In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame - * to render. - */ - Sequence* seq; - - /** - * @brief Array to store the nested sequence hierarchy - * - * Should be left empty. This array gets passed around compose_sequence() as it calls itself recursively to - * handle nested sequences. - */ - QVector nests; - - /** - * @brief Set compose mode to video or audio - * - * Accepts olive::kTypeVideo to render video, olive::kTypeAudio if this function should render audio. - */ - 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. - */ - OldEffectNode* gizmos; - - /** - * @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown. - * - * A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw - * any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a - * "best effort", but not the actual image. - * - * This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**. - * - * \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better - * way to communicate this information. - * - * Additionally, since - * compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with - * stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect - * of this though is that the preview may become less responsive if it's stuck trying to render one frame. With - * the current system, the preview may show incomplete frames occasionally but at least it will show something. - * This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be - * removed. - */ - bool texture_failed; - - /** - * @brief Run all cachers in the same thread that compose_sequence() is in - * - * Standard behavior is that all clips cache frames in their own thread and signals are sent between - * compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling - * the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a - * technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used - * when rendering as timing isn't as important as creating output frames as quickly as possible. - * - * \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes - * it easier to synchronize everything, export performance could probably benefit from keeping them in separate - * threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could - * be removed. - */ - bool wait_for_mutexes; - - /** - * @brief Set the current playback speed (adjusted with Shuttle Left/Right) - * - * Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed. - * - * \see ComposeSequenceParams::video - */ - int playback_speed; - - /** - * @brief Premultiply alpha shader - * - * Used only for video rendering. Never accessed with audio rendering. - * - * compose_sequence()'s internal composition - * expects premultipled alpha, but it will pre-emptively multiply any footage that is not set as already - * premultiplied (see Footage::alpha_is_premultiplied) using this shader. Must be compiled and linked beforehand. - * See RenderThread::premultiply_program for how this is properly set up. - */ - QOpenGLShaderProgram* premultiply_program; - - /** - * @brief The OpenGL framebuffer object that the final texture to be shown is rendered to. - * - * Used only for video rendering. Never accessed with audio rendering. - * - * When compose_sequence() is rendering the final image, this framebuffer will be bound. - */ - const FramebufferObject* main_buffer; - - /** - * @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer - * - * 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. - */ - const FramebufferObject* backend_buffer1; - - /** - * @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer - * - * 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. - */ - 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. - */ -GLuint compose_sequence(ComposeSequenceParams ¶ms); - -/** - * @brief Convenience wrapper function for compose_sequence() to render audio - * - * Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for - * video rendering. For audio rendering, this function is easier to handle and will correctly set up - * compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object. - * - * @param viewer - * - * The Viewer object calling this function - * - * @param seq - * - * The Sequence whose audio to render. - * - * @param playback_speed - * - * The current playback speed (controlled by Shuttle Left/Right) - * - * @param - * - * Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**. - */ -void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_for_mutexes); -} -} - -void UpdateOCIOGLState(const ComposeSequenceParams ¶ms); - -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 +/*** + + 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 RENDERFUNCTIONS_H +#define RENDERFUNCTIONS_H + +#include +#include +#include +#include +namespace OCIO = OCIO_NAMESPACE::v1; + +#include "timeline/sequence.h" +#include "nodes/oldeffectnode.h" +#include "panels/viewer.h" + +/** + * @brief The ComposeSequenceParams struct + * + * Struct sent to the compose_sequence() function. + */ +struct ComposeSequenceParams { + + /** + * @brief Reference to the Viewer class that's calling compose_sequence() + * + * Primarily used for calling Viewer::play_wake() when appropriate. + */ + Viewer* viewer; + + /** + * @brief The OpenGL context to use while rendering. + * + * For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed. + * + * \see ComposeSequenceParams::video + */ + QOpenGLContext* ctx; + + /** + * @brief The OpenGL pipeline used for rendering + * + * \see olive::rendering::GetPipeline(). + */ + QOpenGLShaderProgram* pipeline; + + /** + * @brief The sequence to compose + * + * In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame + * to render. + */ + Sequence* seq; + + /** + * @brief Array to store the nested sequence hierarchy + * + * Should be left empty. This array gets passed around compose_sequence() as it calls itself recursively to + * handle nested sequences. + */ + QVector nests; + + /** + * @brief Set compose mode to video or audio + * + * Accepts olive::kTypeVideo to render video, olive::kTypeAudio if this function should render audio. + */ + 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. + */ + OldEffectNode* gizmos; + + /** + * @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown. + * + * A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw + * any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a + * "best effort", but not the actual image. + * + * This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**. + * + * \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better + * way to communicate this information. + * + * Additionally, since + * compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with + * stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect + * of this though is that the preview may become less responsive if it's stuck trying to render one frame. With + * the current system, the preview may show incomplete frames occasionally but at least it will show something. + * This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be + * removed. + */ + bool texture_failed; + + /** + * @brief Run all cachers in the same thread that compose_sequence() is in + * + * Standard behavior is that all clips cache frames in their own thread and signals are sent between + * compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling + * the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a + * technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used + * when rendering as timing isn't as important as creating output frames as quickly as possible. + * + * \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes + * it easier to synchronize everything, export performance could probably benefit from keeping them in separate + * threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could + * be removed. + */ + bool wait_for_mutexes; + + /** + * @brief Set the current playback speed (adjusted with Shuttle Left/Right) + * + * Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed. + * + * \see ComposeSequenceParams::video + */ + int playback_speed; + + /** + * @brief Premultiply alpha shader + * + * Used only for video rendering. Never accessed with audio rendering. + * + * compose_sequence()'s internal composition + * expects premultipled alpha, but it will pre-emptively multiply any footage that is not set as already + * premultiplied (see Footage::alpha_is_premultiplied) using this shader. Must be compiled and linked beforehand. + * See RenderThread::premultiply_program for how this is properly set up. + */ + QOpenGLShaderProgram* premultiply_program; + + /** + * @brief The OpenGL framebuffer object that the final texture to be shown is rendered to. + * + * Used only for video rendering. Never accessed with audio rendering. + * + * When compose_sequence() is rendering the final image, this framebuffer will be bound. + */ + const FramebufferObject* main_buffer; + + /** + * @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer + * + * 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. + */ + const FramebufferObject* backend_buffer1; + + /** + * @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer + * + * 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. + */ + 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. + */ +GLuint compose_sequence(ComposeSequenceParams ¶ms); + +/** + * @brief Convenience wrapper function for compose_sequence() to render audio + * + * Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for + * video rendering. For audio rendering, this function is easier to handle and will correctly set up + * compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object. + * + * @param viewer + * + * The Viewer object calling this function + * + * @param seq + * + * The Sequence whose audio to render. + * + * @param playback_speed + * + * The current playback speed (controlled by Shuttle Left/Right) + * + * @param + * + * Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**. + */ +void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_for_mutexes); +} +} + +void UpdateOCIOGLState(const ComposeSequenceParams ¶ms); + +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 c260185fb..e9d1c818a 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -1,387 +1,387 @@ -/*** - - 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 "renderthread.h" - -#include -#include -#include -#include -#include -#include - -#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), - seq(nullptr), - tex_width(-1), - tex_height(-1), - queued(false), - texture_failed(false), - ocio_lut_texture(0), - ocio_shader(nullptr), - running(true), - ocio_config_date(0), - front_buffer_switcher(false), - pipeline_program(nullptr) -{ - surface.create(); -} - -RenderThread::~RenderThread() { - surface.destroy(); -} - -void RenderThread::run() { - wait_lock_.lock(); - - while (running) { - if (!queued) { - wait_cond_.wait(&wait_lock_); - } - if (!running) { - break; - } - queued = false; - - if (share_ctx != nullptr) { - if (ctx != nullptr) { - ctx->makeCurrent(&surface); - - // if the sequence size has changed, we'll need to reinitialize the textures - if (seq->width() != tex_width || seq->height() != tex_height) { - delete_buffers(); - - // cache sequence values for future checks - tex_width = seq->width(); - tex_height = seq->height(); - } - - // 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()); - } - if (!front_buffer_2.IsCreated()) { - front_buffer_2.Create(ctx, seq->width(), seq->height()); - } - if (!back_buffer_1.IsCreated()) { - back_buffer_1.Create(ctx, seq->width(), seq->height()); - } - if (!back_buffer_2.IsCreated()) { - back_buffer_2.Create(ctx, seq->width(), seq->height()); - } - - // If there's no pipeline shader, create it now - if (pipeline_program == nullptr) { - delete_shaders(); - - pipeline_program = olive::shader::GetPipeline(); - } - - // 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 - paint(); - - front_buffer_switcher = !front_buffer_switcher; - - emit ready(); - } - } - } - - delete_ctx(); - - wait_lock_.unlock(); -} - -QMutex *RenderThread::get_texture_mutex() -{ - // return the mutex for the opposite texture being drawn to by the renderer - return front_buffer_switcher ? &front_mutex2 : &front_mutex1; -} - -const GLuint &RenderThread::get_texture() -{ - // return the opposite texture to the texture being drawn to by the renderer - return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.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() { - // set up compose_sequence() parameters - ComposeSequenceParams params; - params.viewer = nullptr; - params.ctx = ctx; - params.seq = seq; - params.type = olive::kTypeVideo; - params.texture_failed = false; - params.wait_for_mutexes = true; - params.playback_speed = playback_speed_; - 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(); - - FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2; - - // Blit the composite buffer to one of the front buffers - - // If we're color managing, conver the linear composited frame to display color space - if (olive::config.enable_color_management && ocio_shader != nullptr) { - - olive::rendering::OCIOBlit(ocio_shader.get(), - ocio_lut_texture, - buffer, - composite_buffer.texture()); - - } else { - - // 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 - f->glFinish(); - - f->glDisable(GL_BLEND); - - texture_failed = params.texture_failed; - - active_mutex.unlock(); - - if (!save_fn.isEmpty()) { - if (texture_failed) { - // texture failed, try again - queued = true; - } else { - 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); - f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - save_fn = ""; - } - } - - if (pixel_buffer != nullptr) { - - // set main framebuffer to the current read buffer - f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer()); - - // store pixels in 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 - f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - pixel_buffer = nullptr; - } - - // release - f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); -} - -void RenderThread::start_render(QOpenGLContext *share, - Sequence* s, - int playback_speed, - const QString& save, - GLvoid* pixels, - int pixel_linesize, - int idivider) { - Q_UNUSED(idivider); - - seq = s; - - playback_speed_ = playback_speed; - - // stall any dependent actions - texture_failed = true; - - if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) { - share_ctx = share; - delete_ctx(); - ctx = new QOpenGLContext(); - ctx->setFormat(share_ctx->format()); - ctx->setShareContext(share_ctx); - ctx->create(); - ctx->moveToThread(this); - } - - save_fn = save; - pixel_buffer = pixels; - pixel_buffer_linesize = pixel_linesize; - - queued = true; - - wait_cond_.wakeAll(); -} - -bool RenderThread::did_texture_fail() { - return texture_failed; -} - -void RenderThread::cancel() { - running = false; - wait_cond_.wakeAll(); - wait(); -} - -void RenderThread::wait_until_paused() -{ - - // Wait for thread to finish whatever it's doing before proceeding. - // - // FIXME: This is slow. Perhaps there's a better way... - - if (wait_lock_.tryLock()) { - wait_lock_.unlock(); - return; - } else { - wait_lock_.lock(); - wait_lock_.unlock(); - } -} - -void RenderThread::delete_buffers() { - composite_buffer.Destroy(); - front_buffer_1.Destroy(); - front_buffer_2.Destroy(); - back_buffer_1.Destroy(); - back_buffer_2.Destroy(); -} - -void RenderThread::delete_shaders() { - pipeline_program = nullptr; -} - -void RenderThread::delete_ctx() { - if (ctx != nullptr) { - delete_shaders(); - delete_buffers(); - destroy_ocio(); - } - - delete ctx; - ctx = nullptr; -} +/*** + + 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 "renderthread.h" + +#include +#include +#include +#include +#include +#include + +#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), + seq(nullptr), + tex_width(-1), + tex_height(-1), + queued(false), + texture_failed(false), + ocio_lut_texture(0), + ocio_shader(nullptr), + running(true), + ocio_config_date(0), + front_buffer_switcher(false), + pipeline_program(nullptr) +{ + surface.create(); +} + +RenderThread::~RenderThread() { + surface.destroy(); +} + +void RenderThread::run() { + wait_lock_.lock(); + + while (running) { + if (!queued) { + wait_cond_.wait(&wait_lock_); + } + if (!running) { + break; + } + queued = false; + + if (share_ctx != nullptr) { + if (ctx != nullptr) { + ctx->makeCurrent(&surface); + + // if the sequence size has changed, we'll need to reinitialize the textures + if (seq->width() != tex_width || seq->height() != tex_height) { + delete_buffers(); + + // cache sequence values for future checks + tex_width = seq->width(); + tex_height = seq->height(); + } + + // 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()); + } + if (!front_buffer_2.IsCreated()) { + front_buffer_2.Create(ctx, seq->width(), seq->height()); + } + if (!back_buffer_1.IsCreated()) { + back_buffer_1.Create(ctx, seq->width(), seq->height()); + } + if (!back_buffer_2.IsCreated()) { + back_buffer_2.Create(ctx, seq->width(), seq->height()); + } + + // If there's no pipeline shader, create it now + if (pipeline_program == nullptr) { + delete_shaders(); + + pipeline_program = olive::shader::GetPipeline(); + } + + // 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 + paint(); + + front_buffer_switcher = !front_buffer_switcher; + + emit ready(); + } + } + } + + delete_ctx(); + + wait_lock_.unlock(); +} + +QMutex *RenderThread::get_texture_mutex() +{ + // return the mutex for the opposite texture being drawn to by the renderer + return front_buffer_switcher ? &front_mutex2 : &front_mutex1; +} + +const GLuint &RenderThread::get_texture() +{ + // return the opposite texture to the texture being drawn to by the renderer + return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.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() { + // set up compose_sequence() parameters + ComposeSequenceParams params; + params.viewer = nullptr; + params.ctx = ctx; + params.seq = seq; + params.type = olive::kTypeVideo; + params.texture_failed = false; + params.wait_for_mutexes = true; + params.playback_speed = playback_speed_; + 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(); + + FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2; + + // Blit the composite buffer to one of the front buffers + + // If we're color managing, conver the linear composited frame to display color space + if (olive::config.enable_color_management && ocio_shader != nullptr) { + + olive::rendering::OCIOBlit(ocio_shader.get(), + ocio_lut_texture, + buffer, + composite_buffer.texture()); + + } else { + + // 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 + f->glFinish(); + + f->glDisable(GL_BLEND); + + texture_failed = params.texture_failed; + + active_mutex.unlock(); + + if (!save_fn.isEmpty()) { + if (texture_failed) { + // texture failed, try again + queued = true; + } else { + 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); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + save_fn = ""; + } + } + + if (pixel_buffer != nullptr) { + + // set main framebuffer to the current read buffer + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer()); + + // store pixels in 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 + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + pixel_buffer = nullptr; + } + + // release + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void RenderThread::start_render(QOpenGLContext *share, + Sequence* s, + int playback_speed, + const QString& save, + GLvoid* pixels, + int pixel_linesize, + int idivider) { + Q_UNUSED(idivider); + + seq = s; + + playback_speed_ = playback_speed; + + // stall any dependent actions + texture_failed = true; + + if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) { + share_ctx = share; + delete_ctx(); + ctx = new QOpenGLContext(); + ctx->setFormat(share_ctx->format()); + ctx->setShareContext(share_ctx); + ctx->create(); + ctx->moveToThread(this); + } + + save_fn = save; + pixel_buffer = pixels; + pixel_buffer_linesize = pixel_linesize; + + queued = true; + + wait_cond_.wakeAll(); +} + +bool RenderThread::did_texture_fail() { + return texture_failed; +} + +void RenderThread::cancel() { + running = false; + wait_cond_.wakeAll(); + wait(); +} + +void RenderThread::wait_until_paused() +{ + + // Wait for thread to finish whatever it's doing before proceeding. + // + // FIXME: This is slow. Perhaps there's a better way... + + if (wait_lock_.tryLock()) { + wait_lock_.unlock(); + return; + } else { + wait_lock_.lock(); + wait_lock_.unlock(); + } +} + +void RenderThread::delete_buffers() { + composite_buffer.Destroy(); + front_buffer_1.Destroy(); + front_buffer_2.Destroy(); + back_buffer_1.Destroy(); + back_buffer_2.Destroy(); +} + +void RenderThread::delete_shaders() { + pipeline_program = nullptr; +} + +void RenderThread::delete_ctx() { + if (ctx != nullptr) { + delete_shaders(); + delete_buffers(); + destroy_ocio(); + } + + delete ctx; + ctx = nullptr; +} diff --git a/rendering/renderthread.h b/rendering/renderthread.h index 689fa9113..e741060b2 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -1,116 +1,116 @@ -/*** - - 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 RENDERTHREAD_H -#define RENDERTHREAD_H - -#include -#include -#include -#include -#include -#include -#include - -#include "timeline/sequence.h" -#include "nodes/oldeffectnode.h" -#include "rendering/framebufferobject.h" -#include "qopenglshaderprogramptr.h" - -class RenderThread : public QThread { - Q_OBJECT -public: - RenderThread(); - ~RenderThread(); - void run(); - - QMutex* get_texture_mutex(); - const GLuint& get_texture(); - - OldEffectNode* gizmos; - void paint(); - void start_render(QOpenGLContext* share, - Sequence *s, - int playback_speed, - const QString &save = nullptr, - GLvoid *pixels = nullptr, - int pixel_linesize = 0, - int idivider = 0); - bool did_texture_fail(); - void cancel(); - void wait_until_paused(); - -public slots: - // cleanup functions - void delete_ctx(); - void delete_buffers(); - void delete_shaders(); - void destroy_ocio(); -signals: - void ready(); -private: - - // OpenColorIO functions - void set_up_ocio(); - - // OpenColorIO variables - GLuint ocio_lut_texture; - QOpenGLShaderProgramPtr ocio_shader; - qint64 ocio_config_date; - - FramebufferObject front_buffer_1; - QMutex front_mutex1; - - FramebufferObject front_buffer_2; - QMutex front_mutex2; - - FramebufferObject composite_buffer; - - bool front_buffer_switcher; - - QWaitCondition wait_cond_; - QMutex wait_lock_; - - QWaitCondition main_thread_wait_cond_; - QMutex main_thread_lock_; - - QOffscreenSurface surface; - QOpenGLContext* share_ctx; - QOpenGLContext* ctx; - QOpenGLShaderProgramPtr pipeline_program; - - FramebufferObject back_buffer_1; - FramebufferObject back_buffer_2; - - Sequence* seq; - - int playback_speed_; - int divider; - int tex_width; - int tex_height; - bool queued; - bool texture_failed; - bool running; - QString save_fn; - GLvoid *pixel_buffer; - int pixel_buffer_linesize; -}; - -#endif // RENDERTHREAD_H +/*** + + 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 RENDERTHREAD_H +#define RENDERTHREAD_H + +#include +#include +#include +#include +#include +#include +#include + +#include "timeline/sequence.h" +#include "nodes/oldeffectnode.h" +#include "rendering/framebufferobject.h" +#include "qopenglshaderprogramptr.h" + +class RenderThread : public QThread { + Q_OBJECT +public: + RenderThread(); + ~RenderThread(); + void run(); + + QMutex* get_texture_mutex(); + const GLuint& get_texture(); + + OldEffectNode* gizmos; + void paint(); + void start_render(QOpenGLContext* share, + Sequence *s, + int playback_speed, + const QString &save = nullptr, + GLvoid *pixels = nullptr, + int pixel_linesize = 0, + int idivider = 0); + bool did_texture_fail(); + void cancel(); + void wait_until_paused(); + +public slots: + // cleanup functions + void delete_ctx(); + void delete_buffers(); + void delete_shaders(); + void destroy_ocio(); +signals: + void ready(); +private: + + // OpenColorIO functions + void set_up_ocio(); + + // OpenColorIO variables + GLuint ocio_lut_texture; + QOpenGLShaderProgramPtr ocio_shader; + qint64 ocio_config_date; + + FramebufferObject front_buffer_1; + QMutex front_mutex1; + + FramebufferObject front_buffer_2; + QMutex front_mutex2; + + FramebufferObject composite_buffer; + + bool front_buffer_switcher; + + QWaitCondition wait_cond_; + QMutex wait_lock_; + + QWaitCondition main_thread_wait_cond_; + QMutex main_thread_lock_; + + QOffscreenSurface surface; + QOpenGLContext* share_ctx; + QOpenGLContext* ctx; + QOpenGLShaderProgramPtr pipeline_program; + + FramebufferObject back_buffer_1; + FramebufferObject back_buffer_2; + + Sequence* seq; + + int playback_speed_; + int divider; + int tex_width; + int tex_height; + bool queued; + bool texture_failed; + bool running; + QString save_fn; + GLvoid *pixel_buffer; + int pixel_buffer_linesize; +}; + +#endif // RENDERTHREAD_H diff --git a/rendering/shadergenerators.cpp b/rendering/shadergenerators.cpp index a9b90c6bc..8964fcc84 100644 --- a/rendering/shadergenerators.cpp +++ b/rendering/shadergenerators.cpp @@ -1,230 +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; -} +#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 index 02123afc6..d10c5b025 100644 --- a/rendering/shadergenerators.h +++ b/rendering/shadergenerators.h @@ -1,26 +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 +#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 e4562fdc4..d044e107a 100644 --- a/timeline/clip.cpp +++ b/timeline/clip.cpp @@ -1,720 +1,720 @@ -/*** - - 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 "clip.h" - -#include - -#include "nodes/oldeffectnode.h" -#include "effects/transition.h" -#include "project/footage.h" -#include "global/config.h" -#include "rendering/cacher.h" -#include "rendering/renderfunctions.h" -#include "panels/project.h" -#include "timeline/sequence.h" -#include "panels/timeline.h" -#include "project/media.h" -#include "undo/undo.h" -#include "global/clipboard.h" -#include "global/debug.h" -#include "global/timing.h" - -Clip::Clip(Track *s) : - track_(s), - cacher(this), - enabled_(true), - clip_in_(0), - timeline_in_(0), - timeline_out_(0), - media_(nullptr), - reverse_(false), - autoscale_(olive::config.autoscale_by_default), - opening_transition(nullptr), - closing_transition(nullptr), - undeletable(false), - replaced(false), - open_(false), - texture(0) -{ -} - -ClipPtr Clip::copy(Track* s) { - ClipPtr copy = std::make_shared(s); - - copy->set_enabled(enabled()); - copy->set_name(name()); - copy->set_clip_in(clip_in()); - copy->set_timeline_in(timeline_in()); - copy->set_timeline_out(timeline_out()); - copy->set_color(color()); - copy->set_media(media(), media_stream_index()); - copy->set_autoscaled(autoscaled()); - copy->set_speed(speed()); - copy->set_reversed(reversed()); - - for (int i=0;ieffects.append(effects.at(i)->copy(copy.get())); - } - - copy->set_cached_frame_rate((this->track_ == nullptr) ? cached_frame_rate() : this->track_->sequence()->frame_rate()); - - copy->refresh(); - - return copy; -} - -bool Clip::IsActiveAt(long timecode) -{ - return enabled() - && 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->track_ == nullptr) { - return false; - } - - 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() -{ - return color_; -} - -void Clip::set_color(int r, int g, int b) -{ - color_.setRed(r); - color_.setGreen(g); - color_.setBlue(b); -} - -void Clip::set_color(const QColor &c) -{ - color_ = c; -} - -Media *Clip::media() -{ - return media_; -} - -FootageStream *Clip::media_stream() -{ - if (media() != nullptr - && media()->get_type() == MEDIA_TYPE_FOOTAGE) { - return media()->to_footage()->get_stream_from_file_index(type() == olive::kTypeVideo, media_stream_index()); - } - - return nullptr; -} - -int Clip::media_stream_index() -{ - return media_stream_; -} - -void Clip::set_media(Media *m, int s) -{ - media_ = m; - 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_; -} - -void Clip::set_enabled(bool e) -{ - enabled_ = e; -} - -void Clip::reset_audio() { - if (UsesCacher()) { - cacher.ResetAudio(); - } - if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { - - QVector nested_sequence_clips = media()->to_sequence()->GetAllClips(); - - for (int i=0;ireset_audio(); - } - - } -} - -void Clip::refresh() { - // validates media if it was replaced - if (replaced && media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* m = media()->to_footage(); - - if (type() == olive::kTypeVideo && m->video_tracks.size() > 0) { - set_media(media(), m->video_tracks.at(0).file_index); - } else if (type() == olive::kTypeAudio && m->audio_tracks.size() > 0) { - set_media(media(), m->audio_tracks.at(0).file_index); - } - } - replaced = false; - - // reinitializes all effects... just in case - for (int i=0;irefresh(); - } -} - -QVector &Clip::get_markers() { - if (media() != nullptr) { - return media()->get_markers(); - } - return markers; -} - -int Clip::IndexOfEffect(OldEffectNode *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 (clip in - length) - return clip_in_ - opening_transition->get_true_length(); - } - return clip_in_; -} - -void Clip::set_clip_in(long c) -{ - clip_in_ = c; -} - -long Clip::timeline_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) - return timeline_in_ - opening_transition->get_true_length(); - } - return timeline_in_; -} - -void Clip::set_timeline_in(long t) -{ - timeline_in_ = 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 + length) - return timeline_out_ + closing_transition->get_true_length(); - } else { - return timeline_out_; - } -} - -void Clip::set_timeline_out(long t) -{ - timeline_out_ = t; -} - -bool Clip::reversed() -{ - return reverse_; -} - -void Clip::set_reversed(bool r) -{ - reverse_ = r; -} - -bool Clip::autoscaled() -{ - return autoscale_; -} - -void Clip::set_autoscaled(bool b) -{ - autoscale_ = b; -} - -double Clip::cached_frame_rate() -{ - return cached_fr_; -} - -void Clip::set_cached_frame_rate(double d) -{ - cached_fr_ = d; -} - -const QString &Clip::name() -{ - return name_; -} - -void Clip::set_name(const QString &s) -{ - name_ = s; -} - -const ClipSpeed& Clip::speed() -{ - return speed_; -} - -void Clip::set_speed(const ClipSpeed& d) -{ - speed_ = d; -} - -AVRational Clip::time_base() -{ - return cacher.media_time_base(); -} - -Track *Clip::track() -{ - return track_; -} - -void Clip::set_track(Track *t) -{ - track_ = t; -} - -// timeline functions -long Clip::length() { - return timeline_out_ - timeline_in_; -} - -double Clip::media_frame_rate() { - Q_ASSERT(type() == olive::kTypeVideo); - if (media_ != nullptr) { - double rate = media_->get_frame_rate(media_stream_index()); - if (!qIsNaN(rate)) return rate; - } - if (track() != nullptr) return track()->sequence()->frame_rate(); - return qSNaN(); -} - -long Clip::media_length() { - if (this->track() != nullptr) { - double fr = this->track()->sequence()->frame_rate(); - - fr /= speed_.value; - - if (media_ == nullptr) { - return LONG_MAX; - } else { - switch (media_->get_type()) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* m = media_->to_footage(); - 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 { - return m->get_length_in_frames(fr); - } - } - case MEDIA_TYPE_SEQUENCE: - { - Sequence* s = media_->to_sequence().get(); - return rescale_frame_number(s->GetEndFrame(), s->frame_rate(), fr); - } - } - } - } - return 0; -} - -int Clip::media_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 (track() != nullptr) return track()->sequence()->width(); - break; - } - case MEDIA_TYPE_SEQUENCE: - { - Sequence* s = media_->to_sequence().get(); - return s->width(); - } - } - return 0; -} - -int Clip::media_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 (track() != nullptr) return track()->sequence()->height(); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* s = media_->to_sequence().get(); - return s->height(); - } - } - return 0; -} - -void Clip::refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points) { - if (change_timeline_points) { - 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;iParameterCount();j++) { - NodeIO* r = e->Parameter(j); - for (int l=0;lFieldCount();l++) { - EffectField* f = r->Field(l); - for (int k=0;kkeyframes.size();k++) { - ca->append(new SetDouble(&f->keyframes[k].time, f->keyframes[k].time, qRound(f->keyframes[k].time * multiplier))); - } - } - } - } -} - -void Clip::Open() { - if (!open_ && state_change_lock.tryLock()) { - open_ = true; - - for (int i=0;iopen(); - } - - // reset variable used to optimize uploading frame data - texture_timestamp = -1; - - if (UsesCacher()) { - // cacher will unlock open_lock - cacher.Open(); - } else { - // this media doesn't use a cacher, so we unlock here - state_change_lock.unlock(); - } - } -} - -void Clip::Close(bool wait) { - // thread safety, prevents Close() running from two separate threads simultaneously - if (open_ && state_change_lock.tryLock()) { - open_ = false; - - if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { - media()->to_sequence()->Close(); - } - - // destroy opengl texture in main thread - if (texture > 0) { - QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &texture); - texture = 0; - } - - // close all effects - for (int i=0;iis_open()) { - effects.at(i)->close(); - } - } - - // delete framebuffers - fbo.clear(); - - // delete OCIO shader - ocio_shader = nullptr; - - if (UsesCacher()) { - cacher.Close(wait); - } else { - state_change_lock.unlock(); - } - } -} - -bool Clip::IsOpen() -{ - return open_; -} - -void Clip::Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed) { - cacher.Cache(playhead, scrubbing, nests, playback_speed); - cacher_frame = playhead; -} - -bool Clip::Retrieve() -{ - bool ret = false; - - if (UsesCacher()) { - - // Retrieve the frame from the cacher that we requested in Cache(). - AVFrame* frame = cacher.Retrieve(); - - // Wait for exclusive control of the queue to avoid any threading collisions - cacher.queue()->lock(); - - // Check if we retrieved a frame (nullptr) and if the queue still contains this frame. - // - // `nullptr` is returned if the cacher failed to get any sort of frame and is uncommon, but we do need - // to handle it. - // - // We check the queue because in some situations (e.g. intensive scrubbing), in the time it took to gain - // exclusive control of the queue, the cacher may have deleted the frame. - // Therefore we check to ensure the queue still contains the frame now that we have exclusive control, - // to avoid any attempt to utilize now-freed memory. - - if (frame != nullptr && cacher.queue()->contains(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 == 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 - 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] - ); - - } - - f->glBindTexture(GL_TEXTURE_2D, 0); - - f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - texture_timestamp = frame->pts; - - ret = true; - } else { - qCritical() << "Failed to retrieve frame for clip" << name(); - } - - cacher.queue()->unlock(); - } - - return ret; -} - -bool Clip::UsesCacher() -{ - return type() == olive::kTypeAudio || (media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE); -} - -ClipSpeed::ClipSpeed() : - value(1.0), - maintain_audio_pitch(false) -{ -} +/*** + + 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 "clip.h" + +#include + +#include "nodes/oldeffectnode.h" +#include "effects/transition.h" +#include "project/footage.h" +#include "global/config.h" +#include "rendering/cacher.h" +#include "rendering/renderfunctions.h" +#include "panels/project.h" +#include "timeline/sequence.h" +#include "panels/timeline.h" +#include "project/media.h" +#include "undo/undo.h" +#include "global/clipboard.h" +#include "global/debug.h" +#include "global/timing.h" + +Clip::Clip(Track *s) : + track_(s), + cacher(this), + enabled_(true), + clip_in_(0), + timeline_in_(0), + timeline_out_(0), + media_(nullptr), + reverse_(false), + autoscale_(olive::config.autoscale_by_default), + opening_transition(nullptr), + closing_transition(nullptr), + undeletable(false), + replaced(false), + open_(false), + texture(0) +{ +} + +ClipPtr Clip::copy(Track* s) { + ClipPtr copy = std::make_shared(s); + + copy->set_enabled(enabled()); + copy->set_name(name()); + copy->set_clip_in(clip_in()); + copy->set_timeline_in(timeline_in()); + copy->set_timeline_out(timeline_out()); + copy->set_color(color()); + copy->set_media(media(), media_stream_index()); + copy->set_autoscaled(autoscaled()); + copy->set_speed(speed()); + copy->set_reversed(reversed()); + + for (int i=0;ieffects.append(effects.at(i)->copy(copy.get())); + } + + copy->set_cached_frame_rate((this->track_ == nullptr) ? cached_frame_rate() : this->track_->sequence()->frame_rate()); + + copy->refresh(); + + return copy; +} + +bool Clip::IsActiveAt(long timecode) +{ + return enabled() + && 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->track_ == nullptr) { + return false; + } + + 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() +{ + return color_; +} + +void Clip::set_color(int r, int g, int b) +{ + color_.setRed(r); + color_.setGreen(g); + color_.setBlue(b); +} + +void Clip::set_color(const QColor &c) +{ + color_ = c; +} + +Media *Clip::media() +{ + return media_; +} + +FootageStream *Clip::media_stream() +{ + if (media() != nullptr + && media()->get_type() == MEDIA_TYPE_FOOTAGE) { + return media()->to_footage()->get_stream_from_file_index(type() == olive::kTypeVideo, media_stream_index()); + } + + return nullptr; +} + +int Clip::media_stream_index() +{ + return media_stream_; +} + +void Clip::set_media(Media *m, int s) +{ + media_ = m; + 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_; +} + +void Clip::set_enabled(bool e) +{ + enabled_ = e; +} + +void Clip::reset_audio() { + if (UsesCacher()) { + cacher.ResetAudio(); + } + if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { + + QVector nested_sequence_clips = media()->to_sequence()->GetAllClips(); + + for (int i=0;ireset_audio(); + } + + } +} + +void Clip::refresh() { + // validates media if it was replaced + if (replaced && media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = media()->to_footage(); + + if (type() == olive::kTypeVideo && m->video_tracks.size() > 0) { + set_media(media(), m->video_tracks.at(0).file_index); + } else if (type() == olive::kTypeAudio && m->audio_tracks.size() > 0) { + set_media(media(), m->audio_tracks.at(0).file_index); + } + } + replaced = false; + + // reinitializes all effects... just in case + for (int i=0;irefresh(); + } +} + +QVector &Clip::get_markers() { + if (media() != nullptr) { + return media()->get_markers(); + } + return markers; +} + +int Clip::IndexOfEffect(OldEffectNode *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 (clip in - length) + return clip_in_ - opening_transition->get_true_length(); + } + return clip_in_; +} + +void Clip::set_clip_in(long c) +{ + clip_in_ = c; +} + +long Clip::timeline_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) + return timeline_in_ - opening_transition->get_true_length(); + } + return timeline_in_; +} + +void Clip::set_timeline_in(long t) +{ + timeline_in_ = 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 + length) + return timeline_out_ + closing_transition->get_true_length(); + } else { + return timeline_out_; + } +} + +void Clip::set_timeline_out(long t) +{ + timeline_out_ = t; +} + +bool Clip::reversed() +{ + return reverse_; +} + +void Clip::set_reversed(bool r) +{ + reverse_ = r; +} + +bool Clip::autoscaled() +{ + return autoscale_; +} + +void Clip::set_autoscaled(bool b) +{ + autoscale_ = b; +} + +double Clip::cached_frame_rate() +{ + return cached_fr_; +} + +void Clip::set_cached_frame_rate(double d) +{ + cached_fr_ = d; +} + +const QString &Clip::name() +{ + return name_; +} + +void Clip::set_name(const QString &s) +{ + name_ = s; +} + +const ClipSpeed& Clip::speed() +{ + return speed_; +} + +void Clip::set_speed(const ClipSpeed& d) +{ + speed_ = d; +} + +AVRational Clip::time_base() +{ + return cacher.media_time_base(); +} + +Track *Clip::track() +{ + return track_; +} + +void Clip::set_track(Track *t) +{ + track_ = t; +} + +// timeline functions +long Clip::length() { + return timeline_out_ - timeline_in_; +} + +double Clip::media_frame_rate() { + Q_ASSERT(type() == olive::kTypeVideo); + if (media_ != nullptr) { + double rate = media_->get_frame_rate(media_stream_index()); + if (!qIsNaN(rate)) return rate; + } + if (track() != nullptr) return track()->sequence()->frame_rate(); + return qSNaN(); +} + +long Clip::media_length() { + if (this->track() != nullptr) { + double fr = this->track()->sequence()->frame_rate(); + + fr /= speed_.value; + + if (media_ == nullptr) { + return LONG_MAX; + } else { + switch (media_->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* m = media_->to_footage(); + 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 { + return m->get_length_in_frames(fr); + } + } + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = media_->to_sequence().get(); + return rescale_frame_number(s->GetEndFrame(), s->frame_rate(), fr); + } + } + } + } + return 0; +} + +int Clip::media_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 (track() != nullptr) return track()->sequence()->width(); + break; + } + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = media_->to_sequence().get(); + return s->width(); + } + } + return 0; +} + +int Clip::media_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 (track() != nullptr) return track()->sequence()->height(); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = media_->to_sequence().get(); + return s->height(); + } + } + return 0; +} + +void Clip::refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points) { + if (change_timeline_points) { + 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;iParameterCount();j++) { + NodeIO* r = e->Parameter(j); + for (int l=0;lFieldCount();l++) { + EffectField* f = r->Field(l); + for (int k=0;kkeyframes.size();k++) { + ca->append(new SetDouble(&f->keyframes[k].time, f->keyframes[k].time, qRound(f->keyframes[k].time * multiplier))); + } + } + } + } +} + +void Clip::Open() { + if (!open_ && state_change_lock.tryLock()) { + open_ = true; + + for (int i=0;iopen(); + } + + // reset variable used to optimize uploading frame data + texture_timestamp = -1; + + if (UsesCacher()) { + // cacher will unlock open_lock + cacher.Open(); + } else { + // this media doesn't use a cacher, so we unlock here + state_change_lock.unlock(); + } + } +} + +void Clip::Close(bool wait) { + // thread safety, prevents Close() running from two separate threads simultaneously + if (open_ && state_change_lock.tryLock()) { + open_ = false; + + if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { + media()->to_sequence()->Close(); + } + + // destroy opengl texture in main thread + if (texture > 0) { + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &texture); + texture = 0; + } + + // close all effects + for (int i=0;iis_open()) { + effects.at(i)->close(); + } + } + + // delete framebuffers + fbo.clear(); + + // delete OCIO shader + ocio_shader = nullptr; + + if (UsesCacher()) { + cacher.Close(wait); + } else { + state_change_lock.unlock(); + } + } +} + +bool Clip::IsOpen() +{ + return open_; +} + +void Clip::Cache(long playhead, bool scrubbing, QVector& nests, int playback_speed) { + cacher.Cache(playhead, scrubbing, nests, playback_speed); + cacher_frame = playhead; +} + +bool Clip::Retrieve() +{ + bool ret = false; + + if (UsesCacher()) { + + // Retrieve the frame from the cacher that we requested in Cache(). + AVFrame* frame = cacher.Retrieve(); + + // Wait for exclusive control of the queue to avoid any threading collisions + cacher.queue()->lock(); + + // Check if we retrieved a frame (nullptr) and if the queue still contains this frame. + // + // `nullptr` is returned if the cacher failed to get any sort of frame and is uncommon, but we do need + // to handle it. + // + // We check the queue because in some situations (e.g. intensive scrubbing), in the time it took to gain + // exclusive control of the queue, the cacher may have deleted the frame. + // Therefore we check to ensure the queue still contains the frame now that we have exclusive control, + // to avoid any attempt to utilize now-freed memory. + + if (frame != nullptr && cacher.queue()->contains(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 == 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 + 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] + ); + + } + + f->glBindTexture(GL_TEXTURE_2D, 0); + + f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + texture_timestamp = frame->pts; + + ret = true; + } else { + qCritical() << "Failed to retrieve frame for clip" << name(); + } + + cacher.queue()->unlock(); + } + + return ret; +} + +bool Clip::UsesCacher() +{ + return type() == olive::kTypeAudio || (media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE); +} + +ClipSpeed::ClipSpeed() : + value(1.0), + maintain_audio_pitch(false) +{ +} diff --git a/timeline/clip.h b/timeline/clip.h index 553308f36..e4057d376 100644 --- a/timeline/clip.h +++ b/timeline/clip.h @@ -1,191 +1,191 @@ -/*** - - 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 CLIP_H -#define CLIP_H - -#include -#include -#include -#include -#include -#include - -#include "rendering/cacher.h" - -#include "nodes/oldeffectnode.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" - -class Track; - -struct ClipSpeed { - ClipSpeed(); - double value; - bool maintain_audio_pitch; -}; - -class Clip { -public: - Clip(Track *s); - ~Clip(); - 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); - void set_color(const QColor& c); - - Media* media(); - FootageStream* media_stream(); - int media_stream_index(); - int media_width(); - int media_height(); - double media_frame_rate(); - long media_length(); - void set_media(Media* m, int s); - - void Move(ComboAction* ca, - long iin, - long iout, - long iclip_in, - 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); - - long timeline_in(bool with_transition = false); - void set_timeline_in(long t); - - long timeline_out(bool with_transition = false); - void set_timeline_out(long t); - - Track* track(); - void set_track(Track* t); - - bool reversed(); - void set_reversed(bool r); - - bool autoscaled(); - void set_autoscaled(bool b); - - double cached_frame_rate(); - void set_cached_frame_rate(double d); - - const QString& name(); - void set_name(const QString& s); - - const ClipSpeed& speed(); - void set_speed(const ClipSpeed& s); - - AVRational time_base(); - - void reset_audio(); - void refresh(); - - long length(); - - void refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points); - Track* parent_; - - // markers - QVector& get_markers(); - - // other variables (should be deep copied/duplicated in copy()) - int IndexOfEffect(OldEffectNode* e); - QList effects; - QVector linked; - TransitionPtr opening_transition; - TransitionPtr closing_transition; - - // playback functions - void Open(); - void Cache(long playhead, bool scrubbing, QVector &nests, int playback_speed); - bool Retrieve(); - void Close(bool wait); - bool IsOpen(); - - bool UsesCacher(); - - // temporary variables - int load_id; - bool undeletable; - bool replaced; - - // caching functions - QMutex state_change_lock; - QMutex cache_lock; - - // video playback variables - 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_; - QString name_; - Media* media_; - int media_stream_; - ClipSpeed speed_; - double cached_fr_; - bool reverse_; - bool autoscale_; - - Cacher cacher; - long cacher_frame; - - NodeGraph pipeline_; - - QVector markers; - QColor color_; - bool open_; -}; - -#endif // CLIP_H +/*** + + 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 CLIP_H +#define CLIP_H + +#include +#include +#include +#include +#include +#include + +#include "rendering/cacher.h" + +#include "nodes/oldeffectnode.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" + +class Track; + +struct ClipSpeed { + ClipSpeed(); + double value; + bool maintain_audio_pitch; +}; + +class Clip { +public: + Clip(Track *s); + ~Clip(); + 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); + void set_color(const QColor& c); + + Media* media(); + FootageStream* media_stream(); + int media_stream_index(); + int media_width(); + int media_height(); + double media_frame_rate(); + long media_length(); + void set_media(Media* m, int s); + + void Move(ComboAction* ca, + long iin, + long iout, + long iclip_in, + 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); + + long timeline_in(bool with_transition = false); + void set_timeline_in(long t); + + long timeline_out(bool with_transition = false); + void set_timeline_out(long t); + + Track* track(); + void set_track(Track* t); + + bool reversed(); + void set_reversed(bool r); + + bool autoscaled(); + void set_autoscaled(bool b); + + double cached_frame_rate(); + void set_cached_frame_rate(double d); + + const QString& name(); + void set_name(const QString& s); + + const ClipSpeed& speed(); + void set_speed(const ClipSpeed& s); + + AVRational time_base(); + + void reset_audio(); + void refresh(); + + long length(); + + void refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points); + Track* parent_; + + // markers + QVector& get_markers(); + + // other variables (should be deep copied/duplicated in copy()) + int IndexOfEffect(OldEffectNode* e); + QList effects; + QVector linked; + TransitionPtr opening_transition; + TransitionPtr closing_transition; + + // playback functions + void Open(); + void Cache(long playhead, bool scrubbing, QVector &nests, int playback_speed); + bool Retrieve(); + void Close(bool wait); + bool IsOpen(); + + bool UsesCacher(); + + // temporary variables + int load_id; + bool undeletable; + bool replaced; + + // caching functions + QMutex state_change_lock; + QMutex cache_lock; + + // video playback variables + 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_; + QString name_; + Media* media_; + int media_stream_; + ClipSpeed speed_; + double cached_fr_; + bool reverse_; + bool autoscale_; + + Cacher cacher; + long cacher_frame; + + NodeGraph pipeline_; + + QVector markers; + QColor color_; + bool open_; +}; + +#endif // CLIP_H diff --git a/timeline/marker.cpp b/timeline/marker.cpp index f4043da2c..24ebbd539 100644 --- a/timeline/marker.cpp +++ b/timeline/marker.cpp @@ -1,165 +1,165 @@ -/*** - - 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 "marker.h" - -#include "global/config.h" -#include "undo/undo.h" -#include "undo/undostack.h" -#include "ui/mainwindow.h" -#include "timeline/sequence.h" -#include "timeline/clip.h" -#include "panels/panels.h" -#include "panels/viewer.h" - -#include -#include - -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), - QPoint(x + MARKER_SIZE, y), - QPoint(x - MARKER_SIZE, y), - QPoint(x - MARKER_SIZE, bottom - MARKER_SIZE) - }; - p.setPen(Qt::black); - if (selected) { - p.setBrush(QColor(208, 255, 208)); - } else { - p.setBrush(QColor(128, 224, 128)); - } - p.drawPolygon(points, 5); -} - -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::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 clip 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(); - - // 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::undo_stack.push(ca); - - // redraw UI for new markers - update_ui(false); - panel_footage_viewer->update_viewer(); - - } -} - -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(); - - } - -} - -void Marker::Save(QXmlStreamWriter &stream) const -{ - stream.writeStartElement("marker"); - stream.writeAttribute("frame", QString::number(frame)); - stream.writeAttribute("name", name); - stream.writeEndElement(); -} +/*** + + 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 "marker.h" + +#include "global/config.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "ui/mainwindow.h" +#include "timeline/sequence.h" +#include "timeline/clip.h" +#include "panels/panels.h" +#include "panels/viewer.h" + +#include +#include + +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), + QPoint(x + MARKER_SIZE, y), + QPoint(x - MARKER_SIZE, y), + QPoint(x - MARKER_SIZE, bottom - MARKER_SIZE) + }; + p.setPen(Qt::black); + if (selected) { + p.setBrush(QColor(208, 255, 208)); + } else { + p.setBrush(QColor(128, 224, 128)); + } + p.drawPolygon(points, 5); +} + +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::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 clip 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(); + + // 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::undo_stack.push(ca); + + // redraw UI for new markers + update_ui(false); + panel_footage_viewer->update_viewer(); + + } +} + +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(); + + } + +} + +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 7d8c9ad7a..f56f86b8c 100644 --- a/timeline/marker.h +++ b/timeline/marker.h @@ -1,46 +1,46 @@ -/*** - - 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 MARKER_H -#define MARKER_H - -#define MARKER_SIZE 4 - -#include -#include -#include -#include - -class Clip; -class Sequence; - -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); -}; - -#endif // MARKER_H +/*** + + 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 MARKER_H +#define MARKER_H + +#define MARKER_SIZE 4 + +#include +#include +#include +#include + +class Clip; +class Sequence; + +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); +}; + +#endif // MARKER_H diff --git a/timeline/mediaimportdata.cpp b/timeline/mediaimportdata.cpp index 1358576e7..a323c7b1c 100644 --- a/timeline/mediaimportdata.cpp +++ b/timeline/mediaimportdata.cpp @@ -1,17 +1,17 @@ -#include "mediaimportdata.h" - -olive::timeline::MediaImportData::MediaImportData(Media *media, olive::timeline::MediaImportType import_type) : - media_(media), - import_type_(import_type) -{ -} - -Media *olive::timeline::MediaImportData::media() const -{ - return media_; -} - -olive::timeline::MediaImportType olive::timeline::MediaImportData::type() const -{ - return import_type_; -} +#include "mediaimportdata.h" + +olive::timeline::MediaImportData::MediaImportData(Media *media, olive::timeline::MediaImportType import_type) : + media_(media), + import_type_(import_type) +{ +} + +Media *olive::timeline::MediaImportData::media() const +{ + return media_; +} + +olive::timeline::MediaImportType olive::timeline::MediaImportData::type() const +{ + return import_type_; +} diff --git a/timeline/mediaimportdata.h b/timeline/mediaimportdata.h index 866ea37dd..70597c40e 100644 --- a/timeline/mediaimportdata.h +++ b/timeline/mediaimportdata.h @@ -1,29 +1,29 @@ -#ifndef MEDIAIMPORTDATA_H -#define MEDIAIMPORTDATA_H - -#include "project/media.h" - -namespace olive { -namespace timeline { - -enum MediaImportType { - kImportVideoOnly, - kImportAudioOnly, - kImportBoth -}; - -class MediaImportData { -public: - MediaImportData(Media* media = nullptr, MediaImportType import_type = kImportBoth); - Media* media() const; - MediaImportType type() const; -private: - Media* media_; - MediaImportType import_type_; -}; - -} -} - - -#endif // MEDIAIMPORTDATA_H +#ifndef MEDIAIMPORTDATA_H +#define MEDIAIMPORTDATA_H + +#include "project/media.h" + +namespace olive { +namespace timeline { + +enum MediaImportType { + kImportVideoOnly, + kImportAudioOnly, + kImportBoth +}; + +class MediaImportData { +public: + MediaImportData(Media* media = nullptr, MediaImportType import_type = kImportBoth); + Media* media() const; + MediaImportType type() const; +private: + Media* media_; + MediaImportType import_type_; +}; + +} +} + + +#endif // MEDIAIMPORTDATA_H diff --git a/timeline/selection.h b/timeline/selection.h index bdf045eac..519ea5939 100644 --- a/timeline/selection.h +++ b/timeline/selection.h @@ -1,53 +1,53 @@ -/*** - - 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 SELECTION_H -#define SELECTION_H - -#include - -class Clip; -class Track; - -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 +/*** + + 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 SELECTION_H +#define SELECTION_H + +#include + +class Clip; +class Track; + +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 2c5a5049f..0d419cc04 100644 --- a/timeline/sequence.cpp +++ b/timeline/sequence.cpp @@ -1,1404 +1,1404 @@ -/*** - - 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 "sequence.h" - -#include - -#include "timelinefunctions.h" -#include "panels/panels.h" -#include "global/clipboard.h" -#include "global/config.h" -#include "global/debug.h" - -Sequence::Sequence() : - playhead(0), - using_workarea(false), - workarea_in(0), - workarea_out(0), - wrapper_sequence(false), - texture_io(nullptr) -{ - AddTrack(olive::kTypeVideo); - AddTrack(olive::kTypeAudio); -} - -SequencePtr Sequence::copy() { - SequencePtr s = std::make_shared(); - s->name_ = tr("%1 (copy)").arg(name_); - s->set_width(width()); - s->set_height(height()); - s->frame_rate_ = frame_rate_; - s->audio_frequency_ = audio_frequency_; - s->audio_layout_ = audio_layout_; - - /* FIXME - // deep copy all of the sequence's clips - for (int i=0;itrack_lists_[i] = track_lists_.at(i)->copy(s.get()); - } - */ - - // copy all of the sequence's markers - s->markers = markers; - - return s; -} - -void Sequence::Save(QXmlStreamWriter &stream) -{ - // Provide unique IDs for each Clip - QVector all_clips = GetAllClips(); - for (int i=0;iload_id = i; - } - - 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; - - /* FIXME - for (int j=0;jSave(stream); - } - */ - - for (int j=0;j(all_tracks.at(i)); - - if (t != nullptr) { - end_frame = qMax(t->GetEndFrame(), end_frame); - } - } - - return end_frame; -} - -QVector Sequence::GetAllClips() -{ - QVector all_clips; - - const QObjectList& tracks = children(); - - for (int j=0;j(tracks.at(j)); - - if (t != nullptr) { - all_clips.append(t->GetAllClips()); - } - - } - - return all_clips; -} - -QVector Sequence::GetTrackList(olive::TrackType type) -{ - QVector tracks; - - const QObjectList& all_tracks = children(); - - for (int i=0;i(all_tracks.at(i)); - if (t->type() == type) { - tracks.append(t); - } - } - - return tracks; -} - -GLuint Sequence::texture() -{ - if (texture_io == nullptr) return 0; - - texture_io->ParentNode()->Process(0); - return texture_io->GetValue().value(); -} - -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; - - const QObjectList& all_tracks = children(); - - for (int j=0;j(all_tracks.at(j)); - - selected_clips.append(t->GetSelectedClips(containing)); - } - - return selected_clips; -} - -void Sequence::AddClipsFromGhosts(ComboAction* ca, const QVector& ghosts) -{ - // 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())); - } - } - } - - if (olive::config.enable_seek_to_import) { - panel_sequence_viewer->seek(earliest_point); - } - - olive::timeline::snapped = false; -} - -void Sequence::MoveClip(Clip *c, ComboAction *ca, long iin, long iout, long iclip_in, Track *itrack, bool verify_transitions, bool relative) -{ - 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; - - const QObjectList& all_tracks = children(); - - 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 j=0;j(all_tracks.at(j)); - areas.append(Selection(in_point, in_point+1, t)); - } - - // 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 j=0;j(all_tracks.at(j)); - areas.append(Selection(area_in, area_out, t)); - } - - // 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; - - const QObjectList& all_tracks = children(); - - for (int j=0;j(all_tracks.at(j)); - areas_to_delete.append(Selection(workarea_in, workarea_out, t)); - } - - 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) -{ - const QObjectList& all_tracks = children(); - - for (int j=0;j(all_tracks.at(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) { - - const QObjectList& all_tracks = children(); - QObject* obj; - - foreach (obj, all_tracks) { - - Track* track = static_cast(obj); - - 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()))); - } - } - } - } - } - } - - // Get ripple point and ripple length - long minimum_in = LONG_MAX; - long minimum_length = LONG_MAX; - for (int i=0;i area_copy = areas; - for (int i=0;iDeselectArea(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) { - - const QObjectList& all_tracks = children(); - - for (int j=0;j(all_tracks.at(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 = 0; - - 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); - } - -} - -OldEffectNode *Sequence::GetSelectedGizmo() -{ - OldEffectNode* gizmo_ptr = nullptr; - - QVector clips = GetAllClips(); - - for (int i=0;iIsActiveAt(playhead) - && c->IsSelected()) { - // This clip is selected and currently active - we'll use this for gizmos - - if (!c->effects.isEmpty()) { - - // find which effect has gizmos selected, or default to the first gizmo effect we find if there is - // none selected - - for (int j=0;jeffects.size();j++) { - OldEffectNode* e = c->effects.at(j).get(); - - // retrieve gizmo data from effect - if (e->are_gizmos_enabled()) { - if (gizmo_ptr == nullptr) { - gizmo_ptr = e; - } - if (panel_effect_controls->IsEffectSelected(e)) { - gizmo_ptr = e; - break; - } - } - } - } - - if (gizmo_ptr != nullptr) { - break; - } - } - } - - return gizmo_ptr; -} - -void Sequence::SelectAll() -{ - const QObjectList& all_tracks = children(); - for (int i=0;i(all_tracks.at(i))->SelectAll(); - } -} - -void Sequence::SelectAtPlayhead() -{ - const QObjectList& all_tracks = children(); - for (int i=0;i(all_tracks.at(i))->SelectAtPoint(playhead); - } -} - -void Sequence::ClearSelections() -{ - const QObjectList& all_tracks = children(); - for (int i=0;i(all_tracks.at(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;i track_clips = s.track()->GetAllClips(); - - for (int j=0;jtimeline_out() < s.in() || c->timeline_in() > s.out())) { - - // If so, we'll be copying this clip - original_clips.append(c); - - ClipPtr copy = c->copy(nullptr); - - // 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()); - } - - if (copy->timeline_out() > s.out()) { - copy->set_timeline_out(s.out()); - } - - // 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); - - } - } - } - - // 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); - } - - } -} - -QVector Sequence::Selections() -{ - QVector selections; - - const QObjectList& all_tracks = children(); - - for (int i=0;i(all_tracks.at(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); -} - -Track *Sequence::PreviousTrack(Track *t) -{ - Track* previous_track = nullptr; - - // Loop through tracks - const QObjectList& all_tracks = children(); - for (int i=0;i(all_tracks.at(i)); - - if (comp_track == t) { - // If this is the track, we'll know the previous track by now - break; - } else if (comp_track->type() == t->type()) { - // Otherwise, we'll keep "track" of it - previous_track = t; - } - } - - return previous_track; -} - -Track *Sequence::NextTrack(Track *t) -{ - if (LastTrack(t->type()) == t) { - - return AddTrack(t->type()); - - } else { - - Track* next_track = nullptr; - - const QObjectList& all_tracks = children(); - - for (int i=all_tracks.size()-1;i>=0;i--) { - - Track* track = static_cast(all_tracks.at(i)); - - if (track == t) { - break; - } - - if (track->type() == t->type()) { - next_track = track; - } - - } - - return next_track; - } -} - -Track *Sequence::SiblingTrack(Track *t, int diff) -{ - if (diff == 0) { - return t; - } - - return TrackAt(t->type(), qMax(0, IndexOfTrack(t) + diff)); -} - -int Sequence::IndexOfTrack(Track *t) -{ - int counter = -1; - - const QObjectList& all_tracks = children(); - - for (int i=0;i(all_tracks.at(i)); - - if (track->type() == t->type()) { - counter++; - } - - if (track == t) { - return counter; - } - } - - return -1; -} - -Track *Sequence::FirstTrack(olive::TrackType type) -{ - const QObjectList& all_tracks = children(); - - for (int i=0;i(all_tracks.at(i)); - - if (track->type() == type) { - return track; - } - } - return nullptr; -} - -Track *Sequence::LastTrack(olive::TrackType type) -{ - const QObjectList& all_tracks = children(); - - for (int i=all_tracks.size()-1;i>=0;i--) { - - Track* track = static_cast(all_tracks.at(i)); - - if (track->type() == type) { - return track; - } - } - return nullptr; -} - -Track *Sequence::TrackAt(olive::TrackType type, int index) -{ - int counter = -1; - - const QObjectList& all_tracks = children(); - - for (int i=0;i(all_tracks.at(i)); - - if (track->type() == type) { - counter++; - } - - if (counter == index) { - return track; - } - } - - Track* t; - - do { - t = AddTrack(type); - counter++; - } while (index > counter); - - return t; -} - -int Sequence::TrackCount(olive::TrackType type) -{ - int counter = 0; - - const QObjectList& all_tracks = children(); - - for (int i=0;i(all_tracks.at(i)); - - if (track->type() == type) { - counter++; - } - } - - return counter; -} - -Track* Sequence::AddTrack(olive::TrackType type) -{ - Track* t = new Track(nullptr, type); - t->setParent(this); - return t; -} - -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; -} +/*** + + 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 "sequence.h" + +#include + +#include "timelinefunctions.h" +#include "panels/panels.h" +#include "global/clipboard.h" +#include "global/config.h" +#include "global/debug.h" + +Sequence::Sequence() : + playhead(0), + using_workarea(false), + workarea_in(0), + workarea_out(0), + wrapper_sequence(false), + texture_io(nullptr) +{ + AddTrack(olive::kTypeVideo); + AddTrack(olive::kTypeAudio); +} + +SequencePtr Sequence::copy() { + SequencePtr s = std::make_shared(); + s->name_ = tr("%1 (copy)").arg(name_); + s->set_width(width()); + s->set_height(height()); + s->frame_rate_ = frame_rate_; + s->audio_frequency_ = audio_frequency_; + s->audio_layout_ = audio_layout_; + + /* FIXME + // deep copy all of the sequence's clips + for (int i=0;itrack_lists_[i] = track_lists_.at(i)->copy(s.get()); + } + */ + + // copy all of the sequence's markers + s->markers = markers; + + return s; +} + +void Sequence::Save(QXmlStreamWriter &stream) +{ + // Provide unique IDs for each Clip + QVector all_clips = GetAllClips(); + for (int i=0;iload_id = i; + } + + 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; + + /* FIXME + for (int j=0;jSave(stream); + } + */ + + for (int j=0;j(all_tracks.at(i)); + + if (t != nullptr) { + end_frame = qMax(t->GetEndFrame(), end_frame); + } + } + + return end_frame; +} + +QVector Sequence::GetAllClips() +{ + QVector all_clips; + + const QObjectList& tracks = children(); + + for (int j=0;j(tracks.at(j)); + + if (t != nullptr) { + all_clips.append(t->GetAllClips()); + } + + } + + return all_clips; +} + +QVector Sequence::GetTrackList(olive::TrackType type) +{ + QVector tracks; + + const QObjectList& all_tracks = children(); + + for (int i=0;i(all_tracks.at(i)); + if (t->type() == type) { + tracks.append(t); + } + } + + return tracks; +} + +GLuint Sequence::texture() +{ + if (texture_io == nullptr) return 0; + + texture_io->ParentNode()->Process(0); + return texture_io->GetValue().value(); +} + +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; + + const QObjectList& all_tracks = children(); + + for (int j=0;j(all_tracks.at(j)); + + selected_clips.append(t->GetSelectedClips(containing)); + } + + return selected_clips; +} + +void Sequence::AddClipsFromGhosts(ComboAction* ca, const QVector& ghosts) +{ + // 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())); + } + } + } + + if (olive::config.enable_seek_to_import) { + panel_sequence_viewer->seek(earliest_point); + } + + olive::timeline::snapped = false; +} + +void Sequence::MoveClip(Clip *c, ComboAction *ca, long iin, long iout, long iclip_in, Track *itrack, bool verify_transitions, bool relative) +{ + 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; + + const QObjectList& all_tracks = children(); + + 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 j=0;j(all_tracks.at(j)); + areas.append(Selection(in_point, in_point+1, t)); + } + + // 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 j=0;j(all_tracks.at(j)); + areas.append(Selection(area_in, area_out, t)); + } + + // 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; + + const QObjectList& all_tracks = children(); + + for (int j=0;j(all_tracks.at(j)); + areas_to_delete.append(Selection(workarea_in, workarea_out, t)); + } + + 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) +{ + const QObjectList& all_tracks = children(); + + for (int j=0;j(all_tracks.at(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) { + + const QObjectList& all_tracks = children(); + QObject* obj; + + foreach (obj, all_tracks) { + + Track* track = static_cast(obj); + + 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()))); + } + } + } + } + } + } + + // Get ripple point and ripple length + long minimum_in = LONG_MAX; + long minimum_length = LONG_MAX; + for (int i=0;i area_copy = areas; + for (int i=0;iDeselectArea(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) { + + const QObjectList& all_tracks = children(); + + for (int j=0;j(all_tracks.at(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 = 0; + + 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); + } + +} + +OldEffectNode *Sequence::GetSelectedGizmo() +{ + OldEffectNode* gizmo_ptr = nullptr; + + QVector clips = GetAllClips(); + + for (int i=0;iIsActiveAt(playhead) + && c->IsSelected()) { + // This clip is selected and currently active - we'll use this for gizmos + + if (!c->effects.isEmpty()) { + + // find which effect has gizmos selected, or default to the first gizmo effect we find if there is + // none selected + + for (int j=0;jeffects.size();j++) { + OldEffectNode* e = c->effects.at(j).get(); + + // retrieve gizmo data from effect + if (e->are_gizmos_enabled()) { + if (gizmo_ptr == nullptr) { + gizmo_ptr = e; + } + if (panel_effect_controls->IsEffectSelected(e)) { + gizmo_ptr = e; + break; + } + } + } + } + + if (gizmo_ptr != nullptr) { + break; + } + } + } + + return gizmo_ptr; +} + +void Sequence::SelectAll() +{ + const QObjectList& all_tracks = children(); + for (int i=0;i(all_tracks.at(i))->SelectAll(); + } +} + +void Sequence::SelectAtPlayhead() +{ + const QObjectList& all_tracks = children(); + for (int i=0;i(all_tracks.at(i))->SelectAtPoint(playhead); + } +} + +void Sequence::ClearSelections() +{ + const QObjectList& all_tracks = children(); + for (int i=0;i(all_tracks.at(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;i track_clips = s.track()->GetAllClips(); + + for (int j=0;jtimeline_out() < s.in() || c->timeline_in() > s.out())) { + + // If so, we'll be copying this clip + original_clips.append(c); + + ClipPtr copy = c->copy(nullptr); + + // 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()); + } + + if (copy->timeline_out() > s.out()) { + copy->set_timeline_out(s.out()); + } + + // 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); + + } + } + } + + // 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); + } + + } +} + +QVector Sequence::Selections() +{ + QVector selections; + + const QObjectList& all_tracks = children(); + + for (int i=0;i(all_tracks.at(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); +} + +Track *Sequence::PreviousTrack(Track *t) +{ + Track* previous_track = nullptr; + + // Loop through tracks + const QObjectList& all_tracks = children(); + for (int i=0;i(all_tracks.at(i)); + + if (comp_track == t) { + // If this is the track, we'll know the previous track by now + break; + } else if (comp_track->type() == t->type()) { + // Otherwise, we'll keep "track" of it + previous_track = t; + } + } + + return previous_track; +} + +Track *Sequence::NextTrack(Track *t) +{ + if (LastTrack(t->type()) == t) { + + return AddTrack(t->type()); + + } else { + + Track* next_track = nullptr; + + const QObjectList& all_tracks = children(); + + for (int i=all_tracks.size()-1;i>=0;i--) { + + Track* track = static_cast(all_tracks.at(i)); + + if (track == t) { + break; + } + + if (track->type() == t->type()) { + next_track = track; + } + + } + + return next_track; + } +} + +Track *Sequence::SiblingTrack(Track *t, int diff) +{ + if (diff == 0) { + return t; + } + + return TrackAt(t->type(), qMax(0, IndexOfTrack(t) + diff)); +} + +int Sequence::IndexOfTrack(Track *t) +{ + int counter = -1; + + const QObjectList& all_tracks = children(); + + for (int i=0;i(all_tracks.at(i)); + + if (track->type() == t->type()) { + counter++; + } + + if (track == t) { + return counter; + } + } + + return -1; +} + +Track *Sequence::FirstTrack(olive::TrackType type) +{ + const QObjectList& all_tracks = children(); + + for (int i=0;i(all_tracks.at(i)); + + if (track->type() == type) { + return track; + } + } + return nullptr; +} + +Track *Sequence::LastTrack(olive::TrackType type) +{ + const QObjectList& all_tracks = children(); + + for (int i=all_tracks.size()-1;i>=0;i--) { + + Track* track = static_cast(all_tracks.at(i)); + + if (track->type() == type) { + return track; + } + } + return nullptr; +} + +Track *Sequence::TrackAt(olive::TrackType type, int index) +{ + int counter = -1; + + const QObjectList& all_tracks = children(); + + for (int i=0;i(all_tracks.at(i)); + + if (track->type() == type) { + counter++; + } + + if (counter == index) { + return track; + } + } + + Track* t; + + do { + t = AddTrack(type); + counter++; + } while (index > counter); + + return t; +} + +int Sequence::TrackCount(olive::TrackType type) +{ + int counter = 0; + + const QObjectList& all_tracks = children(); + + for (int i=0;i(all_tracks.at(i)); + + if (track->type() == type) { + counter++; + } + } + + return counter; +} + +Track* Sequence::AddTrack(olive::TrackType type) +{ + Track* t = new Track(nullptr, type); + t->setParent(this); + return t; +} + +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 5fadbc534..8cb58607a 100644 --- a/timeline/sequence.h +++ b/timeline/sequence.h @@ -1,161 +1,161 @@ -/*** - - 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 SEQUENCE_H -#define SEQUENCE_H - -#include -#include - -#include "clip.h" -#include "marker.h" -#include "selection.h" -#include "ghost.h" -#include "nodes/nodegraph.h" - -class Sequence : public NodeGraph { - Q_OBJECT -public: - Sequence(); - SequencePtr copy(); - - void Save(QXmlStreamWriter& stream); - - const QString& name(); - void set_name(const QString& s); - - const double& frame_rate(); - void set_frame_rate(const double& d); - - const int& audio_frequency(); - void set_audio_frequency(const int& f); - - const int& audio_layout(); - void set_audio_layout(const int& l); - - long GetEndFrame(); - QVector GetAllClips(); - - /** - * @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); - - 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); - - OldEffectNode* GetSelectedGizmo(); - - bool IsClipSelected(Clip* clip, bool containing = true); - bool IsTransitionSelected(Transition* t); - - void SelectAll(); - void SelectAtPlayhead(); - void ClearSelections(); - void AddSelectionsToClipboard(bool delete_originals); - QVector Selections(); - void SetSelections(const QVector& selections); - void TidySelections(); - - Track* PreviousTrack(Track* t); - Track* NextTrack(Track* t); - Track* SiblingTrack(Track* t, int diff); - int IndexOfTrack(Track* t); - Track* FirstTrack(olive::TrackType type); - Track* LastTrack(olive::TrackType type); - Track* TrackAt(olive::TrackType type, int index); - int TrackCount(olive::TrackType type); - QVector GetTrackList(olive::TrackType type); - - // FIXME: TEST CODE - GLuint texture(); - NodeIO* texture_io; - // END TEST CODE - - long playhead; - - bool using_workarea; - long workarea_in; - long workarea_out; - - bool wrapper_sequence; - - int save_id; - - QVector markers; - -private: - Track *AddTrack(olive::TrackType type); - - //QVector tracks_; - - 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); - - QString name_; - double frame_rate_; - int audio_frequency_; - int audio_layout_; -}; - -using SequencePtr = std::shared_ptr; - -#endif // SEQUENCE_H +/*** + + 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 SEQUENCE_H +#define SEQUENCE_H + +#include +#include + +#include "clip.h" +#include "marker.h" +#include "selection.h" +#include "ghost.h" +#include "nodes/nodegraph.h" + +class Sequence : public NodeGraph { + Q_OBJECT +public: + Sequence(); + SequencePtr copy(); + + void Save(QXmlStreamWriter& stream); + + const QString& name(); + void set_name(const QString& s); + + const double& frame_rate(); + void set_frame_rate(const double& d); + + const int& audio_frequency(); + void set_audio_frequency(const int& f); + + const int& audio_layout(); + void set_audio_layout(const int& l); + + long GetEndFrame(); + QVector GetAllClips(); + + /** + * @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); + + 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); + + OldEffectNode* GetSelectedGizmo(); + + bool IsClipSelected(Clip* clip, bool containing = true); + bool IsTransitionSelected(Transition* t); + + void SelectAll(); + void SelectAtPlayhead(); + void ClearSelections(); + void AddSelectionsToClipboard(bool delete_originals); + QVector Selections(); + void SetSelections(const QVector& selections); + void TidySelections(); + + Track* PreviousTrack(Track* t); + Track* NextTrack(Track* t); + Track* SiblingTrack(Track* t, int diff); + int IndexOfTrack(Track* t); + Track* FirstTrack(olive::TrackType type); + Track* LastTrack(olive::TrackType type); + Track* TrackAt(olive::TrackType type, int index); + int TrackCount(olive::TrackType type); + QVector GetTrackList(olive::TrackType type); + + // FIXME: TEST CODE + GLuint texture(); + NodeIO* texture_io; + // END TEST CODE + + long playhead; + + bool using_workarea; + long workarea_in; + long workarea_out; + + bool wrapper_sequence; + + int save_id; + + QVector markers; + +private: + Track *AddTrack(olive::TrackType type); + + //QVector tracks_; + + 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); + + QString name_; + double frame_rate_; + int audio_frequency_; + int audio_layout_; +}; + +using SequencePtr = std::shared_ptr; + +#endif // SEQUENCE_H diff --git a/timeline/timelinetools.h b/timeline/timelinetools.h index 4fbe6f704..dfbaec221 100644 --- a/timeline/timelinetools.h +++ b/timeline/timelinetools.h @@ -1,47 +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 +/*** + + 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/ui/blur.cpp b/ui/blur.cpp index c13ede7d6..29052b585 100644 --- a/ui/blur.cpp +++ b/ui/blur.cpp @@ -1,85 +1,85 @@ -/*** - - 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) { - int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; - int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; - - int r1 = rect.top(); - int r2 = rect.bottom(); - int c1 = rect.left(); - int c2 = rect.right(); - - int bpl = result.bytesPerLine(); - int rgba[4]; - unsigned char* p; - - int i1 = 0; - int i2 = 3; - - if (alphaOnly) - i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); - - for (int col = c1; col <= c2; col++) { - p = result.scanLine(r1) + col * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p += bpl; - for (int j = r1; j < r2; j++, p += bpl) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - for (int row = r1; row <= r2; row++) { - p = result.scanLine(row) + c1 * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p += 4; - for (int j = c1; j < c2; j++, p += 4) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - for (int col = c1; col <= c2; col++) { - p = result.scanLine(r2) + col * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p -= bpl; - for (int j = r1; j < r2; j++, p -= bpl) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } - - for (int row = r1; row <= r2; row++) { - p = result.scanLine(row) + c2 * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; - - p -= 4; - for (int j = c1; j < c2; j++, p -= 4) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } -} +/*** + + 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) { + int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; + int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; + + int r1 = rect.top(); + int r2 = rect.bottom(); + int c1 = rect.left(); + int c2 = rect.right(); + + int bpl = result.bytesPerLine(); + int rgba[4]; + unsigned char* p; + + int i1 = 0; + int i2 = 3; + + if (alphaOnly) + i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r1) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p += bpl; + for (int j = r1; j < r2; j++, p += bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c1 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p += 4; + for (int j = c1; j < c2; j++, p += 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r2) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p -= bpl; + for (int j = r1; j < r2; j++, p -= bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } + + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c2 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; + + p -= 4; + for (int j = c1; j < c2; j++, p -= 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } +} diff --git a/ui/blur.h b/ui/blur.h index ca2b930d8..461f67525 100644 --- a/ui/blur.h +++ b/ui/blur.h @@ -1,52 +1,52 @@ -/*** - - 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 - -#include -#include - -namespace olive { - namespace ui { - /** - * @brief Convenience function for blurring a QImage - * - * @param result - * - * QImage to blur - * - * @param rect - * - * The rectangle of the QImage to blur. Use QImage::rect() to blur the entire image. - * - * @param radius - * - * The blur radius - how much to blur the image. - * - * @param alphaOnly - * - * True if only the alpha channel should be blurred rather than the entire RGBA space. - */ - void blur(QImage& result, const QRect& rect, int radius, bool alphaOnly); - } -} - -#endif // BLUR_H +/*** + + 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 + +#include +#include + +namespace olive { + namespace ui { + /** + * @brief Convenience function for blurring a QImage + * + * @param result + * + * QImage to blur + * + * @param rect + * + * The rectangle of the QImage to blur. Use QImage::rect() to blur the entire image. + * + * @param radius + * + * The blur radius - how much to blur the image. + * + * @param alphaOnly + * + * True if only the alpha channel should be blurred rather than the entire RGBA space. + */ + void blur(QImage& result, const QRect& rect, int radius, bool alphaOnly); + } +} + +#endif // BLUR_H diff --git a/ui/checkboxex.cpp b/ui/checkboxex.cpp index e76c12985..fefb88668 100644 --- a/ui/checkboxex.cpp +++ b/ui/checkboxex.cpp @@ -1,33 +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); -} +/*** + + 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 index b5d20fd17..a7ebb054f 100644 --- a/ui/checkboxex.h +++ b/ui/checkboxex.h @@ -1,35 +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 +/*** + + 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 dcd2c1c72..fd888d46b 100644 --- a/ui/clickablelabel.cpp +++ b/ui/clickablelabel.cpp @@ -1,38 +1,38 @@ -/*** - - 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 "clickablelabel.h" - -ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f) : - QLabel(parent, f) -{} - -ClickableLabel::ClickableLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : - QLabel(text, parent, f) -{} - -void ClickableLabel::mousePressEvent(QMouseEvent *) { - emit clicked(); -} - -void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *ev) -{ - emit double_clicked(); -} +/*** + + 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 "clickablelabel.h" + +ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f) : + QLabel(parent, f) +{} + +ClickableLabel::ClickableLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : + QLabel(text, parent, f) +{} + +void ClickableLabel::mousePressEvent(QMouseEvent *) { + emit clicked(); +} + +void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *ev) +{ + emit double_clicked(); +} diff --git a/ui/clickablelabel.h b/ui/clickablelabel.h index 2cf9d6848..dc2c8bb3f 100644 --- a/ui/clickablelabel.h +++ b/ui/clickablelabel.h @@ -1,43 +1,43 @@ -/*** - - 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 CLICKABLELABEL_H -#define CLICKABLELABEL_H - -#include - -/** - * @brief The ClickableLabel class - * - * Simple QLabel-derived class that emits a clicked() signal when the widget receives a mouse press event. - */ -class ClickableLabel : public QLabel { - Q_OBJECT -public: - 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 double_clicked(); -}; - -#endif // CLICKABLELABEL_H +/*** + + 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 CLICKABLELABEL_H +#define CLICKABLELABEL_H + +#include + +/** + * @brief The ClickableLabel class + * + * Simple QLabel-derived class that emits a clicked() signal when the widget receives a mouse press event. + */ +class ClickableLabel : public QLabel { + Q_OBJECT +public: + 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 double_clicked(); +}; + +#endif // CLICKABLELABEL_H diff --git a/ui/collapsiblewidget.cpp b/ui/collapsiblewidget.cpp index d8136ce91..0d32b4835 100644 --- a/ui/collapsiblewidget.cpp +++ b/ui/collapsiblewidget.cpp @@ -1,198 +1,198 @@ -/*** - - 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 "collapsiblewidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ui/icons.h" -#include "global/debug.h" - -CollapsibleWidget::CollapsibleWidget(QWidget* parent) : - QWidget(parent), - contents(nullptr) -{ - layout = new QVBoxLayout(this); - layout->setMargin(0); - layout->setSpacing(0); - - title_bar = new CollapsibleWidgetHeader(this); - title_bar->setFocusPolicy(Qt::ClickFocus); - title_bar->setAutoFillBackground(true); - title_bar_layout = new QHBoxLayout(title_bar); - title_bar_layout->setMargin(5); - enabled_check = new QCheckBox(title_bar); - enabled_check->setChecked(true); - header = new QLabel(title_bar); - collapse_button = new QPushButton(title_bar); - collapse_button->setIconSize(collapse_button->iconSize()*0.5); - collapse_button->setFlat(true); - SetTitle(tr("")); - title_bar_layout->addWidget(collapse_button); - title_bar_layout->addWidget(enabled_check); - title_bar_layout->addWidget(header); - title_bar_layout->addStretch(); - layout->addWidget(title_bar); - - connect(title_bar, SIGNAL(select()), this, SLOT(Selected())); - - set_button_icon(true); -} - -void CollapsibleWidget::Selected() { - emit deselect_others(this); -} - -bool CollapsibleWidget::IsFocused() { - if (hasFocus()) return true; - return title_bar->hasFocus(); -} - -bool CollapsibleWidget::IsExpanded() { - return contents->isVisible(); -} - -void CollapsibleWidget::SetExpanded(bool s) -{ - contents->setVisible(s); - set_button_icon(s); - emit visibleChanged(s); -} - -bool CollapsibleWidget::IsSelected() -{ - 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) { - collapse_button->setIcon(open ? olive::icon::DownArrow : olive::icon::RightArrow); -} - -void CollapsibleWidget::SetContents(QWidget* c) { - bool existing = (contents != nullptr); - contents = c; - if (!existing) { - layout->addWidget(contents); - connect(collapse_button, SIGNAL(clicked()), this, SLOT(on_visible_change())); - } -} - -QString CollapsibleWidget::Title() -{ - return header->text(); -} - -void CollapsibleWidget::SetTitle(const QString &s) { - header->setText(s); -} - -void CollapsibleWidget::on_visible_change() { - SetExpanded(!IsExpanded()); -} - -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) { - SetSelected(false); - } - } else { - SetSelected(true, !(event->modifiers() & Qt::ShiftModifier)); - } -} - -void CollapsibleWidgetHeader::paintEvent(QPaintEvent *event) { - QWidget::paintEvent(event); - QPainter p(this); - p.setPen(Qt::white); - int line_y = height() - 1; - p.drawLine(0, line_y, width(), line_y); -} +/*** + + 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 "collapsiblewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui/icons.h" +#include "global/debug.h" + +CollapsibleWidget::CollapsibleWidget(QWidget* parent) : + QWidget(parent), + contents(nullptr) +{ + layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + title_bar = new CollapsibleWidgetHeader(this); + title_bar->setFocusPolicy(Qt::ClickFocus); + title_bar->setAutoFillBackground(true); + title_bar_layout = new QHBoxLayout(title_bar); + title_bar_layout->setMargin(5); + enabled_check = new QCheckBox(title_bar); + enabled_check->setChecked(true); + header = new QLabel(title_bar); + collapse_button = new QPushButton(title_bar); + collapse_button->setIconSize(collapse_button->iconSize()*0.5); + collapse_button->setFlat(true); + SetTitle(tr("")); + title_bar_layout->addWidget(collapse_button); + title_bar_layout->addWidget(enabled_check); + title_bar_layout->addWidget(header); + title_bar_layout->addStretch(); + layout->addWidget(title_bar); + + connect(title_bar, SIGNAL(select()), this, SLOT(Selected())); + + set_button_icon(true); +} + +void CollapsibleWidget::Selected() { + emit deselect_others(this); +} + +bool CollapsibleWidget::IsFocused() { + if (hasFocus()) return true; + return title_bar->hasFocus(); +} + +bool CollapsibleWidget::IsExpanded() { + return contents->isVisible(); +} + +void CollapsibleWidget::SetExpanded(bool s) +{ + contents->setVisible(s); + set_button_icon(s); + emit visibleChanged(s); +} + +bool CollapsibleWidget::IsSelected() +{ + 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) { + collapse_button->setIcon(open ? olive::icon::DownArrow : olive::icon::RightArrow); +} + +void CollapsibleWidget::SetContents(QWidget* c) { + bool existing = (contents != nullptr); + contents = c; + if (!existing) { + layout->addWidget(contents); + connect(collapse_button, SIGNAL(clicked()), this, SLOT(on_visible_change())); + } +} + +QString CollapsibleWidget::Title() +{ + return header->text(); +} + +void CollapsibleWidget::SetTitle(const QString &s) { + header->setText(s); +} + +void CollapsibleWidget::on_visible_change() { + SetExpanded(!IsExpanded()); +} + +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) { + SetSelected(false); + } + } else { + SetSelected(true, !(event->modifiers() & Qt::ShiftModifier)); + } +} + +void CollapsibleWidgetHeader::paintEvent(QPaintEvent *event) { + QWidget::paintEvent(event); + QPainter p(this); + p.setPen(Qt::white); + int line_y = height() - 1; + p.drawLine(0, line_y, width(), line_y); +} diff --git a/ui/collapsiblewidget.h b/ui/collapsiblewidget.h index eca9536d1..514c3c63e 100644 --- a/ui/collapsiblewidget.h +++ b/ui/collapsiblewidget.h @@ -1,91 +1,91 @@ -/*** - - 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 COLLAPSIBLEWIDGET_H -#define COLLAPSIBLEWIDGET_H - -#include -#include -#include -#include -#include -#include -#include -#include - -class CollapsibleWidgetHeader : public QWidget { - Q_OBJECT -public: - CollapsibleWidgetHeader(QWidget* parent = nullptr); - - bool IsSelected(); - void SetSelected(bool s, bool deselect_others = false); - - void SetSelectable(bool s); -protected: - virtual bool event(QEvent *event) override; - virtual void mousePressEvent(QMouseEvent* event) override; - virtual void paintEvent(QPaintEvent *event) override; -signals: - void select(); -private: - bool selected_; - bool selectable_; -}; - -class CollapsibleWidget : public QWidget -{ - Q_OBJECT -public: - CollapsibleWidget(QWidget* parent = nullptr); - void SetContents(QWidget* c); - QString Title(); - void SetTitle(const QString &); - 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: - QLabel* header; - QVBoxLayout* layout; - QPushButton* collapse_button; - QFrame* line; - QHBoxLayout* title_bar_layout; - void set_button_icon(bool open); - -signals: - void deselect_others(QWidget*); - void visibleChanged(bool); - -private slots: - void on_visible_change(); - -public slots: - void Selected(); -}; - -#endif // COLLAPSIBLEWIDGET_H +/*** + + 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 COLLAPSIBLEWIDGET_H +#define COLLAPSIBLEWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class CollapsibleWidgetHeader : public QWidget { + Q_OBJECT +public: + CollapsibleWidgetHeader(QWidget* parent = nullptr); + + bool IsSelected(); + void SetSelected(bool s, bool deselect_others = false); + + void SetSelectable(bool s); +protected: + virtual bool event(QEvent *event) override; + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void paintEvent(QPaintEvent *event) override; +signals: + void select(); +private: + bool selected_; + bool selectable_; +}; + +class CollapsibleWidget : public QWidget +{ + Q_OBJECT +public: + CollapsibleWidget(QWidget* parent = nullptr); + void SetContents(QWidget* c); + QString Title(); + void SetTitle(const QString &); + 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: + QLabel* header; + QVBoxLayout* layout; + QPushButton* collapse_button; + QFrame* line; + QHBoxLayout* title_bar_layout; + void set_button_icon(bool open); + +signals: + void deselect_others(QWidget*); + void visibleChanged(bool); + +private slots: + void on_visible_change(); + +public slots: + void Selected(); +}; + +#endif // COLLAPSIBLEWIDGET_H diff --git a/ui/effectui.cpp b/ui/effectui.cpp index 29c63a69a..1f72728ee 100644 --- a/ui/effectui.cpp +++ b/ui/effectui.cpp @@ -1,358 +1,358 @@ -/*** - - 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 - -#include "timeline/clip.h" -#include "ui/menuhelper.h" -#include "ui/keyframenavigator.h" -#include "ui/clickablelabel.h" -#include "ui/menu.h" -#include "panels/panels.h" - -EffectUI::EffectUI(OldEffectNode* e) : - effect_(e) -{ - Q_ASSERT(e != nullptr); - - QString effect_name; - - // If this effect is actually a transition - if (e->type() == EFFECT_TYPE_TRANSITION) { - - Transition* t = static_cast(e); - - // Since effects can have two clip attachments, find out which one is selected - Clip* selected_clip = t->parent_clip; - bool both_selected = false; - - // Check if this is a shared transition - if (t->secondary_clip != nullptr) { - - // Check which clips are selected - if (t->secondary_clip->IsSelected()) { - - selected_clip = t->secondary_clip; - - if (t->parent_clip->IsSelected()) { - // Both clips are selected - both_selected = true; - } - - } else if (!t->parent_clip->IsSelected()) { - - // Neither are selected, but the naming scheme (no "opening" or "closing" modifier) will be the same - both_selected = true; - - } - - } - - // See if the transition is the clip's opening or closing transition and label it accordingly - if (both_selected) { - effect_name = t->name(); - } else if (selected_clip->opening_transition.get() == t) { - effect_name = tr("%1 (Opening)").arg(t->name()); - } else { - effect_name = tr("%1 (Closing)").arg(t->name()); - } - - } else { - - // Otherwise just set the title normally - effect_name = e->name(); - - } - - SetTitle(effect_name); - - QWidget* ui = new QWidget(this); - 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))); - - layout_ = new QGridLayout(ui); - layout_->setSpacing(4); - - connect(title_bar, - SIGNAL(customContextMenuRequested(const QPoint&)), - this, - SLOT(show_context_menu(const QPoint&))); - - widgets_.resize(e->ParameterCount()); - keyframe_navigators_.resize(e->ParameterCount()); - - for (int i=0;iParameterCount();i++) { - NodeIO* row = e->Parameter(i); - - ClickableLabel* row_label = new ClickableLabel(row->name()); - connect(row_label, SIGNAL(clicked()), row, SLOT(FocusRow())); - - labels_.append(row_label); - - if (row->IsNodeOutput()) { - - row_label->setAlignment(Qt::AlignRight); - layout_->addWidget(row_label, i, 2); - - } else { - - 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; - - } - } - - enabled_check->setChecked(e->IsEnabled()); - connect(enabled_check, SIGNAL(toggled(bool)), e, SLOT(SetEnabled(bool))); - connect(enabled_check, SIGNAL(toggled(bool)), e, SLOT(FieldChanged())); -} - -void EffectUI::AddAdditionalEffect(OldEffectNode *e) -{ - // Ensure this is the same kind of effect and will be fully compatible - Q_ASSERT(e->id() == effect_->id()); - - // Add 'multiple' modifier to header label (but only once) - if (additional_effects_.isEmpty()) { - QString new_title = tr("%1 (multiple)").arg(Title()); - - SetTitle(new_title); - } - - // Add effect to list - additional_effects_.append(e); - - // Attach this UI's widgets to the additional effect - for (int i=0;iParameterCount();i++) { - - NodeIO* row = effect_->Parameter(i); - - // Attach existing keyframe navigator to this effect's row - AttachKeyframeNavigationToRow(e->Parameter(i), keyframe_navigators_.at(i)); - - for (int j=0;jFieldCount();j++) { - - // Attach existing field widget to this effect's field - e->Parameter(i)->Field(j)->CreateWidget(Widget(i, j)); - - } - - } -} - -OldEffectNode *EffectUI::GetEffect() -{ - return effect_; -} - -int EffectUI::GetRowY(int row, QWidget* mapToWidget) { - - // Currently to get a Y value in the context of `mapToWidget`, we use `panel_effect_controls` as the base. Mapping - // to global doesn't work for some reason, so this is the best reference point we have. - - 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 - + mapped_coord; -} - -void EffectUI::UpdateFromEffect() -{ - OldEffectNode* effect = GetEffect(); - - for (int j=0;jParameterCount();j++) { - - NodeIO* row = effect->Parameter(j); - - for (int k=0;kFieldCount();k++) { - EffectField* field = row->Field(k); - - // Check if this UI object is attached to one effect or many - if (additional_effects_.isEmpty()) { - - field->UpdateWidgetValue(Widget(j, k), effect->Now()); - - } else { - - bool same_value = true; - - for (int i=0;i 0 ? additional_effects_.at(i-1)->Parameter(j)->Field(k) : field; - EffectField* additional_field = additional_effects_.at(i)->Parameter(j)->Field(k); - - if (additional_field->GetValueAt(additional_effects_.at(i)->Now()) - != previous_field->GetValueAt(additional_effects_.at(i)->Now())) { - same_value = false; - break; - } - } - - if (same_value) { - field->UpdateWidgetValue(Widget(j, k), effect->Now()); - } else { - field->UpdateWidgetValue(Widget(j, k), qSNaN()); - } - - } - } - } -} - -bool EffectUI::IsAttachedToClip(Clip *c) -{ - if (GetEffect()->parent_clip == c) { - return true; - } - - for (int i=0;iparent_clip == c) { - return true; - } - } - - return false; -} - -QWidget *EffectUI::Widget(int row, int field) -{ - return widgets_.at(row).at(field); -} - -void EffectUI::AttachKeyframeNavigationToRow(NodeIO *row, KeyframeNavigator *nav) -{ - if (nav == nullptr) { - return; - } - - connect(nav, SIGNAL(goto_previous_key()), row, SLOT(GoToPreviousKeyframe())); - connect(nav, SIGNAL(toggle_key()), row, SLOT(ToggleKeyframe())); - connect(nav, SIGNAL(goto_next_key()), row, SLOT(GoToNextKeyframe())); - connect(nav, SIGNAL(keyframe_enabled_changed(bool)), row, SLOT(SetKeyframingEnabled(bool))); - connect(nav, SIGNAL(clicked()), row, SLOT(FocusRow())); - connect(row, SIGNAL(KeyframingSetChanged(bool)), nav, SLOT(enable_keyframes(bool))); -} - -void EffectUI::show_context_menu(const QPoint& pos) { - if (effect_->type() == EFFECT_TYPE_EFFECT) { - Menu menu; - - Clip* c = effect_->parent_clip; - - int index = c->IndexOfEffect(effect_); - - QAction* cut_action = menu.addAction(tr("Cu&t")); - connect(cut_action, SIGNAL(triggered(bool)), this, SIGNAL(CutRequested())); - - QAction* copy_action = menu.addAction(tr("&Copy")); - connect(copy_action, SIGNAL(triggered(bool)), this, SIGNAL(CopyRequested())); - - olive::MenuHelper.create_effect_paste_action(&menu); - - menu.addSeparator(); - - QAction* move_up_action = nullptr; - QAction* move_down_action = nullptr; - - if (index > 0) { - move_up_action = menu.addAction(tr("Move &Up"), GetEffect(), SLOT(move_up())); - } - - if (index < c->effects.size() - 1) { - move_down_action = menu.addAction(tr("Move &Down"), GetEffect(), SLOT(move_down())); - } - - menu.addSeparator(); - - QAction* delete_action = menu.addAction(tr("D&elete"), GetEffect(), SLOT(delete_self())); - - // Loop through additional effects and link these too - for (int i=0;imapToGlobal(pos)); - } -} +/*** + + 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 + +#include "timeline/clip.h" +#include "ui/menuhelper.h" +#include "ui/keyframenavigator.h" +#include "ui/clickablelabel.h" +#include "ui/menu.h" +#include "panels/panels.h" + +EffectUI::EffectUI(OldEffectNode* e) : + effect_(e) +{ + Q_ASSERT(e != nullptr); + + QString effect_name; + + // If this effect is actually a transition + if (e->type() == EFFECT_TYPE_TRANSITION) { + + Transition* t = static_cast(e); + + // Since effects can have two clip attachments, find out which one is selected + Clip* selected_clip = t->parent_clip; + bool both_selected = false; + + // Check if this is a shared transition + if (t->secondary_clip != nullptr) { + + // Check which clips are selected + if (t->secondary_clip->IsSelected()) { + + selected_clip = t->secondary_clip; + + if (t->parent_clip->IsSelected()) { + // Both clips are selected + both_selected = true; + } + + } else if (!t->parent_clip->IsSelected()) { + + // Neither are selected, but the naming scheme (no "opening" or "closing" modifier) will be the same + both_selected = true; + + } + + } + + // See if the transition is the clip's opening or closing transition and label it accordingly + if (both_selected) { + effect_name = t->name(); + } else if (selected_clip->opening_transition.get() == t) { + effect_name = tr("%1 (Opening)").arg(t->name()); + } else { + effect_name = tr("%1 (Closing)").arg(t->name()); + } + + } else { + + // Otherwise just set the title normally + effect_name = e->name(); + + } + + SetTitle(effect_name); + + QWidget* ui = new QWidget(this); + 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))); + + layout_ = new QGridLayout(ui); + layout_->setSpacing(4); + + connect(title_bar, + SIGNAL(customContextMenuRequested(const QPoint&)), + this, + SLOT(show_context_menu(const QPoint&))); + + widgets_.resize(e->ParameterCount()); + keyframe_navigators_.resize(e->ParameterCount()); + + for (int i=0;iParameterCount();i++) { + NodeIO* row = e->Parameter(i); + + ClickableLabel* row_label = new ClickableLabel(row->name()); + connect(row_label, SIGNAL(clicked()), row, SLOT(FocusRow())); + + labels_.append(row_label); + + if (row->IsNodeOutput()) { + + row_label->setAlignment(Qt::AlignRight); + layout_->addWidget(row_label, i, 2); + + } else { + + 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; + + } + } + + enabled_check->setChecked(e->IsEnabled()); + connect(enabled_check, SIGNAL(toggled(bool)), e, SLOT(SetEnabled(bool))); + connect(enabled_check, SIGNAL(toggled(bool)), e, SLOT(FieldChanged())); +} + +void EffectUI::AddAdditionalEffect(OldEffectNode *e) +{ + // Ensure this is the same kind of effect and will be fully compatible + Q_ASSERT(e->id() == effect_->id()); + + // Add 'multiple' modifier to header label (but only once) + if (additional_effects_.isEmpty()) { + QString new_title = tr("%1 (multiple)").arg(Title()); + + SetTitle(new_title); + } + + // Add effect to list + additional_effects_.append(e); + + // Attach this UI's widgets to the additional effect + for (int i=0;iParameterCount();i++) { + + NodeIO* row = effect_->Parameter(i); + + // Attach existing keyframe navigator to this effect's row + AttachKeyframeNavigationToRow(e->Parameter(i), keyframe_navigators_.at(i)); + + for (int j=0;jFieldCount();j++) { + + // Attach existing field widget to this effect's field + e->Parameter(i)->Field(j)->CreateWidget(Widget(i, j)); + + } + + } +} + +OldEffectNode *EffectUI::GetEffect() +{ + return effect_; +} + +int EffectUI::GetRowY(int row, QWidget* mapToWidget) { + + // Currently to get a Y value in the context of `mapToWidget`, we use `panel_effect_controls` as the base. Mapping + // to global doesn't work for some reason, so this is the best reference point we have. + + 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 + + mapped_coord; +} + +void EffectUI::UpdateFromEffect() +{ + OldEffectNode* effect = GetEffect(); + + for (int j=0;jParameterCount();j++) { + + NodeIO* row = effect->Parameter(j); + + for (int k=0;kFieldCount();k++) { + EffectField* field = row->Field(k); + + // Check if this UI object is attached to one effect or many + if (additional_effects_.isEmpty()) { + + field->UpdateWidgetValue(Widget(j, k), effect->Now()); + + } else { + + bool same_value = true; + + for (int i=0;i 0 ? additional_effects_.at(i-1)->Parameter(j)->Field(k) : field; + EffectField* additional_field = additional_effects_.at(i)->Parameter(j)->Field(k); + + if (additional_field->GetValueAt(additional_effects_.at(i)->Now()) + != previous_field->GetValueAt(additional_effects_.at(i)->Now())) { + same_value = false; + break; + } + } + + if (same_value) { + field->UpdateWidgetValue(Widget(j, k), effect->Now()); + } else { + field->UpdateWidgetValue(Widget(j, k), qSNaN()); + } + + } + } + } +} + +bool EffectUI::IsAttachedToClip(Clip *c) +{ + if (GetEffect()->parent_clip == c) { + return true; + } + + for (int i=0;iparent_clip == c) { + return true; + } + } + + return false; +} + +QWidget *EffectUI::Widget(int row, int field) +{ + return widgets_.at(row).at(field); +} + +void EffectUI::AttachKeyframeNavigationToRow(NodeIO *row, KeyframeNavigator *nav) +{ + if (nav == nullptr) { + return; + } + + connect(nav, SIGNAL(goto_previous_key()), row, SLOT(GoToPreviousKeyframe())); + connect(nav, SIGNAL(toggle_key()), row, SLOT(ToggleKeyframe())); + connect(nav, SIGNAL(goto_next_key()), row, SLOT(GoToNextKeyframe())); + connect(nav, SIGNAL(keyframe_enabled_changed(bool)), row, SLOT(SetKeyframingEnabled(bool))); + connect(nav, SIGNAL(clicked()), row, SLOT(FocusRow())); + connect(row, SIGNAL(KeyframingSetChanged(bool)), nav, SLOT(enable_keyframes(bool))); +} + +void EffectUI::show_context_menu(const QPoint& pos) { + if (effect_->type() == EFFECT_TYPE_EFFECT) { + Menu menu; + + Clip* c = effect_->parent_clip; + + int index = c->IndexOfEffect(effect_); + + QAction* cut_action = menu.addAction(tr("Cu&t")); + connect(cut_action, SIGNAL(triggered(bool)), this, SIGNAL(CutRequested())); + + QAction* copy_action = menu.addAction(tr("&Copy")); + connect(copy_action, SIGNAL(triggered(bool)), this, SIGNAL(CopyRequested())); + + olive::MenuHelper.create_effect_paste_action(&menu); + + menu.addSeparator(); + + QAction* move_up_action = nullptr; + QAction* move_down_action = nullptr; + + if (index > 0) { + move_up_action = menu.addAction(tr("Move &Up"), GetEffect(), SLOT(move_up())); + } + + if (index < c->effects.size() - 1) { + move_down_action = menu.addAction(tr("Move &Down"), GetEffect(), SLOT(move_down())); + } + + menu.addSeparator(); + + QAction* delete_action = menu.addAction(tr("D&elete"), GetEffect(), SLOT(delete_self())); + + // Loop through additional effects and link these too + for (int i=0;imapToGlobal(pos)); + } +} diff --git a/ui/effectui.h b/ui/effectui.h index 332c2ea84..942966de4 100644 --- a/ui/effectui.h +++ b/ui/effectui.h @@ -1,223 +1,223 @@ -/*** - - 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 "nodes/oldeffectnode.h" -#include "ui/nodeui.h" -#include "ui/keyframenavigator.h" - -/** - * @brief The EffectUI class - * - * EffectUI is a complete QWidget-based representation of an Effect that can be added to any Qt layout. It overrides - * CollapsibleWidget (meaning the Effect can be collapsed to just a titlebar to save space). The titlebar is - * automatically set to the Effect's name and the contents are composed of a grid layout (QGridLayout) corresponding - * to the Effect's EffectRow and EffectField children. - * - * Many EffectUIs can be created from a single Effect, and many Effects can be attached to a single EffectUI (provided - * the Effects are all the same type). Neither gains ownership of each other and deleting an EffectUI without any other - * work is perfectly safe (deleting an Effect with an open EffectUI however, is not). - */ -class EffectUI : public CollapsibleWidget { - Q_OBJECT -public: - /** - * @brief EffectUI Constructor - * - * Creates a QWidget-based UI representation of an Effect. - * - * @param e - * - * The Effect to make a UI of. It must be a valid object. - */ - EffectUI(OldEffectNode* e); - - /** - * @brief Attach additional effects to this UI - * - * Olive allows users to modify several effects (of the same type) with one UI representation. To do this, you can - * add any amount of extra Effect objects using this function and the UI will attach all of its UI functions to that - * Effect as well without creating any new QWidgets. - * - * @param e - * - * The Effect to add to this UI object. - */ - void AddAdditionalEffect(OldEffectNode* e); - - /** - * @brief Get the primary Effect that this UI object was created for - * - * @return - * - * The Effect object passed to the constructor when creating this EffectUI. - */ - OldEffectNode* GetEffect(); - - /** - * @brief Get the Y position of a given row - * - * Retrieve the on-screen Y position of the attached Effect's EffectRow at a given index. This is primarily used for - * displaying UI elements that align with the the row's on-screen widgets (e.g. keyframes in the EffectControls - * panel). - * - * The Y value provided is specifically the center point of the row's name label. It gets mapped to a provided - * QWidget object so it can be used locally by that QWidget without further modification. - * - * @param row - * - * The index of the EffectRow to retrieve the Y position of. - * - * @param mapToWidget - * - * The widget to map the Y value to. - * - * @return - * - * The row's Y position. - */ - int GetRowY(int row, QWidget *mapToWidget = nullptr); - - /** - * @brief Update widgets with the current Effect's values. - * - * When the Timeline playhead moves, the current values in the Effect might change if its fields are keyframed. - * In order to visually update these values on the UI, this function should be called. It will loop through all - * fields of all attached effects and update them to the value at the current Timeline playhead. - * - * Currently this function is called by update_ui() which is also responsible for updating other parts of the UI - * like the Timeline and Viewer so they all get updated together. - */ - void UpdateFromEffect(); - - /** - * @brief Check if a given Clip has an Effect referenced by this EffectUI - * - * Olive allows users to modify several effects (of the same type) with one UI representation. The behavior is if - * multiple clips are selected that have effects of the same type, all those Effects can be modified by the same - * EffectUI object. However this behavior is undesirable if a single Clip has more than one of the same type of Effect - * (e.g. two or more blurs). In this scenario, the user will most likely expect two separate UI objects for each of - * these effects individually, rather than consolidating them into one UI object. - * - * To address this, EffectControls will check this function to determine if this EffectUI already references - * an Effect of this type from this Clip. If it does, it's assumed a new EffectUI should be made rather than - * consolidating that Effect into the same EffectUI. - * - * @param c - * - * The Clip to determine whether an Effect of this type is already referenced by this EffectUI. - * - * @return - * - * True is an Effect from this Clip is already attached to this EffectUI. - */ - bool IsAttachedToClip(Clip* c); - -protected: - -signals: - /** - * @brief Cut signal - * - * Emitted when the user selects Cut from the right-click context menu. - */ - void CutRequested(); - - /** - * @brief Copy signal - * - * Emitted when the user selects Copy from the right-click context menu. - */ - void CopyRequested(); -private: - /** - * @brief Retrieve the QWidget corresponding a specific EffectField - * - * Convenience function equivalent to widgets_.at(row).at(field). - * - * @param row - * - * EffectRow index to retrieve field QWidget from - * - * @param field - * - * EffectField index to retrieve QWidget from - * - * @return - * - * The QWidget at this row and field index. - */ - QWidget* Widget(int row, int field); - - /** - * @brief Internal reference to the Effect this object was constructed around. - */ - OldEffectNode* effect_; - - /** - * @brief Internal array of additional Effect objects attached to this UI. - */ - QVector additional_effects_; - - /** - * @brief Layout for UI widgets - */ - QGridLayout* layout_; - - /** - * @brief Grid array of QWidgets corresponding to the Effect's rows and fields - */ - QVector< QVector > widgets_; - - /** - * @brief Array of QLabel objects corresponding to each row's name(). - */ - QVector labels_; - - /** - * @brief Array of KeyframeNavigator objects corresponding to each row. - */ - QVector keyframe_navigators_; - - /** - * @brief Attach a KeyframeNavigator object to an EffectRow. - * - * Internal function for connecting a KeyframeNavigator UI object to an EffectRow. - * - * @param row - * - * The EffectRow object. - * - * @param nav - * - * The KeyframeNavigator object. - */ - void AttachKeyframeNavigationToRow(NodeIO* row, KeyframeNavigator* nav); -private slots: - /** - * @brief Slot for titlebar's right-click signal to show a context menu for extra Effect functions. - */ - void show_context_menu(const QPoint&); -}; - -#endif // EFFECTUI_H +/*** + + 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 "nodes/oldeffectnode.h" +#include "ui/nodeui.h" +#include "ui/keyframenavigator.h" + +/** + * @brief The EffectUI class + * + * EffectUI is a complete QWidget-based representation of an Effect that can be added to any Qt layout. It overrides + * CollapsibleWidget (meaning the Effect can be collapsed to just a titlebar to save space). The titlebar is + * automatically set to the Effect's name and the contents are composed of a grid layout (QGridLayout) corresponding + * to the Effect's EffectRow and EffectField children. + * + * Many EffectUIs can be created from a single Effect, and many Effects can be attached to a single EffectUI (provided + * the Effects are all the same type). Neither gains ownership of each other and deleting an EffectUI without any other + * work is perfectly safe (deleting an Effect with an open EffectUI however, is not). + */ +class EffectUI : public CollapsibleWidget { + Q_OBJECT +public: + /** + * @brief EffectUI Constructor + * + * Creates a QWidget-based UI representation of an Effect. + * + * @param e + * + * The Effect to make a UI of. It must be a valid object. + */ + EffectUI(OldEffectNode* e); + + /** + * @brief Attach additional effects to this UI + * + * Olive allows users to modify several effects (of the same type) with one UI representation. To do this, you can + * add any amount of extra Effect objects using this function and the UI will attach all of its UI functions to that + * Effect as well without creating any new QWidgets. + * + * @param e + * + * The Effect to add to this UI object. + */ + void AddAdditionalEffect(OldEffectNode* e); + + /** + * @brief Get the primary Effect that this UI object was created for + * + * @return + * + * The Effect object passed to the constructor when creating this EffectUI. + */ + OldEffectNode* GetEffect(); + + /** + * @brief Get the Y position of a given row + * + * Retrieve the on-screen Y position of the attached Effect's EffectRow at a given index. This is primarily used for + * displaying UI elements that align with the the row's on-screen widgets (e.g. keyframes in the EffectControls + * panel). + * + * The Y value provided is specifically the center point of the row's name label. It gets mapped to a provided + * QWidget object so it can be used locally by that QWidget without further modification. + * + * @param row + * + * The index of the EffectRow to retrieve the Y position of. + * + * @param mapToWidget + * + * The widget to map the Y value to. + * + * @return + * + * The row's Y position. + */ + int GetRowY(int row, QWidget *mapToWidget = nullptr); + + /** + * @brief Update widgets with the current Effect's values. + * + * When the Timeline playhead moves, the current values in the Effect might change if its fields are keyframed. + * In order to visually update these values on the UI, this function should be called. It will loop through all + * fields of all attached effects and update them to the value at the current Timeline playhead. + * + * Currently this function is called by update_ui() which is also responsible for updating other parts of the UI + * like the Timeline and Viewer so they all get updated together. + */ + void UpdateFromEffect(); + + /** + * @brief Check if a given Clip has an Effect referenced by this EffectUI + * + * Olive allows users to modify several effects (of the same type) with one UI representation. The behavior is if + * multiple clips are selected that have effects of the same type, all those Effects can be modified by the same + * EffectUI object. However this behavior is undesirable if a single Clip has more than one of the same type of Effect + * (e.g. two or more blurs). In this scenario, the user will most likely expect two separate UI objects for each of + * these effects individually, rather than consolidating them into one UI object. + * + * To address this, EffectControls will check this function to determine if this EffectUI already references + * an Effect of this type from this Clip. If it does, it's assumed a new EffectUI should be made rather than + * consolidating that Effect into the same EffectUI. + * + * @param c + * + * The Clip to determine whether an Effect of this type is already referenced by this EffectUI. + * + * @return + * + * True is an Effect from this Clip is already attached to this EffectUI. + */ + bool IsAttachedToClip(Clip* c); + +protected: + +signals: + /** + * @brief Cut signal + * + * Emitted when the user selects Cut from the right-click context menu. + */ + void CutRequested(); + + /** + * @brief Copy signal + * + * Emitted when the user selects Copy from the right-click context menu. + */ + void CopyRequested(); +private: + /** + * @brief Retrieve the QWidget corresponding a specific EffectField + * + * Convenience function equivalent to widgets_.at(row).at(field). + * + * @param row + * + * EffectRow index to retrieve field QWidget from + * + * @param field + * + * EffectField index to retrieve QWidget from + * + * @return + * + * The QWidget at this row and field index. + */ + QWidget* Widget(int row, int field); + + /** + * @brief Internal reference to the Effect this object was constructed around. + */ + OldEffectNode* effect_; + + /** + * @brief Internal array of additional Effect objects attached to this UI. + */ + QVector additional_effects_; + + /** + * @brief Layout for UI widgets + */ + QGridLayout* layout_; + + /** + * @brief Grid array of QWidgets corresponding to the Effect's rows and fields + */ + QVector< QVector > widgets_; + + /** + * @brief Array of QLabel objects corresponding to each row's name(). + */ + QVector labels_; + + /** + * @brief Array of KeyframeNavigator objects corresponding to each row. + */ + QVector keyframe_navigators_; + + /** + * @brief Attach a KeyframeNavigator object to an EffectRow. + * + * Internal function for connecting a KeyframeNavigator UI object to an EffectRow. + * + * @param row + * + * The EffectRow object. + * + * @param nav + * + * The KeyframeNavigator object. + */ + void AttachKeyframeNavigationToRow(NodeIO* row, KeyframeNavigator* nav); +private slots: + /** + * @brief Slot for titlebar's right-click signal to show a context menu for extra Effect functions. + */ + void show_context_menu(const QPoint&); +}; + +#endif // EFFECTUI_H diff --git a/ui/embeddedfilechooser.cpp b/ui/embeddedfilechooser.cpp index f19505703..582f213e0 100644 --- a/ui/embeddedfilechooser.cpp +++ b/ui/embeddedfilechooser.cpp @@ -1,75 +1,75 @@ -/*** - - 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 "embeddedfilechooser.h" - -#include -#include -#include -#include -#include - -EmbeddedFileChooser::EmbeddedFileChooser(QWidget* parent) : QWidget(parent) { - QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); - file_label = new QLabel(this); - update_label(); - layout->addWidget(file_label); - QPushButton* browse_button = new QPushButton("...", this); - browse_button->setFixedWidth(25); - layout->addWidget(browse_button); - connect(browse_button, SIGNAL(clicked(bool)), this, SLOT(browse())); -} - -const QString &EmbeddedFileChooser::getFilename() { - return filename; -} - -void EmbeddedFileChooser::setFilename(const QString &s) { - filename = s; - update_label(); - emit changed(filename); -} - -void EmbeddedFileChooser::update_label() { - QString l = "" + tr("File:") + " "; - if (filename.isEmpty()) { - l += "(none)"; - } else { - bool file_exists = QFileInfo::exists(filename); - if (!file_exists) l += ""; - QString short_fn = filename.mid(filename.lastIndexOf('/')+1); - if (short_fn.size() > 20) { - l += "..." + short_fn.right(20); - } else { - l += short_fn; - } - if (!file_exists) l += ""; - } - l += ""; - file_label->setText(l); -} - -void EmbeddedFileChooser::browse() { - QString fn = QFileDialog::getOpenFileName(this); - if (!fn.isEmpty()) { - setFilename(fn); - } -} +/*** + + 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 "embeddedfilechooser.h" + +#include +#include +#include +#include +#include + +EmbeddedFileChooser::EmbeddedFileChooser(QWidget* parent) : QWidget(parent) { + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + file_label = new QLabel(this); + update_label(); + layout->addWidget(file_label); + QPushButton* browse_button = new QPushButton("...", this); + browse_button->setFixedWidth(25); + layout->addWidget(browse_button); + connect(browse_button, SIGNAL(clicked(bool)), this, SLOT(browse())); +} + +const QString &EmbeddedFileChooser::getFilename() { + return filename; +} + +void EmbeddedFileChooser::setFilename(const QString &s) { + filename = s; + update_label(); + emit changed(filename); +} + +void EmbeddedFileChooser::update_label() { + QString l = "" + tr("File:") + " "; + if (filename.isEmpty()) { + l += "(none)"; + } else { + bool file_exists = QFileInfo::exists(filename); + if (!file_exists) l += ""; + QString short_fn = filename.mid(filename.lastIndexOf('/')+1); + if (short_fn.size() > 20) { + l += "..." + short_fn.right(20); + } else { + l += short_fn; + } + if (!file_exists) l += ""; + } + l += ""; + file_label->setText(l); +} + +void EmbeddedFileChooser::browse() { + QString fn = QFileDialog::getOpenFileName(this); + if (!fn.isEmpty()) { + setFilename(fn); + } +} diff --git a/ui/embeddedfilechooser.h b/ui/embeddedfilechooser.h index f67596325..28397cc12 100644 --- a/ui/embeddedfilechooser.h +++ b/ui/embeddedfilechooser.h @@ -1,45 +1,45 @@ -/*** - - 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 EMBEDDEDFILECHOOSER_H -#define EMBEDDEDFILECHOOSER_H - -#include - -class QLabel; - -class EmbeddedFileChooser : public QWidget { - Q_OBJECT -public: - EmbeddedFileChooser(QWidget* parent = 0); - - const QString& getFilename(); - void setFilename(const QString& s); -signals: - void changed(const QString& s); -private: - QLabel* file_label; - QString filename; - void update_label(); -private slots: - void browse(); -}; - -#endif // EMBEDDEDFILECHOOSER_H +/*** + + 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 EMBEDDEDFILECHOOSER_H +#define EMBEDDEDFILECHOOSER_H + +#include + +class QLabel; + +class EmbeddedFileChooser : public QWidget { + Q_OBJECT +public: + EmbeddedFileChooser(QWidget* parent = 0); + + const QString& getFilename(); + void setFilename(const QString& s); +signals: + void changed(const QString& s); +private: + QLabel* file_label; + QString filename; + void update_label(); +private slots: + void browse(); +}; + +#endif // EMBEDDEDFILECHOOSER_H diff --git a/ui/flowlayout.cpp b/ui/flowlayout.cpp index 243fd5099..0dd3a1f60 100644 --- a/ui/flowlayout.cpp +++ b/ui/flowlayout.cpp @@ -1,200 +1,200 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include - -#include "flowlayout.h" -FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) - : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) -{ - setContentsMargins(margin, margin, margin, margin); -} - -FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) - : m_hSpace(hSpacing), m_vSpace(vSpacing) -{ - setContentsMargins(margin, margin, margin, margin); -} - -FlowLayout::~FlowLayout() -{ - QLayoutItem *item; - while ((item = takeAt(0))) - delete item; -} - -void FlowLayout::addItem(QLayoutItem *item) -{ - itemList.append(item); -} - -int FlowLayout::horizontalSpacing() const -{ - if (m_hSpace >= 0) { - return m_hSpace; - } else { - return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); - } -} - -int FlowLayout::verticalSpacing() const -{ - if (m_vSpace >= 0) { - return m_vSpace; - } else { - return smartSpacing(QStyle::PM_LayoutVerticalSpacing); - } -} - -int FlowLayout::count() const -{ - return itemList.size(); -} - -QLayoutItem *FlowLayout::itemAt(int index) const -{ - return itemList.value(index); -} - -QLayoutItem *FlowLayout::takeAt(int index) -{ - if (index >= 0 && index < itemList.size()) - return itemList.takeAt(index); - else - return 0; -} - -Qt::Orientations FlowLayout::expandingDirections() const -{ - return 0; -} - -bool FlowLayout::hasHeightForWidth() const -{ - return true; -} - -int FlowLayout::heightForWidth(int width) const -{ - int height = doLayout(QRect(0, 0, width, 0), true); - return height; -} - -void FlowLayout::setGeometry(const QRect &rect) -{ - QLayout::setGeometry(rect); - doLayout(rect, false); -} - -QSize FlowLayout::sizeHint() const -{ - return minimumSize(); -} - -QSize FlowLayout::minimumSize() const -{ - QSize size; - QLayoutItem *item; - foreach (item, itemList) - size = size.expandedTo(item->minimumSize()); - - size += QSize(2*margin(), 2*margin()); - return size; -} - -int FlowLayout::doLayout(const QRect &rect, bool testOnly) const -{ - int left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); - int x = effectiveRect.x(); - int y = effectiveRect.y(); - int lineHeight = 0; - - QLayoutItem *item; - foreach (item, itemList) { - QWidget *wid = item->widget(); - int spaceX = horizontalSpacing(); - if (spaceX == -1) - spaceX = wid->style()->layoutSpacing( - QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); - int spaceY = verticalSpacing(); - if (spaceY == -1) - spaceY = wid->style()->layoutSpacing( - QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); - int nextX = x + item->sizeHint().width() + spaceX; - if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { - x = effectiveRect.x(); - y = y + lineHeight + spaceY; - nextX = x + item->sizeHint().width() + spaceX; - lineHeight = 0; - } - - if (!testOnly) - item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); - - x = nextX; - lineHeight = qMax(lineHeight, item->sizeHint().height()); - } - return y + lineHeight - rect.y() + bottom; -} -int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const -{ - QObject *parent = this->parent(); - if (!parent) { - return -1; - } else if (parent->isWidgetType()) { - QWidget *pw = static_cast(parent); - return pw->style()->pixelMetric(pm, 0, pw); - } else { - return static_cast(parent)->spacing(); - } -} +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include + +#include "flowlayout.h" +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} + +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} + +int FlowLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FlowLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem *FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem *FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return 0; +} + +Qt::Orientations FlowLayout::expandingDirections() const +{ + return 0; +} + +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + QLayoutItem *item; + foreach (item, itemList) + size = size.expandedTo(item->minimumSize()); + + size += QSize(2*margin(), 2*margin()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + QLayoutItem *item; + foreach (item, itemList) { + QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, 0, pw); + } else { + return static_cast(parent)->spacing(); + } +} diff --git a/ui/flowlayout.h b/ui/flowlayout.h index 5531318a1..72f5282aa 100644 --- a/ui/flowlayout.h +++ b/ui/flowlayout.h @@ -1,87 +1,87 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#ifndef FLOWLAYOUT_H -#define FLOWLAYOUT_H - -#include -#include -#include -class FlowLayout : public QLayout -{ -public: - explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); - explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); - ~FlowLayout(); - - void addItem(QLayoutItem *item) override; - int horizontalSpacing() const; - int verticalSpacing() const; - Qt::Orientations expandingDirections() const override; - bool hasHeightForWidth() const override; - int heightForWidth(int) const override; - int count() const override; - QLayoutItem *itemAt(int index) const override; - QSize minimumSize() const override; - void setGeometry(const QRect &rect) override; - QSize sizeHint() const override; - QLayoutItem *takeAt(int index) override; - -private: - int doLayout(const QRect &rect, bool testOnly) const; - int smartSpacing(QStyle::PixelMetric pm) const; - - QList itemList; - int m_hSpace; - int m_vSpace; -}; - -#endif // FLOWLAYOUT_H +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include +#include +#include +class FlowLayout : public QLayout +{ +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout(); + + void addItem(QLayoutItem *item) override; + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int) const override; + int count() const override; + QLayoutItem *itemAt(int index) const override; + QSize minimumSize() const override; + void setGeometry(const QRect &rect) override; + QSize sizeHint() const override; + QLayoutItem *takeAt(int index) override; + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +}; + +#endif // FLOWLAYOUT_H diff --git a/ui/focusfilter.cpp b/ui/focusfilter.cpp index be1736a48..e890178ce 100644 --- a/ui/focusfilter.cpp +++ b/ui/focusfilter.cpp @@ -1,279 +1,279 @@ -/*** - - 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 "focusfilter.h" - -#include "panels/panels.h" -#include "timeline/sequence.h" -#include "ui/timelineheader.h" - -FocusFilter olive::FocusFilter; - -FocusFilter::FocusFilter() {} - -void FocusFilter::go_to_in() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_in(); - } else { - panel_sequence_viewer->go_to_in(); - } -} - -void FocusFilter::go_to_out() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_out(); - } else { - panel_sequence_viewer->go_to_out(); - } -} - -void FocusFilter::go_to_start() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_start(); - } else { - panel_sequence_viewer->go_to_start(); - } -} - -void FocusFilter::prev_frame() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->previous_frame(); - } else { - panel_sequence_viewer->previous_frame(); - } -} - -void FocusFilter::play_in_to_out() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->play(true); - } else { - panel_sequence_viewer->play(true); - } -} - -void FocusFilter::next_frame() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->next_frame(); - } else { - panel_sequence_viewer->next_frame(); - } -} - -void FocusFilter::go_to_end() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_end(); - } else { - panel_sequence_viewer->go_to_end(); - } -} - -void FocusFilter::set_viewer_fullscreen() { - if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->viewer_widget()->set_fullscreen(); - } else { - panel_sequence_viewer->viewer_widget()->set_fullscreen(); - } -} - -void FocusFilter::set_marker() { - if (Timeline::GetTopSequence() != nullptr) { - QDockWidget* focused_panel = get_focused_panel(); - - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->set_marker(); - } else if (focused_panel == panel_sequence_viewer) { - panel_sequence_viewer->set_marker(); - } else { - panel_timeline.first()->set_marker(); - } - } -} - -void FocusFilter::playpause() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->toggle_play(); - } else { - panel_sequence_viewer->toggle_play(); - } -} - -void FocusFilter::pause() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->pause(); - } else { - panel_sequence_viewer->pause(); - } -} - -void FocusFilter::increase_speed() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->increase_speed(); - } else { - panel_sequence_viewer->increase_speed(); - } -} - -void FocusFilter::decrease_speed() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->decrease_speed(); - } else { - panel_sequence_viewer->decrease_speed(); - } -} - -void FocusFilter::set_in_point() { - if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->set_in_point(); - } else { - panel_sequence_viewer->set_in_point(); - } -} - -void FocusFilter::set_out_point() { - if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->set_out_point(); - } else { - panel_sequence_viewer->set_out_point(); - } -} - -void FocusFilter::clear_in() { - if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->clear_in(); - } else { - panel_sequence_viewer->clear_in(); - } -} - -void FocusFilter::clear_out() { - if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->clear_out(); - } else { - panel_sequence_viewer->clear_out(); - } -} - -void FocusFilter::clear_inout() { - if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->clear_inout_point(); - } else { - panel_sequence_viewer->clear_inout_point(); - } -} - -void FocusFilter::delete_function() { - 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->focused()) { - panel_effect_controls->DeleteSelectedEffects(); - } 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->focused()) { - panel_graph_editor->delete_selected_keys(); - } else { - 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.first()->focused()) { - panel_project.first()->duplicate_selected(); - } -} - -void FocusFilter::select_all() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_graph_editor) { - panel_graph_editor->select_all(); - } else { - panel_timeline.first()->select_all(); - } -} - -void FocusFilter::zoom_in() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_effect_controls) { - panel_effect_controls->set_zoom(true); - } else if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->set_zoom(true); - } else if (focused_panel == panel_sequence_viewer) { - panel_sequence_viewer->set_zoom(true); - } else { - panel_timeline.first()->zoom_in(); - } -} - -void FocusFilter::zoom_out() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_effect_controls) { - panel_effect_controls->set_zoom(false); - } else if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->set_zoom(false); - } else if (focused_panel == panel_sequence_viewer) { - panel_sequence_viewer->set_zoom(false); - } else { - panel_timeline.first()->zoom_out(); - } -} - -void FocusFilter::cut() { - if (Timeline::GetTopSequence() != nullptr) { - QDockWidget* focused_panel = get_focused_panel(); - if (panel_effect_controls == focused_panel) { - panel_effect_controls->copy(true); - } else { - panel_timeline.first()->copy(true); - } - } -} - -void FocusFilter::copy() { - if (Timeline::GetTopSequence() != nullptr) { - QDockWidget* focused_panel = get_focused_panel(); - if (panel_effect_controls == focused_panel) { - panel_effect_controls->copy(false); - } else { - panel_timeline.first()->copy(false); - } - } -} +/*** + + 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 "focusfilter.h" + +#include "panels/panels.h" +#include "timeline/sequence.h" +#include "ui/timelineheader.h" + +FocusFilter olive::FocusFilter; + +FocusFilter::FocusFilter() {} + +void FocusFilter::go_to_in() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_in(); + } else { + panel_sequence_viewer->go_to_in(); + } +} + +void FocusFilter::go_to_out() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_out(); + } else { + panel_sequence_viewer->go_to_out(); + } +} + +void FocusFilter::go_to_start() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_start(); + } else { + panel_sequence_viewer->go_to_start(); + } +} + +void FocusFilter::prev_frame() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->previous_frame(); + } else { + panel_sequence_viewer->previous_frame(); + } +} + +void FocusFilter::play_in_to_out() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->play(true); + } else { + panel_sequence_viewer->play(true); + } +} + +void FocusFilter::next_frame() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->next_frame(); + } else { + panel_sequence_viewer->next_frame(); + } +} + +void FocusFilter::go_to_end() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_end(); + } else { + panel_sequence_viewer->go_to_end(); + } +} + +void FocusFilter::set_viewer_fullscreen() { + if (get_focused_panel() == panel_footage_viewer) { + panel_footage_viewer->viewer_widget()->set_fullscreen(); + } else { + panel_sequence_viewer->viewer_widget()->set_fullscreen(); + } +} + +void FocusFilter::set_marker() { + if (Timeline::GetTopSequence() != nullptr) { + QDockWidget* focused_panel = get_focused_panel(); + + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->set_marker(); + } else if (focused_panel == panel_sequence_viewer) { + panel_sequence_viewer->set_marker(); + } else { + panel_timeline.first()->set_marker(); + } + } +} + +void FocusFilter::playpause() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->toggle_play(); + } else { + panel_sequence_viewer->toggle_play(); + } +} + +void FocusFilter::pause() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->pause(); + } else { + panel_sequence_viewer->pause(); + } +} + +void FocusFilter::increase_speed() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->increase_speed(); + } else { + panel_sequence_viewer->increase_speed(); + } +} + +void FocusFilter::decrease_speed() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->decrease_speed(); + } else { + panel_sequence_viewer->decrease_speed(); + } +} + +void FocusFilter::set_in_point() { + if (get_focused_panel() == panel_footage_viewer) { + panel_footage_viewer->set_in_point(); + } else { + panel_sequence_viewer->set_in_point(); + } +} + +void FocusFilter::set_out_point() { + if (get_focused_panel() == panel_footage_viewer) { + panel_footage_viewer->set_out_point(); + } else { + panel_sequence_viewer->set_out_point(); + } +} + +void FocusFilter::clear_in() { + if (get_focused_panel() == panel_footage_viewer) { + panel_footage_viewer->clear_in(); + } else { + panel_sequence_viewer->clear_in(); + } +} + +void FocusFilter::clear_out() { + if (get_focused_panel() == panel_footage_viewer) { + panel_footage_viewer->clear_out(); + } else { + panel_sequence_viewer->clear_out(); + } +} + +void FocusFilter::clear_inout() { + if (get_focused_panel() == panel_footage_viewer) { + panel_footage_viewer->clear_inout_point(); + } else { + panel_sequence_viewer->clear_inout_point(); + } +} + +void FocusFilter::delete_function() { + 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->focused()) { + panel_effect_controls->DeleteSelectedEffects(); + } 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->focused()) { + panel_graph_editor->delete_selected_keys(); + } else { + 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.first()->focused()) { + panel_project.first()->duplicate_selected(); + } +} + +void FocusFilter::select_all() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_graph_editor) { + panel_graph_editor->select_all(); + } else { + panel_timeline.first()->select_all(); + } +} + +void FocusFilter::zoom_in() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_effect_controls) { + panel_effect_controls->set_zoom(true); + } else if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->set_zoom(true); + } else if (focused_panel == panel_sequence_viewer) { + panel_sequence_viewer->set_zoom(true); + } else { + panel_timeline.first()->zoom_in(); + } +} + +void FocusFilter::zoom_out() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_effect_controls) { + panel_effect_controls->set_zoom(false); + } else if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->set_zoom(false); + } else if (focused_panel == panel_sequence_viewer) { + panel_sequence_viewer->set_zoom(false); + } else { + panel_timeline.first()->zoom_out(); + } +} + +void FocusFilter::cut() { + if (Timeline::GetTopSequence() != nullptr) { + QDockWidget* focused_panel = get_focused_panel(); + if (panel_effect_controls == focused_panel) { + panel_effect_controls->copy(true); + } else { + panel_timeline.first()->copy(true); + } + } +} + +void FocusFilter::copy() { + if (Timeline::GetTopSequence() != nullptr) { + QDockWidget* focused_panel = get_focused_panel(); + if (panel_effect_controls == focused_panel) { + panel_effect_controls->copy(false); + } else { + panel_timeline.first()->copy(false); + } + } +} diff --git a/ui/focusfilter.h b/ui/focusfilter.h index 1fae39ede..841749f4e 100644 --- a/ui/focusfilter.h +++ b/ui/focusfilter.h @@ -1,231 +1,231 @@ -/*** - - 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 FOCUSFILTER_H -#define FOCUSFILTER_H - -#include - -/** - * @brief The FocusFilter class - * - * Some keyboard shortcuts/menu actions will do different things depending on the panel that's currently focused. - * For example, pressing "Set Marker" will set a marker on the main active sequence if the timeline is focused, - * or on the media in the Media Viewer if the Media Viewer is focused. This class provides slots/functions that - * can be called that will check which panel is focused and call the appropriate function. - * - * Responds to `config.hover_focus`. Default behavior is focus by clicking on the panels, but if `hover_focus` is - * **TRUE**, the focused panel will be whichever panel has the cursor currently hovering over it. - */ -class FocusFilter : public QObject { - Q_OBJECT -public: - /** - * @brief FocusFilter Constructor - * - * Currently empty. - */ - FocusFilter(); - -public slots: - /** - * @brief Cuts selected clips or selected effects (but not both). - * - * If the Effect Controls panel is focused, cuts selected effects. Otherwise cuts selected clips. - */ - void cut(); - - /** - * @brief Copies selected clips or selected effects (but not both). - * - * If the Effect Controls panel is focused, copies selected effects. Otherwise copies selected clips. - */ - void copy(); - - /** - * @brief Duplicates currently selected items - * - * Currently this only duplicates Sequences in the project panel. - */ - void duplicate(); - - /** - * @brief Go to In Point. - * - * Calls go_to_in() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void go_to_in(); - - /** - * @brief Go to Out Point. - * - * Calls go_to_out() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void go_to_out(); - - /** - * @brief Go to Start - * - * Calls go_to_start() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void go_to_start(); - - /** - * @brief Go to Previous Frame - * - * Calls previous_frame() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void prev_frame(); - - /** - * @brief Play In Point to Out Point - * - * Calls play(true) on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void play_in_to_out(); - - /** - * @brief Toggle Play/Pause - * - * Calls toggle_play() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void playpause(); - - /** - * @brief Pause/Shuttle Stop. - * - * Calls pause() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void pause(); - - /** - * @brief Increase Speed/Shuttle Right - * - * Calls increase_speed() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void increase_speed(); - - /** - * @brief Decrease Speed/Shuttle Left - * - * Calls decrease_speed() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void decrease_speed(); - - /** - * @brief Go to Next Frame - * - * Calls next_frame() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void next_frame(); - - /** - * @brief Go to End - * - * Calls go_to_end() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void go_to_end(); - - /** - * @brief Set currently focused viewer to full screen - * - * Calls viewer_widget->set_fullscreen() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void set_viewer_fullscreen(); - - /** - * @brief Set a marker at the current playhead - * - * Calls set_marker() on Media Viewer or Sequence Viewer if it's focused. Otherwise calls it on Timeline. - */ - void set_marker(); - - /** - * @brief Set in point - * - * Calls set_in_point() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void set_in_point(); - - /** - * @brief Set out point - * - * Calls set_out_point() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void set_out_point(); - - /** - * @brief Clear in point - * - * Calls clear_in() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void clear_in(); - - /** - * @brief Clear out point - * - * Calls clear_out() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void clear_out(); - - /** - * @brief Clear in/out point - * - * Calls clear_inout() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. - */ - void clear_inout(); - - /** - * @brief Delete - * - * Calls various delete functions based on which UI elements are focused. Deletes span anywhere from deleting - * clips (Timeline), to effects (Effect Controls), to markers (TimelineHeader). - */ - void delete_function(); - - /** - * @brief Select All - * - * Calls select_all() on Graph Editor if its focused or Timeline if it's not. - */ - void select_all(); - - /** - * @brief Zoom In - * - * Calls zoom_in() on Effect Controls, Footage Viewer, or Sequence Viewer if one of them is focused. Otherwise - * calls it on Timeline. - */ - void zoom_in(); - - /** - * @brief Zoom Out - * - * Calls zoom_out() on Effect Controls, Footage Viewer, or Sequence Viewer if one of them is focused. Otherwise - * calls it on Timeline. - */ - void zoom_out(); -}; - -namespace olive { -extern FocusFilter FocusFilter; -} - -#endif // FOCUSFILTER_H +/*** + + 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 FOCUSFILTER_H +#define FOCUSFILTER_H + +#include + +/** + * @brief The FocusFilter class + * + * Some keyboard shortcuts/menu actions will do different things depending on the panel that's currently focused. + * For example, pressing "Set Marker" will set a marker on the main active sequence if the timeline is focused, + * or on the media in the Media Viewer if the Media Viewer is focused. This class provides slots/functions that + * can be called that will check which panel is focused and call the appropriate function. + * + * Responds to `config.hover_focus`. Default behavior is focus by clicking on the panels, but if `hover_focus` is + * **TRUE**, the focused panel will be whichever panel has the cursor currently hovering over it. + */ +class FocusFilter : public QObject { + Q_OBJECT +public: + /** + * @brief FocusFilter Constructor + * + * Currently empty. + */ + FocusFilter(); + +public slots: + /** + * @brief Cuts selected clips or selected effects (but not both). + * + * If the Effect Controls panel is focused, cuts selected effects. Otherwise cuts selected clips. + */ + void cut(); + + /** + * @brief Copies selected clips or selected effects (but not both). + * + * If the Effect Controls panel is focused, copies selected effects. Otherwise copies selected clips. + */ + void copy(); + + /** + * @brief Duplicates currently selected items + * + * Currently this only duplicates Sequences in the project panel. + */ + void duplicate(); + + /** + * @brief Go to In Point. + * + * Calls go_to_in() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void go_to_in(); + + /** + * @brief Go to Out Point. + * + * Calls go_to_out() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void go_to_out(); + + /** + * @brief Go to Start + * + * Calls go_to_start() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void go_to_start(); + + /** + * @brief Go to Previous Frame + * + * Calls previous_frame() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void prev_frame(); + + /** + * @brief Play In Point to Out Point + * + * Calls play(true) on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void play_in_to_out(); + + /** + * @brief Toggle Play/Pause + * + * Calls toggle_play() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void playpause(); + + /** + * @brief Pause/Shuttle Stop. + * + * Calls pause() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void pause(); + + /** + * @brief Increase Speed/Shuttle Right + * + * Calls increase_speed() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void increase_speed(); + + /** + * @brief Decrease Speed/Shuttle Left + * + * Calls decrease_speed() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void decrease_speed(); + + /** + * @brief Go to Next Frame + * + * Calls next_frame() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void next_frame(); + + /** + * @brief Go to End + * + * Calls go_to_end() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void go_to_end(); + + /** + * @brief Set currently focused viewer to full screen + * + * Calls viewer_widget->set_fullscreen() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void set_viewer_fullscreen(); + + /** + * @brief Set a marker at the current playhead + * + * Calls set_marker() on Media Viewer or Sequence Viewer if it's focused. Otherwise calls it on Timeline. + */ + void set_marker(); + + /** + * @brief Set in point + * + * Calls set_in_point() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void set_in_point(); + + /** + * @brief Set out point + * + * Calls set_out_point() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void set_out_point(); + + /** + * @brief Clear in point + * + * Calls clear_in() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void clear_in(); + + /** + * @brief Clear out point + * + * Calls clear_out() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void clear_out(); + + /** + * @brief Clear in/out point + * + * Calls clear_inout() on Media Viewer it's focused. Otherwise calls it on Sequence Viewer. + */ + void clear_inout(); + + /** + * @brief Delete + * + * Calls various delete functions based on which UI elements are focused. Deletes span anywhere from deleting + * clips (Timeline), to effects (Effect Controls), to markers (TimelineHeader). + */ + void delete_function(); + + /** + * @brief Select All + * + * Calls select_all() on Graph Editor if its focused or Timeline if it's not. + */ + void select_all(); + + /** + * @brief Zoom In + * + * Calls zoom_in() on Effect Controls, Footage Viewer, or Sequence Viewer if one of them is focused. Otherwise + * calls it on Timeline. + */ + void zoom_in(); + + /** + * @brief Zoom Out + * + * Calls zoom_out() on Effect Controls, Footage Viewer, or Sequence Viewer if one of them is focused. Otherwise + * calls it on Timeline. + */ + void zoom_out(); +}; + +namespace olive { +extern FocusFilter FocusFilter; +} + +#endif // FOCUSFILTER_H diff --git a/ui/graphview.cpp b/ui/graphview.cpp index 239f1cced..dfc3044f9 100644 --- a/ui/graphview.cpp +++ b/ui/graphview.cpp @@ -1,950 +1,950 @@ -/*** - - 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 "graphview.h" - -#include -#include -#include -#include - -#include "global/config.h" -#include "panels/panels.h" -#include "panels/timeline.h" -#include "panels/viewer.h" -#include "timeline/sequence.h" -#include "ui/keyframedrawing.h" -#include "undo/undo.h" -#include "undo/undostack.h" -#include "nodes/oldeffectnode.h" -#include "timeline/clip.h" -#include "ui/rectangleselect.h" -#include "ui/menu.h" -#include "global/debug.h" - -const double kGraphZoomSpeed = 0.05; -const int kGraphSize = 100; -const int kBezierHandleSize = 3; -const int kBezierLineSize = 2; - -const int kBezierHandleNone = 1; -const int kBezierHandlePre = 2; -const int kBezierHandlePost = 3; - -QColor get_curve_color(int index, int length) { - QColor c; - int hue = qRound((double(index)/double(length))*255); - c.setHsv(hue, 255, 255); - return c; -} - -GraphView::GraphView(QWidget* parent) : QWidget(parent) { - x_scroll = 0; - y_scroll = 0; - mousedown = false; - x_zoom = 1.0; - y_zoom = 1.0; - row = nullptr; - moved_keys = false; - current_handle = kBezierHandleNone; - rect_select = false; - visible_in = 0; - click_add_proc = false; - - setMouseTracking(true); - setFocusPolicy(Qt::ClickFocus); - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); -} - -void GraphView::show_context_menu(const QPoint& pos) { - Menu menu(this); - - QAction* zoom_to_selection = menu.addAction(tr("Zoom to Selection")); - if (selected_keys.size() == 0 || row == nullptr) { - zoom_to_selection->setEnabled(false); - } else { - connect(zoom_to_selection, SIGNAL(triggered(bool)), this, SLOT(set_view_to_selection())); - } - - QAction* zoom_to_all = menu.addAction(tr("Zoom to Show All")); - if (row == nullptr) { - zoom_to_all->setEnabled(false); - } else { - connect(zoom_to_all, SIGNAL(triggered(bool)), this, SLOT(set_view_to_all())); - } - - menu.addSeparator(); - - QAction* reset_action = menu.addAction(tr("Reset View")); - if (row == nullptr) { - reset_action->setEnabled(false); - } else { - connect(reset_action, SIGNAL(triggered(bool)), this, SLOT(reset_view())); - } - - menu.exec(mapToGlobal(pos)); -} - -void GraphView::reset_view() { - x_zoom = 1.0; - y_zoom = 1.0; - set_scroll_x(0); - set_scroll_y(0); - emit zoom_changed(x_zoom, y_zoom); - update(); -} - -void GraphView::set_view_to_selection() { - if (row != nullptr && selected_keys.size() > 0) { - double min_time = DBL_MAX; - double max_time = DBL_MIN; - double min_dbl = DBL_MAX; - double max_dbl = DBL_MIN; - for (int i=0;iField(selected_keys_fields.at(i))->keyframes.at(selected_keys.at(i)); - min_time = qMin(key.time, min_time); - max_time = qMax(key.time, max_time); - min_dbl = qMin(key.data.toDouble(), min_dbl); - max_dbl = qMax(key.data.toDouble(), max_dbl); - } - - /* FIXME - min_time -= row->GetParentEffect()->parent_clip->clip_in(); - max_time -= row->GetParentEffect()->parent_clip->clip_in(); - */ - - set_view_to_rect(min_time, min_dbl, max_time, max_dbl); - } -} - -void GraphView::set_view_to_all() { - if (row != nullptr) { - bool can_set = false; - - double min_time = DBL_MAX; - double max_time = DBL_MIN; - double min_dbl = DBL_MAX; - double max_dbl = DBL_MIN; - for (int i=0;iFieldCount();i++) { - for (int j=0;jField(i)->keyframes.size();j++) { - const EffectKeyframe& key = row->Field(i)->keyframes.at(j); - min_time = qMin(key.time, min_time); - max_time = qMax(key.time, max_time); - min_dbl = qMin(key.data.toDouble(), min_dbl); - max_dbl = qMax(key.data.toDouble(), max_dbl); - can_set = true; - } - } - if (can_set) { - /* FIXME - min_time -= row->GetParentEffect()->parent_clip->clip_in(); - max_time -= row->GetParentEffect()->parent_clip->clip_in(); - */ - - set_view_to_rect(min_time, min_dbl, max_time, max_dbl); - } - } -} - -void GraphView::set_view_to_rect(double x1, double y1, double x2, double y2) { - double padding = 1.5; - double x_diff = double(x2 - x1); - double y_diff = (y2 - y1); - double x_diff_padded = (x_diff+10)*padding; - double y_diff_padded = (y_diff+10)*padding; - set_zoom(double(width()) / x_diff_padded, double(height()) / y_diff_padded); - - set_scroll_x(qRound((double(x1) - ((x_diff_padded-x_diff)/2))*x_zoom)); - set_scroll_y(qRound((double(y1) - ((y_diff_padded-y_diff)/2))*y_zoom)); -} - -void GraphView::draw_line_text(QPainter &p, bool vert, int line_no, int line_pos, int next_line_pos) { - // draws last line's text - QString str = QString::number(line_no*kGraphSize); - int text_sz = vert ? fontMetrics().height() : fontMetrics().width(str); - if (text_sz < (next_line_pos - line_pos)) { - QRect text_rect = vert ? QRect(0, line_pos-50, 50, 50) : QRect(line_pos, height()-50, 50, 50); - p.drawText(text_rect, Qt::AlignBottom | Qt::AlignLeft, str); - } -} - -void GraphView::draw_lines(QPainter& p, bool vert) { - int last_line = INT_MIN; - int last_line_x = INT_MIN; - int lim = vert ? height() : width(); - int scroll = vert ? y_scroll : x_scroll; - for (int i=0;i sort_keys_from_field(EffectField* field) { - QVector sorted_keys; - for (int k=0;kkeyframes.size();k++) { - bool inserted = false; - for (int j=0;jkeyframes.at(sorted_keys.at(j)).time > field->keyframes.at(k).time) { - sorted_keys.insert(j, k); - inserted = true; - break; - } - } - if (!inserted) { - sorted_keys.append(k); - } - } - return sorted_keys; -} - -void GraphView::paintEvent(QPaintEvent *) { - QPainter p(this); - - if (panel_sequence_viewer->seq != nullptr) { - // draw grid lines - - p.setPen(Qt::gray); - - draw_lines(p, true); - draw_lines(p, false); - - // draw keyframes - if (row != nullptr) { - QPen line_pen; - line_pen.setWidth(kBezierLineSize); - - for (int i=row->FieldCount()-1;i>=0;i--) { - EffectField* field = row->Field(i); - - if (field->type() == EffectField::EFFECT_FIELD_DOUBLE && field_visibility.at(i)) { - // sort keyframes by time - QVector sorted_keys = sort_keys_from_field(field); - - int last_key_x = 0; - int last_key_y = 0; - - // draw lines - for (int j=0;jkeyframes.at(sorted_keys.at(j)); - - int key_x = get_screen_x(key.time); - int key_y = get_screen_y(key.data.toDouble()); - - line_pen.setColor(get_curve_color(i, row->FieldCount())); - p.setPen(line_pen); - if (j == 0) { - p.drawLine(0, key_y, key_x, key_y); - } else { - const EffectKeyframe& last_key = field->keyframes.at(sorted_keys.at(j-1)); - - double pre_handle = field->GetValidKeyframeHandlePosition(sorted_keys.at(j), false); - double last_post_handle = field->GetValidKeyframeHandlePosition(sorted_keys.at(j-1), true); - - if (last_key.type == EFFECT_KEYFRAME_HOLD) { - // hold - p.drawLine(last_key_x, last_key_y, key_x, last_key_y); - p.drawLine(key_x, last_key_y, key_x, key_y); - } else if (last_key.type == EFFECT_KEYFRAME_BEZIER || key.type == EFFECT_KEYFRAME_BEZIER) { - QPainterPath bezier_path; - bezier_path.moveTo(last_key_x, last_key_y); - if (last_key.type == EFFECT_KEYFRAME_BEZIER && key.type == EFFECT_KEYFRAME_BEZIER) { - // cubic bezier - bezier_path.cubicTo( - QPointF(last_key_x+last_post_handle*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), - QPointF(key_x+pre_handle*x_zoom, key_y-key.pre_handle.y()*y_zoom), - QPointF(key_x, key_y) - ); - } else if (key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier - // last keyframe is the bezier one - bezier_path.quadTo( - QPointF(last_key_x+last_post_handle*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), - QPointF(key_x, key_y) - ); - } else { - // this keyframe is the bezier one - bezier_path.quadTo( - QPointF(key_x+pre_handle*x_zoom, key_y-key.pre_handle.y()*y_zoom), - QPointF(key_x, key_y) - ); - } - p.drawPath(bezier_path); - } else { - // linear - p.drawLine(last_key_x, last_key_y, key_x, key_y); - } - } - last_key_x = key_x; - last_key_y = key_y; - } - if (last_key_x < width()) p.drawLine(last_key_x, last_key_y, width(), last_key_y); - - // draw keys - for (int j=0;jkeyframes.at(sorted_keys.at(j)); - - int key_x = get_screen_x(key.time); - int key_y = get_screen_y(key.data.toDouble()); - - if (key.type == EFFECT_KEYFRAME_BEZIER) { - p.setPen(Qt::gray); - - // pre handle line - QPointF pre_point(key_x + key.pre_handle.x()*x_zoom, key_y - key.pre_handle.y()*y_zoom); - p.drawLine(pre_point, QPointF(key_x, key_y)); - p.drawEllipse(pre_point, kBezierHandleSize, kBezierHandleSize); - - // post handle line - QPointF post_point(key_x + key.post_handle.x()*x_zoom, key_y - key.post_handle.y()*y_zoom); - p.drawLine(post_point, QPointF(key_x, key_y)); - p.drawEllipse(post_point, kBezierHandleSize, kBezierHandleSize); - } - - bool selected = false; - for (int k=0;kseq->playhead - visible_in)*x_zoom) - x_scroll); - p.drawLine(playhead_x, 0, playhead_x, height()); - - if (rect_select) { - olive::ui::DrawSelectionRectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); - p.setBrush(Qt::NoBrush); - } - } - - p.setPen(Qt::white); - - QRect border = rect(); - border.setWidth(border.width()-1); - border.setHeight(border.height()-1); - p.drawRect(border); -} - -void GraphView::mousePressEvent(QMouseEvent *event) { - if (row != nullptr) { - mousedown = true; - start_x = event->pos().x(); - start_y = event->pos().y(); - - // selecting - int sel_key = -1; - int sel_key_field = -1; - current_handle = kBezierHandleNone; - - if (click_add && (event->buttons() & Qt::LeftButton)) { - selected_keys.clear(); - selected_keys_fields.clear(); - - EffectKeyframe key; - key.time = get_value_x(event->pos().x()); - key.data = get_value_y(event->pos().y()); - key.type = click_add_type; - click_add_key = click_add_field->keyframes.size(); - click_add_field->keyframes.append(key); - update_ui(false); - click_add_proc = true; - } else { - for (int i=0;iFieldCount();i++) { - EffectField* field = row->Field(i); - if (field->type() == EffectField::EFFECT_FIELD_DOUBLE && field_visibility.at(i)) { - for (int j=0;jkeyframes.size();j++) { - const EffectKeyframe& key = field->keyframes.at(j); - int key_x = get_screen_x(key.time); - int key_y = get_screen_y(key.data.toDouble()); - if (event->pos().x() > key_x-KEYFRAME_SIZE - && event->pos().x() < key_x+KEYFRAME_SIZE - && event->pos().y() > key_y-KEYFRAME_SIZE - && event->pos().y() < key_y+KEYFRAME_SIZE) { - sel_key = j; - sel_key_field = i; - break; - } else { - // selecting a handle - QPointF pre_point(key_x + key.pre_handle.x()*x_zoom, key_y - key.pre_handle.y()*y_zoom); - QPointF post_point(key_x + key.post_handle.x()*x_zoom, key_y - key.post_handle.y()*y_zoom); - if (event->pos().x() > pre_point.x()-kBezierHandleSize - && event->pos().x() < pre_point.x()+kBezierHandleSize - && event->pos().y() > pre_point.y()-kBezierHandleSize - && event->pos().y() < pre_point.y()+kBezierHandleSize) { - current_handle = kBezierHandlePre; - } else if (event->pos().x() > post_point.x()-kBezierHandleSize - && event->pos().x() < post_point.x()+kBezierHandleSize - && event->pos().y() > post_point.y()-kBezierHandleSize - && event->pos().y() < post_point.y()+kBezierHandleSize) { - current_handle = kBezierHandlePost; - } - - if (current_handle != kBezierHandleNone) { - sel_key = j; - sel_key_field = i; - handle_index = j; - handle_field = i; - old_pre_handle_x = key.pre_handle.x(); - old_pre_handle_y = key.pre_handle.y(); - old_post_handle_x = key.post_handle.x(); - old_post_handle_y = key.post_handle.y(); - break; - } - } - } - } - if (sel_key > -1) break; - } - - bool already_selected = false; - if (sel_key > -1) { - for (int i=0;imodifiers() & Qt::ShiftModifier) && current_handle == kBezierHandleNone) { - selected_keys.removeAt(i); - selected_keys_fields.removeAt(i); - } - already_selected = true; - break; - } - } - } - if (!already_selected) { - if (!(event->modifiers() & Qt::ShiftModifier)) { - selected_keys.clear(); - selected_keys_fields.clear(); - } - if (sel_key > -1) { - selected_keys.append(sel_key); - selected_keys_fields.append(sel_key_field); - } else { - rect_select = true; - rect_select_x = event->pos().x(); - rect_select_y = event->pos().y(); - rect_select_w = 0; - rect_select_h = 0; - rect_select_offset = selected_keys.size(); - } - } - - selection_update(); - } - } -} - -void GraphView::mouseMoveEvent(QMouseEvent *event) { - if (!mousedown || !click_add) unsetCursor(); - if (mousedown) { - 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(); - start_y = event->pos().y(); - update(); - } else if (click_add_proc) { - click_add_field->keyframes[click_add_key].time = get_value_x(event->pos().x()); - click_add_field->keyframes[click_add_key].data = get_value_y(event->pos().y()); - update_ui(false); - } else if (rect_select) { - rect_select_w = event->pos().x() - rect_select_x; - rect_select_h = event->pos().y() - rect_select_y; - - selected_keys.resize(rect_select_offset); - selected_keys_fields.resize(rect_select_offset); - - for (int i=0;iFieldCount();i++) { - EffectField* f = row->Field(i); - for (int j=0;jkeyframes.size();j++) { - bool already_selected = false; - for (int k=0;kkeyframes.at(j).time), get_screen_y(f->keyframes.at(j).data.toDouble())); - QRect select_rect(rect_select_x, rect_select_y, rect_select_w, rect_select_h); - if (select_rect.contains(key_screen_point)) { - selected_keys.append(j); - selected_keys_fields.append(i); - } - } - } - } - update(); - } else { - switch (current_handle) { - case kBezierHandleNone: - for (int i=0;iField(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].time = qRound(selected_keys_old_vals.at(i) + (double(event->pos().x() - start_x)/x_zoom)); - if (event->modifiers() & Qt::ShiftModifier) { - row->Field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].data = selected_keys_old_doubles.at(i); - } else { - row->Field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].data = qRound(selected_keys_old_doubles.at(i) + (double(start_y - event->pos().y())/y_zoom)); - } - } - moved_keys = true; - update_ui(false); - break; - case kBezierHandlePre: - case kBezierHandlePost: - { - double new_pre_handle_x = old_pre_handle_x; - double new_pre_handle_y = old_pre_handle_y; - double new_post_handle_x = old_post_handle_x; - double new_post_handle_y = old_post_handle_y; - - double x_diff = double(event->pos().x() - start_x)/x_zoom; - double y_diff = double(start_y - event->pos().y())/y_zoom; - - if (current_handle == kBezierHandlePre) { - new_pre_handle_x += x_diff; - if (!(event->modifiers() & Qt::ShiftModifier)) new_pre_handle_y += y_diff; - if (!(event->modifiers() & Qt::ControlModifier)) { - new_post_handle_x = -new_pre_handle_x; - new_post_handle_y = -new_pre_handle_y; - } - } else { - new_post_handle_x += x_diff; - if (!(event->modifiers() & Qt::ShiftModifier)) new_post_handle_y += y_diff; - if (!(event->modifiers() & Qt::ControlModifier)) { - new_pre_handle_x = -new_post_handle_x; - new_pre_handle_y = -new_post_handle_y; - } - } - - EffectKeyframe& key = row->Field(handle_field)->keyframes[handle_index]; - key.pre_handle = QPointF(qMin(0.0, new_pre_handle_x), new_pre_handle_y); - key.post_handle = QPointF(qMax(0.0, new_post_handle_x), new_post_handle_y); - - moved_keys = true; - update_ui(false); - } - break; - } - } - } else if (row != nullptr) { - // clicking on the curve - click_add = false; - - bool hovering_key = false; - - for (int i=0;iFieldCount();i++) { - for (int j=0;jField(i)->keyframes.size();j++) { - const EffectKeyframe& key = row->Field(i)->keyframes.at(j); - int key_x = get_screen_x(key.time); - int key_y = get_screen_y(key.data.toDouble()); - QRect test_rect( - key_x - KEYFRAME_SIZE, - key_y - KEYFRAME_SIZE, - KEYFRAME_SIZE+KEYFRAME_SIZE, - KEYFRAME_SIZE+KEYFRAME_SIZE - ); - QRect pre_rect( - qRound(key_x + key.pre_handle.x()*x_zoom - kBezierHandleSize), - qRound(key_y + key.pre_handle.y()*y_zoom - kBezierHandleSize), - kBezierHandleSize+kBezierHandleSize, - kBezierHandleSize+kBezierHandleSize - ); - QRect post_rect( - qRound(key_x + key.post_handle.x()*x_zoom - kBezierHandleSize), - qRound(key_y + key.post_handle.y()*y_zoom - kBezierHandleSize), - kBezierHandleSize+kBezierHandleSize, - kBezierHandleSize+kBezierHandleSize - ); - - if (test_rect.contains(event->pos()) - || pre_rect.contains(event->pos()) - || post_rect.contains(event->pos())) { - hovering_key = true; - break; - } - } - } - - if (!hovering_key) { - for (int i=0;iFieldCount();i++) { - EffectField* f = row->Field(i); - if (field_visibility.at(i)) { - QVector sorted_keys = sort_keys_from_field(f); - - if (!sorted_keys.isEmpty()) { - if (event->pos().x() <= get_screen_x(f->keyframes.at(sorted_keys.first()).time)) { - int y_comp = get_screen_y(f->keyframes.at(sorted_keys.first()).data.toDouble()); - if (event->pos().y() >= y_comp-kBezierLineSize - && event->pos().y() <= y_comp+kBezierLineSize) { - // dout << "make an EARLY key on field" << i; - click_add = true; - click_add_type = f->keyframes.at(sorted_keys.first()).type; - } - } else if (event->pos().x() >= get_screen_x(f->keyframes.at(sorted_keys.last()).time)) { - int y_comp = get_screen_y(f->keyframes.at(sorted_keys.last()).data.toDouble()); - if (event->pos().y() >= y_comp-kBezierLineSize - && event->pos().y() <= y_comp+kBezierLineSize) { - // dout << "make an LATE key on field" << i; - click_add = true; - click_add_type = f->keyframes.at(sorted_keys.last()).type; - } - } else { - for (int j=1;jkeyframes.at(sorted_keys.at(j-1)); - const EffectKeyframe& key = f->keyframes.at(sorted_keys.at(j)); - - int last_key_x = get_screen_x(last_key.time); - int key_x = get_screen_x(key.time); - int last_key_y = get_screen_y(last_key.data.toDouble()); - int key_y = get_screen_y(key.data.toDouble()); - - click_add_type = last_key.type; - - if (event->pos().x() >= last_key_x - && event->pos().x() <= key_x) { - QRect mouse_rect(event->pos().x()-kBezierLineSize, event->pos().y()-kBezierLineSize, kBezierLineSize+kBezierLineSize, kBezierLineSize+kBezierLineSize); - // NOTE: FILTHY copy/paste from paintEvent - if (last_key.type == EFFECT_KEYFRAME_HOLD) { - // hold - if (event->pos().y() >= last_key_y-kBezierLineSize - && event->pos().y() <= last_key_y+kBezierLineSize) { - // dout << "make an HOLD key on field" << i << "after key" << j; - click_add = true; - } - } else if (last_key.type == EFFECT_KEYFRAME_BEZIER || key.type == EFFECT_KEYFRAME_BEZIER) { - QPainterPath bezier_path; - bezier_path.moveTo(last_key_x, last_key_y); - if (last_key.type == EFFECT_KEYFRAME_BEZIER && key.type == EFFECT_KEYFRAME_BEZIER) { - // cubic bezier - bezier_path.cubicTo( - QPointF(last_key_x+last_key.post_handle.x()*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), - QPointF(key_x+key.pre_handle.x()*x_zoom, key_y-key.pre_handle.y()*y_zoom), - QPointF(key_x, key_y) - ); - } else if (key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier - // last keyframe is the bezier one - bezier_path.quadTo( - QPointF(last_key_x+last_key.post_handle.x()*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), - QPointF(key_x, key_y) - ); - } else { - // this keyframe is the bezier one - bezier_path.quadTo( - QPointF(key_x+key.pre_handle.x()*x_zoom, key_y-key.pre_handle.y()*y_zoom), - QPointF(key_x, key_y) - ); - } - if (bezier_path.intersects(mouse_rect)) { - // dout << "make an BEZIER key on field" << i << "after key" << j; - click_add = true; - } - } else { - // linear - QPainterPath linear_path; - linear_path.moveTo(last_key_x, last_key_y); - linear_path.lineTo(key_x, key_y); - if (linear_path.intersects(mouse_rect)) { - // dout << "make an LINEAR key on field" << i << "after key" << j; - click_add = true; - } - } - } - } - } - } - } - if (click_add) { - click_add_field = f; - setCursor(Qt::CrossCursor); - break; - } - } - } - } -} - -void GraphView::mouseReleaseEvent(QMouseEvent *) { - if (click_add_proc) { - 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) { - case kBezierHandleNone: - for (int i=0;iField(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; - ca->append(new SetDouble(&key.time, selected_keys_old_vals.at(i), key.time)); - ca->append(new SetQVariant(&key.data, selected_keys_old_doubles.at(i), key.data)); - } - break; - case kBezierHandlePre: - case kBezierHandlePost: - { - EffectKeyframe& key = row->Field(handle_field)->keyframes[handle_index]; - ca->append(new SetPointF(&key.pre_handle, QPointF(old_pre_handle_x, old_pre_handle_y), key.pre_handle)); - ca->append(new SetPointF(&key.post_handle, QPointF(old_post_handle_x, old_post_handle_y), key.post_handle)); - } - break; - } - olive::undo_stack.push(ca); - } - moved_keys = false; - mousedown = false; - click_add = false; - click_add_proc = false; - if (rect_select) { - rect_select = false; - selection_update(); - update(); - } -} - -void GraphView::wheelEvent(QWheelEvent *event) { - - bool redraw = false; - bool zooming = false; - - // Respect the "Scroll Wheel Zooms" option here; Ctrl toggles. - // Default zoom: zoom uniformly (both axes equally) - // Alt: zoom vertically - // Shift: zoom horizontally - // Alt + Shift: zoom uniformly - - bool shift = (event->modifiers() & Qt::ShiftModifier); - bool ctrl = (event->modifiers() & Qt::ControlModifier); - bool alt = (event->modifiers() & Qt::AltModifier); - - int delta_h = event->angleDelta().x(); - int delta_v = event->angleDelta().y(); - - double new_x_zoom = x_zoom; - double new_y_zoom = y_zoom; - - if (ctrl != olive::config.scroll_zooms) { - zooming = true; - } - - if (zooming) { - // Combine "source" deltas when zooming. Key modifiers determine axis - delta_h = delta_h + delta_v; - delta_v = delta_h; - } - - // If Alt is held but not Shift, make it a vertical zoom - if (zooming && alt && !shift) { - delta_h = 0; - } - - // If Shift is held but not Alt, make it a horizontal zoom - if (zooming && shift && !alt) { - delta_v = 0; - } - - if (!zooming) { - // Shift to swap axes - if (shift) { - std::swap(delta_h, delta_v); - } - - // Minus to correct for scroll vs. zoom behavior on horiz axis - set_scroll_x(x_scroll - (delta_h / 10)); - set_scroll_y(y_scroll + (delta_v / 10)); - - redraw = true; - } - - if (zooming && (delta_v != 0)) { - double zoom_diff = (kGraphZoomSpeed*y_zoom); - new_y_zoom = y_zoom + (zoom_diff * (delta_v / 120.0)); - - // Center zoom around the mouse cursor vertically - int true_mouse_y = height()-event->pos().y(); - set_scroll_y(qRound((double(y_scroll + true_mouse_y) / y_zoom) * new_y_zoom) - true_mouse_y); - - redraw = true; - } - - if (zooming && (delta_h != 0)) { - double zoom_diff = (kGraphZoomSpeed*x_zoom); - - new_x_zoom = x_zoom + (zoom_diff * (delta_h / 120.0)); - - // Center zoom around the mouse cursor horizontally - set_scroll_x(qRound((double(x_scroll + event->pos().x()) / x_zoom) * new_x_zoom) - event->pos().x()); - - redraw = true; - } - - if (zooming) { - set_zoom(new_x_zoom, new_y_zoom); - } - - if (redraw) { - update(); - } -} - -void GraphView::set_row(NodeIO *r) { - if (row != r) { - selected_keys.clear(); - selected_keys_fields.clear(); - selected_keys_old_vals.clear(); - selected_keys_old_doubles.clear(); - emit selection_changed(false, -1); - row = r; - if (row != nullptr) { - field_visibility.resize(row->FieldCount()); - for (int i=0;iFieldCount();i++) { - field_visibility[i] = row->Field(i)->IsEnabled(); - } - // FIXME - //visible_in = row->ParentNode()->parent_clip->timeline_in(); - set_view_to_all(); - } else { - update(); - } - } -} - -void GraphView::set_selected_keyframe_type(int type) { - if (selected_keys.size() > 0) { - ComboAction* ca = new ComboAction(); - for (int i=0;iField(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; - ca->append(new SetInt(&key.type, type)); - } - olive::undo_stack.push(ca); - update_ui(false); - } -} - -void GraphView::set_field_visibility(int field, bool b) { - field_visibility[field] = b; - update(); -} - -void GraphView::delete_selected_keys() { - if (row != nullptr) { - QVector fields; - for (int i=0;iField(selected_keys_fields.at(i))); - } - delete_keyframes(fields, selected_keys); - } -} - -void GraphView::select_all() { - if (row != nullptr) { - selected_keys.clear(); - selected_keys_fields.clear(); - for (int i=0;iFieldCount();i++) { - EffectField* field = row->Field(i); - for (int j=0;jkeyframes.size();j++) { - selected_keys.append(j); - selected_keys_fields.append(i); - } - } - selection_update(); - } -} - -void GraphView::set_scroll_x(int s) { - x_scroll = s; - emit x_scroll_changed(x_scroll); -} - -void GraphView::set_scroll_y(int s) { - y_scroll = s; - emit y_scroll_changed(y_scroll); -} - -void GraphView::set_zoom(double xz, double yz) { - x_zoom = xz; - y_zoom = yz; - emit zoom_changed(x_zoom, y_zoom); -} - -int GraphView::get_screen_x(double d) { - if (row != nullptr) { - // FIXME - //d -= row->GetParentEffect()->parent_clip->clip_in(); - } - return qRound((d*x_zoom) - x_scroll); -} - -int GraphView::get_screen_y(double d) { - return qRound(height() + y_scroll - d*y_zoom); -} - -long GraphView::get_value_x(int i) { - long frame = qRound((i + x_scroll)/x_zoom); - if (row != nullptr) { - // FIXME - //frame += row->GetParentEffect()->parent_clip->clip_in(); - } - return frame; -} - -double GraphView::get_value_y(int i) { - return double(height() + y_scroll - i)/y_zoom; -} - -void GraphView::selection_update() { - selected_keys_old_vals.clear(); - selected_keys_old_doubles.clear(); - - int selected_key_type = -1; - - for (int i=0;iField(selected_keys_fields.at(i))->keyframes.at(selected_keys.at(i)); - selected_keys_old_vals.append(key.time); - selected_keys_old_doubles.append(key.data.toDouble()); - - if (selected_key_type == -1) { - selected_key_type = key.type; - } else if (selected_key_type != key.type) { - selected_key_type = -2; - } - } - - update(); - - emit selection_changed(selected_keys.size() > 0, selected_key_type); -} +/*** + + 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 "graphview.h" + +#include +#include +#include +#include + +#include "global/config.h" +#include "panels/panels.h" +#include "panels/timeline.h" +#include "panels/viewer.h" +#include "timeline/sequence.h" +#include "ui/keyframedrawing.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "nodes/oldeffectnode.h" +#include "timeline/clip.h" +#include "ui/rectangleselect.h" +#include "ui/menu.h" +#include "global/debug.h" + +const double kGraphZoomSpeed = 0.05; +const int kGraphSize = 100; +const int kBezierHandleSize = 3; +const int kBezierLineSize = 2; + +const int kBezierHandleNone = 1; +const int kBezierHandlePre = 2; +const int kBezierHandlePost = 3; + +QColor get_curve_color(int index, int length) { + QColor c; + int hue = qRound((double(index)/double(length))*255); + c.setHsv(hue, 255, 255); + return c; +} + +GraphView::GraphView(QWidget* parent) : QWidget(parent) { + x_scroll = 0; + y_scroll = 0; + mousedown = false; + x_zoom = 1.0; + y_zoom = 1.0; + row = nullptr; + moved_keys = false; + current_handle = kBezierHandleNone; + rect_select = false; + visible_in = 0; + click_add_proc = false; + + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); +} + +void GraphView::show_context_menu(const QPoint& pos) { + Menu menu(this); + + QAction* zoom_to_selection = menu.addAction(tr("Zoom to Selection")); + if (selected_keys.size() == 0 || row == nullptr) { + zoom_to_selection->setEnabled(false); + } else { + connect(zoom_to_selection, SIGNAL(triggered(bool)), this, SLOT(set_view_to_selection())); + } + + QAction* zoom_to_all = menu.addAction(tr("Zoom to Show All")); + if (row == nullptr) { + zoom_to_all->setEnabled(false); + } else { + connect(zoom_to_all, SIGNAL(triggered(bool)), this, SLOT(set_view_to_all())); + } + + menu.addSeparator(); + + QAction* reset_action = menu.addAction(tr("Reset View")); + if (row == nullptr) { + reset_action->setEnabled(false); + } else { + connect(reset_action, SIGNAL(triggered(bool)), this, SLOT(reset_view())); + } + + menu.exec(mapToGlobal(pos)); +} + +void GraphView::reset_view() { + x_zoom = 1.0; + y_zoom = 1.0; + set_scroll_x(0); + set_scroll_y(0); + emit zoom_changed(x_zoom, y_zoom); + update(); +} + +void GraphView::set_view_to_selection() { + if (row != nullptr && selected_keys.size() > 0) { + double min_time = DBL_MAX; + double max_time = DBL_MIN; + double min_dbl = DBL_MAX; + double max_dbl = DBL_MIN; + for (int i=0;iField(selected_keys_fields.at(i))->keyframes.at(selected_keys.at(i)); + min_time = qMin(key.time, min_time); + max_time = qMax(key.time, max_time); + min_dbl = qMin(key.data.toDouble(), min_dbl); + max_dbl = qMax(key.data.toDouble(), max_dbl); + } + + /* FIXME + min_time -= row->GetParentEffect()->parent_clip->clip_in(); + max_time -= row->GetParentEffect()->parent_clip->clip_in(); + */ + + set_view_to_rect(min_time, min_dbl, max_time, max_dbl); + } +} + +void GraphView::set_view_to_all() { + if (row != nullptr) { + bool can_set = false; + + double min_time = DBL_MAX; + double max_time = DBL_MIN; + double min_dbl = DBL_MAX; + double max_dbl = DBL_MIN; + for (int i=0;iFieldCount();i++) { + for (int j=0;jField(i)->keyframes.size();j++) { + const EffectKeyframe& key = row->Field(i)->keyframes.at(j); + min_time = qMin(key.time, min_time); + max_time = qMax(key.time, max_time); + min_dbl = qMin(key.data.toDouble(), min_dbl); + max_dbl = qMax(key.data.toDouble(), max_dbl); + can_set = true; + } + } + if (can_set) { + /* FIXME + min_time -= row->GetParentEffect()->parent_clip->clip_in(); + max_time -= row->GetParentEffect()->parent_clip->clip_in(); + */ + + set_view_to_rect(min_time, min_dbl, max_time, max_dbl); + } + } +} + +void GraphView::set_view_to_rect(double x1, double y1, double x2, double y2) { + double padding = 1.5; + double x_diff = double(x2 - x1); + double y_diff = (y2 - y1); + double x_diff_padded = (x_diff+10)*padding; + double y_diff_padded = (y_diff+10)*padding; + set_zoom(double(width()) / x_diff_padded, double(height()) / y_diff_padded); + + set_scroll_x(qRound((double(x1) - ((x_diff_padded-x_diff)/2))*x_zoom)); + set_scroll_y(qRound((double(y1) - ((y_diff_padded-y_diff)/2))*y_zoom)); +} + +void GraphView::draw_line_text(QPainter &p, bool vert, int line_no, int line_pos, int next_line_pos) { + // draws last line's text + QString str = QString::number(line_no*kGraphSize); + int text_sz = vert ? fontMetrics().height() : fontMetrics().width(str); + if (text_sz < (next_line_pos - line_pos)) { + QRect text_rect = vert ? QRect(0, line_pos-50, 50, 50) : QRect(line_pos, height()-50, 50, 50); + p.drawText(text_rect, Qt::AlignBottom | Qt::AlignLeft, str); + } +} + +void GraphView::draw_lines(QPainter& p, bool vert) { + int last_line = INT_MIN; + int last_line_x = INT_MIN; + int lim = vert ? height() : width(); + int scroll = vert ? y_scroll : x_scroll; + for (int i=0;i sort_keys_from_field(EffectField* field) { + QVector sorted_keys; + for (int k=0;kkeyframes.size();k++) { + bool inserted = false; + for (int j=0;jkeyframes.at(sorted_keys.at(j)).time > field->keyframes.at(k).time) { + sorted_keys.insert(j, k); + inserted = true; + break; + } + } + if (!inserted) { + sorted_keys.append(k); + } + } + return sorted_keys; +} + +void GraphView::paintEvent(QPaintEvent *) { + QPainter p(this); + + if (panel_sequence_viewer->seq != nullptr) { + // draw grid lines + + p.setPen(Qt::gray); + + draw_lines(p, true); + draw_lines(p, false); + + // draw keyframes + if (row != nullptr) { + QPen line_pen; + line_pen.setWidth(kBezierLineSize); + + for (int i=row->FieldCount()-1;i>=0;i--) { + EffectField* field = row->Field(i); + + if (field->type() == EffectField::EFFECT_FIELD_DOUBLE && field_visibility.at(i)) { + // sort keyframes by time + QVector sorted_keys = sort_keys_from_field(field); + + int last_key_x = 0; + int last_key_y = 0; + + // draw lines + for (int j=0;jkeyframes.at(sorted_keys.at(j)); + + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + + line_pen.setColor(get_curve_color(i, row->FieldCount())); + p.setPen(line_pen); + if (j == 0) { + p.drawLine(0, key_y, key_x, key_y); + } else { + const EffectKeyframe& last_key = field->keyframes.at(sorted_keys.at(j-1)); + + double pre_handle = field->GetValidKeyframeHandlePosition(sorted_keys.at(j), false); + double last_post_handle = field->GetValidKeyframeHandlePosition(sorted_keys.at(j-1), true); + + if (last_key.type == EFFECT_KEYFRAME_HOLD) { + // hold + p.drawLine(last_key_x, last_key_y, key_x, last_key_y); + p.drawLine(key_x, last_key_y, key_x, key_y); + } else if (last_key.type == EFFECT_KEYFRAME_BEZIER || key.type == EFFECT_KEYFRAME_BEZIER) { + QPainterPath bezier_path; + bezier_path.moveTo(last_key_x, last_key_y); + if (last_key.type == EFFECT_KEYFRAME_BEZIER && key.type == EFFECT_KEYFRAME_BEZIER) { + // cubic bezier + bezier_path.cubicTo( + QPointF(last_key_x+last_post_handle*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), + QPointF(key_x+pre_handle*x_zoom, key_y-key.pre_handle.y()*y_zoom), + QPointF(key_x, key_y) + ); + } else if (key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier + // last keyframe is the bezier one + bezier_path.quadTo( + QPointF(last_key_x+last_post_handle*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), + QPointF(key_x, key_y) + ); + } else { + // this keyframe is the bezier one + bezier_path.quadTo( + QPointF(key_x+pre_handle*x_zoom, key_y-key.pre_handle.y()*y_zoom), + QPointF(key_x, key_y) + ); + } + p.drawPath(bezier_path); + } else { + // linear + p.drawLine(last_key_x, last_key_y, key_x, key_y); + } + } + last_key_x = key_x; + last_key_y = key_y; + } + if (last_key_x < width()) p.drawLine(last_key_x, last_key_y, width(), last_key_y); + + // draw keys + for (int j=0;jkeyframes.at(sorted_keys.at(j)); + + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + + if (key.type == EFFECT_KEYFRAME_BEZIER) { + p.setPen(Qt::gray); + + // pre handle line + QPointF pre_point(key_x + key.pre_handle.x()*x_zoom, key_y - key.pre_handle.y()*y_zoom); + p.drawLine(pre_point, QPointF(key_x, key_y)); + p.drawEllipse(pre_point, kBezierHandleSize, kBezierHandleSize); + + // post handle line + QPointF post_point(key_x + key.post_handle.x()*x_zoom, key_y - key.post_handle.y()*y_zoom); + p.drawLine(post_point, QPointF(key_x, key_y)); + p.drawEllipse(post_point, kBezierHandleSize, kBezierHandleSize); + } + + bool selected = false; + for (int k=0;kseq->playhead - visible_in)*x_zoom) - x_scroll); + p.drawLine(playhead_x, 0, playhead_x, height()); + + if (rect_select) { + olive::ui::DrawSelectionRectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); + p.setBrush(Qt::NoBrush); + } + } + + p.setPen(Qt::white); + + QRect border = rect(); + border.setWidth(border.width()-1); + border.setHeight(border.height()-1); + p.drawRect(border); +} + +void GraphView::mousePressEvent(QMouseEvent *event) { + if (row != nullptr) { + mousedown = true; + start_x = event->pos().x(); + start_y = event->pos().y(); + + // selecting + int sel_key = -1; + int sel_key_field = -1; + current_handle = kBezierHandleNone; + + if (click_add && (event->buttons() & Qt::LeftButton)) { + selected_keys.clear(); + selected_keys_fields.clear(); + + EffectKeyframe key; + key.time = get_value_x(event->pos().x()); + key.data = get_value_y(event->pos().y()); + key.type = click_add_type; + click_add_key = click_add_field->keyframes.size(); + click_add_field->keyframes.append(key); + update_ui(false); + click_add_proc = true; + } else { + for (int i=0;iFieldCount();i++) { + EffectField* field = row->Field(i); + if (field->type() == EffectField::EFFECT_FIELD_DOUBLE && field_visibility.at(i)) { + for (int j=0;jkeyframes.size();j++) { + const EffectKeyframe& key = field->keyframes.at(j); + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + if (event->pos().x() > key_x-KEYFRAME_SIZE + && event->pos().x() < key_x+KEYFRAME_SIZE + && event->pos().y() > key_y-KEYFRAME_SIZE + && event->pos().y() < key_y+KEYFRAME_SIZE) { + sel_key = j; + sel_key_field = i; + break; + } else { + // selecting a handle + QPointF pre_point(key_x + key.pre_handle.x()*x_zoom, key_y - key.pre_handle.y()*y_zoom); + QPointF post_point(key_x + key.post_handle.x()*x_zoom, key_y - key.post_handle.y()*y_zoom); + if (event->pos().x() > pre_point.x()-kBezierHandleSize + && event->pos().x() < pre_point.x()+kBezierHandleSize + && event->pos().y() > pre_point.y()-kBezierHandleSize + && event->pos().y() < pre_point.y()+kBezierHandleSize) { + current_handle = kBezierHandlePre; + } else if (event->pos().x() > post_point.x()-kBezierHandleSize + && event->pos().x() < post_point.x()+kBezierHandleSize + && event->pos().y() > post_point.y()-kBezierHandleSize + && event->pos().y() < post_point.y()+kBezierHandleSize) { + current_handle = kBezierHandlePost; + } + + if (current_handle != kBezierHandleNone) { + sel_key = j; + sel_key_field = i; + handle_index = j; + handle_field = i; + old_pre_handle_x = key.pre_handle.x(); + old_pre_handle_y = key.pre_handle.y(); + old_post_handle_x = key.post_handle.x(); + old_post_handle_y = key.post_handle.y(); + break; + } + } + } + } + if (sel_key > -1) break; + } + + bool already_selected = false; + if (sel_key > -1) { + for (int i=0;imodifiers() & Qt::ShiftModifier) && current_handle == kBezierHandleNone) { + selected_keys.removeAt(i); + selected_keys_fields.removeAt(i); + } + already_selected = true; + break; + } + } + } + if (!already_selected) { + if (!(event->modifiers() & Qt::ShiftModifier)) { + selected_keys.clear(); + selected_keys_fields.clear(); + } + if (sel_key > -1) { + selected_keys.append(sel_key); + selected_keys_fields.append(sel_key_field); + } else { + rect_select = true; + rect_select_x = event->pos().x(); + rect_select_y = event->pos().y(); + rect_select_w = 0; + rect_select_h = 0; + rect_select_offset = selected_keys.size(); + } + } + + selection_update(); + } + } +} + +void GraphView::mouseMoveEvent(QMouseEvent *event) { + if (!mousedown || !click_add) unsetCursor(); + if (mousedown) { + 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(); + start_y = event->pos().y(); + update(); + } else if (click_add_proc) { + click_add_field->keyframes[click_add_key].time = get_value_x(event->pos().x()); + click_add_field->keyframes[click_add_key].data = get_value_y(event->pos().y()); + update_ui(false); + } else if (rect_select) { + rect_select_w = event->pos().x() - rect_select_x; + rect_select_h = event->pos().y() - rect_select_y; + + selected_keys.resize(rect_select_offset); + selected_keys_fields.resize(rect_select_offset); + + for (int i=0;iFieldCount();i++) { + EffectField* f = row->Field(i); + for (int j=0;jkeyframes.size();j++) { + bool already_selected = false; + for (int k=0;kkeyframes.at(j).time), get_screen_y(f->keyframes.at(j).data.toDouble())); + QRect select_rect(rect_select_x, rect_select_y, rect_select_w, rect_select_h); + if (select_rect.contains(key_screen_point)) { + selected_keys.append(j); + selected_keys_fields.append(i); + } + } + } + } + update(); + } else { + switch (current_handle) { + case kBezierHandleNone: + for (int i=0;iField(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].time = qRound(selected_keys_old_vals.at(i) + (double(event->pos().x() - start_x)/x_zoom)); + if (event->modifiers() & Qt::ShiftModifier) { + row->Field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].data = selected_keys_old_doubles.at(i); + } else { + row->Field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].data = qRound(selected_keys_old_doubles.at(i) + (double(start_y - event->pos().y())/y_zoom)); + } + } + moved_keys = true; + update_ui(false); + break; + case kBezierHandlePre: + case kBezierHandlePost: + { + double new_pre_handle_x = old_pre_handle_x; + double new_pre_handle_y = old_pre_handle_y; + double new_post_handle_x = old_post_handle_x; + double new_post_handle_y = old_post_handle_y; + + double x_diff = double(event->pos().x() - start_x)/x_zoom; + double y_diff = double(start_y - event->pos().y())/y_zoom; + + if (current_handle == kBezierHandlePre) { + new_pre_handle_x += x_diff; + if (!(event->modifiers() & Qt::ShiftModifier)) new_pre_handle_y += y_diff; + if (!(event->modifiers() & Qt::ControlModifier)) { + new_post_handle_x = -new_pre_handle_x; + new_post_handle_y = -new_pre_handle_y; + } + } else { + new_post_handle_x += x_diff; + if (!(event->modifiers() & Qt::ShiftModifier)) new_post_handle_y += y_diff; + if (!(event->modifiers() & Qt::ControlModifier)) { + new_pre_handle_x = -new_post_handle_x; + new_pre_handle_y = -new_post_handle_y; + } + } + + EffectKeyframe& key = row->Field(handle_field)->keyframes[handle_index]; + key.pre_handle = QPointF(qMin(0.0, new_pre_handle_x), new_pre_handle_y); + key.post_handle = QPointF(qMax(0.0, new_post_handle_x), new_post_handle_y); + + moved_keys = true; + update_ui(false); + } + break; + } + } + } else if (row != nullptr) { + // clicking on the curve + click_add = false; + + bool hovering_key = false; + + for (int i=0;iFieldCount();i++) { + for (int j=0;jField(i)->keyframes.size();j++) { + const EffectKeyframe& key = row->Field(i)->keyframes.at(j); + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + QRect test_rect( + key_x - KEYFRAME_SIZE, + key_y - KEYFRAME_SIZE, + KEYFRAME_SIZE+KEYFRAME_SIZE, + KEYFRAME_SIZE+KEYFRAME_SIZE + ); + QRect pre_rect( + qRound(key_x + key.pre_handle.x()*x_zoom - kBezierHandleSize), + qRound(key_y + key.pre_handle.y()*y_zoom - kBezierHandleSize), + kBezierHandleSize+kBezierHandleSize, + kBezierHandleSize+kBezierHandleSize + ); + QRect post_rect( + qRound(key_x + key.post_handle.x()*x_zoom - kBezierHandleSize), + qRound(key_y + key.post_handle.y()*y_zoom - kBezierHandleSize), + kBezierHandleSize+kBezierHandleSize, + kBezierHandleSize+kBezierHandleSize + ); + + if (test_rect.contains(event->pos()) + || pre_rect.contains(event->pos()) + || post_rect.contains(event->pos())) { + hovering_key = true; + break; + } + } + } + + if (!hovering_key) { + for (int i=0;iFieldCount();i++) { + EffectField* f = row->Field(i); + if (field_visibility.at(i)) { + QVector sorted_keys = sort_keys_from_field(f); + + if (!sorted_keys.isEmpty()) { + if (event->pos().x() <= get_screen_x(f->keyframes.at(sorted_keys.first()).time)) { + int y_comp = get_screen_y(f->keyframes.at(sorted_keys.first()).data.toDouble()); + if (event->pos().y() >= y_comp-kBezierLineSize + && event->pos().y() <= y_comp+kBezierLineSize) { + // dout << "make an EARLY key on field" << i; + click_add = true; + click_add_type = f->keyframes.at(sorted_keys.first()).type; + } + } else if (event->pos().x() >= get_screen_x(f->keyframes.at(sorted_keys.last()).time)) { + int y_comp = get_screen_y(f->keyframes.at(sorted_keys.last()).data.toDouble()); + if (event->pos().y() >= y_comp-kBezierLineSize + && event->pos().y() <= y_comp+kBezierLineSize) { + // dout << "make an LATE key on field" << i; + click_add = true; + click_add_type = f->keyframes.at(sorted_keys.last()).type; + } + } else { + for (int j=1;jkeyframes.at(sorted_keys.at(j-1)); + const EffectKeyframe& key = f->keyframes.at(sorted_keys.at(j)); + + int last_key_x = get_screen_x(last_key.time); + int key_x = get_screen_x(key.time); + int last_key_y = get_screen_y(last_key.data.toDouble()); + int key_y = get_screen_y(key.data.toDouble()); + + click_add_type = last_key.type; + + if (event->pos().x() >= last_key_x + && event->pos().x() <= key_x) { + QRect mouse_rect(event->pos().x()-kBezierLineSize, event->pos().y()-kBezierLineSize, kBezierLineSize+kBezierLineSize, kBezierLineSize+kBezierLineSize); + // NOTE: FILTHY copy/paste from paintEvent + if (last_key.type == EFFECT_KEYFRAME_HOLD) { + // hold + if (event->pos().y() >= last_key_y-kBezierLineSize + && event->pos().y() <= last_key_y+kBezierLineSize) { + // dout << "make an HOLD key on field" << i << "after key" << j; + click_add = true; + } + } else if (last_key.type == EFFECT_KEYFRAME_BEZIER || key.type == EFFECT_KEYFRAME_BEZIER) { + QPainterPath bezier_path; + bezier_path.moveTo(last_key_x, last_key_y); + if (last_key.type == EFFECT_KEYFRAME_BEZIER && key.type == EFFECT_KEYFRAME_BEZIER) { + // cubic bezier + bezier_path.cubicTo( + QPointF(last_key_x+last_key.post_handle.x()*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), + QPointF(key_x+key.pre_handle.x()*x_zoom, key_y-key.pre_handle.y()*y_zoom), + QPointF(key_x, key_y) + ); + } else if (key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier + // last keyframe is the bezier one + bezier_path.quadTo( + QPointF(last_key_x+last_key.post_handle.x()*x_zoom, last_key_y-last_key.post_handle.y()*y_zoom), + QPointF(key_x, key_y) + ); + } else { + // this keyframe is the bezier one + bezier_path.quadTo( + QPointF(key_x+key.pre_handle.x()*x_zoom, key_y-key.pre_handle.y()*y_zoom), + QPointF(key_x, key_y) + ); + } + if (bezier_path.intersects(mouse_rect)) { + // dout << "make an BEZIER key on field" << i << "after key" << j; + click_add = true; + } + } else { + // linear + QPainterPath linear_path; + linear_path.moveTo(last_key_x, last_key_y); + linear_path.lineTo(key_x, key_y); + if (linear_path.intersects(mouse_rect)) { + // dout << "make an LINEAR key on field" << i << "after key" << j; + click_add = true; + } + } + } + } + } + } + } + if (click_add) { + click_add_field = f; + setCursor(Qt::CrossCursor); + break; + } + } + } + } +} + +void GraphView::mouseReleaseEvent(QMouseEvent *) { + if (click_add_proc) { + 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) { + case kBezierHandleNone: + for (int i=0;iField(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; + ca->append(new SetDouble(&key.time, selected_keys_old_vals.at(i), key.time)); + ca->append(new SetQVariant(&key.data, selected_keys_old_doubles.at(i), key.data)); + } + break; + case kBezierHandlePre: + case kBezierHandlePost: + { + EffectKeyframe& key = row->Field(handle_field)->keyframes[handle_index]; + ca->append(new SetPointF(&key.pre_handle, QPointF(old_pre_handle_x, old_pre_handle_y), key.pre_handle)); + ca->append(new SetPointF(&key.post_handle, QPointF(old_post_handle_x, old_post_handle_y), key.post_handle)); + } + break; + } + olive::undo_stack.push(ca); + } + moved_keys = false; + mousedown = false; + click_add = false; + click_add_proc = false; + if (rect_select) { + rect_select = false; + selection_update(); + update(); + } +} + +void GraphView::wheelEvent(QWheelEvent *event) { + + bool redraw = false; + bool zooming = false; + + // Respect the "Scroll Wheel Zooms" option here; Ctrl toggles. + // Default zoom: zoom uniformly (both axes equally) + // Alt: zoom vertically + // Shift: zoom horizontally + // Alt + Shift: zoom uniformly + + bool shift = (event->modifiers() & Qt::ShiftModifier); + bool ctrl = (event->modifiers() & Qt::ControlModifier); + bool alt = (event->modifiers() & Qt::AltModifier); + + int delta_h = event->angleDelta().x(); + int delta_v = event->angleDelta().y(); + + double new_x_zoom = x_zoom; + double new_y_zoom = y_zoom; + + if (ctrl != olive::config.scroll_zooms) { + zooming = true; + } + + if (zooming) { + // Combine "source" deltas when zooming. Key modifiers determine axis + delta_h = delta_h + delta_v; + delta_v = delta_h; + } + + // If Alt is held but not Shift, make it a vertical zoom + if (zooming && alt && !shift) { + delta_h = 0; + } + + // If Shift is held but not Alt, make it a horizontal zoom + if (zooming && shift && !alt) { + delta_v = 0; + } + + if (!zooming) { + // Shift to swap axes + if (shift) { + std::swap(delta_h, delta_v); + } + + // Minus to correct for scroll vs. zoom behavior on horiz axis + set_scroll_x(x_scroll - (delta_h / 10)); + set_scroll_y(y_scroll + (delta_v / 10)); + + redraw = true; + } + + if (zooming && (delta_v != 0)) { + double zoom_diff = (kGraphZoomSpeed*y_zoom); + new_y_zoom = y_zoom + (zoom_diff * (delta_v / 120.0)); + + // Center zoom around the mouse cursor vertically + int true_mouse_y = height()-event->pos().y(); + set_scroll_y(qRound((double(y_scroll + true_mouse_y) / y_zoom) * new_y_zoom) - true_mouse_y); + + redraw = true; + } + + if (zooming && (delta_h != 0)) { + double zoom_diff = (kGraphZoomSpeed*x_zoom); + + new_x_zoom = x_zoom + (zoom_diff * (delta_h / 120.0)); + + // Center zoom around the mouse cursor horizontally + set_scroll_x(qRound((double(x_scroll + event->pos().x()) / x_zoom) * new_x_zoom) - event->pos().x()); + + redraw = true; + } + + if (zooming) { + set_zoom(new_x_zoom, new_y_zoom); + } + + if (redraw) { + update(); + } +} + +void GraphView::set_row(NodeIO *r) { + if (row != r) { + selected_keys.clear(); + selected_keys_fields.clear(); + selected_keys_old_vals.clear(); + selected_keys_old_doubles.clear(); + emit selection_changed(false, -1); + row = r; + if (row != nullptr) { + field_visibility.resize(row->FieldCount()); + for (int i=0;iFieldCount();i++) { + field_visibility[i] = row->Field(i)->IsEnabled(); + } + // FIXME + //visible_in = row->ParentNode()->parent_clip->timeline_in(); + set_view_to_all(); + } else { + update(); + } + } +} + +void GraphView::set_selected_keyframe_type(int type) { + if (selected_keys.size() > 0) { + ComboAction* ca = new ComboAction(); + for (int i=0;iField(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; + ca->append(new SetInt(&key.type, type)); + } + olive::undo_stack.push(ca); + update_ui(false); + } +} + +void GraphView::set_field_visibility(int field, bool b) { + field_visibility[field] = b; + update(); +} + +void GraphView::delete_selected_keys() { + if (row != nullptr) { + QVector fields; + for (int i=0;iField(selected_keys_fields.at(i))); + } + delete_keyframes(fields, selected_keys); + } +} + +void GraphView::select_all() { + if (row != nullptr) { + selected_keys.clear(); + selected_keys_fields.clear(); + for (int i=0;iFieldCount();i++) { + EffectField* field = row->Field(i); + for (int j=0;jkeyframes.size();j++) { + selected_keys.append(j); + selected_keys_fields.append(i); + } + } + selection_update(); + } +} + +void GraphView::set_scroll_x(int s) { + x_scroll = s; + emit x_scroll_changed(x_scroll); +} + +void GraphView::set_scroll_y(int s) { + y_scroll = s; + emit y_scroll_changed(y_scroll); +} + +void GraphView::set_zoom(double xz, double yz) { + x_zoom = xz; + y_zoom = yz; + emit zoom_changed(x_zoom, y_zoom); +} + +int GraphView::get_screen_x(double d) { + if (row != nullptr) { + // FIXME + //d -= row->GetParentEffect()->parent_clip->clip_in(); + } + return qRound((d*x_zoom) - x_scroll); +} + +int GraphView::get_screen_y(double d) { + return qRound(height() + y_scroll - d*y_zoom); +} + +long GraphView::get_value_x(int i) { + long frame = qRound((i + x_scroll)/x_zoom); + if (row != nullptr) { + // FIXME + //frame += row->GetParentEffect()->parent_clip->clip_in(); + } + return frame; +} + +double GraphView::get_value_y(int i) { + return double(height() + y_scroll - i)/y_zoom; +} + +void GraphView::selection_update() { + selected_keys_old_vals.clear(); + selected_keys_old_doubles.clear(); + + int selected_key_type = -1; + + for (int i=0;iField(selected_keys_fields.at(i))->keyframes.at(selected_keys.at(i)); + selected_keys_old_vals.append(key.time); + selected_keys_old_doubles.append(key.data.toDouble()); + + if (selected_key_type == -1) { + selected_key_type = key.type; + } else if (selected_key_type != key.type) { + selected_key_type = -2; + } + } + + update(); + + emit selection_changed(selected_keys.size() > 0, selected_key_type); +} diff --git a/ui/graphview.h b/ui/graphview.h index d88991134..9aa4e4842 100644 --- a/ui/graphview.h +++ b/ui/graphview.h @@ -1,122 +1,122 @@ -/*** - - 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 GRAPHVIEW_H -#define GRAPHVIEW_H - -#include -#include - -#include "nodes/nodeio.h" -#include "effects/effectfields.h" - -QColor get_curve_color(int index, int length); - -class GraphView : public QWidget { - Q_OBJECT -public: - GraphView(QWidget* parent = nullptr); - - void paintEvent(QPaintEvent *event); - void mousePressEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void wheelEvent(QWheelEvent *event); - - void set_row(NodeIO* r); - - void set_selected_keyframe_type(int type); - void set_field_visibility(int field, bool b); - - void delete_selected_keys(); - void select_all(); -signals: - void x_scroll_changed(int); - void y_scroll_changed(int); - void zoom_changed(double, double); - void selection_changed(bool, int); -private: - int x_scroll; - int y_scroll; - bool mousedown; - int start_x; - int start_y; - - double x_zoom; - double y_zoom; - - void set_scroll_x(int s); - void set_scroll_y(int s); - void set_zoom(double xz, double yz); - - int get_screen_x(double); - int get_screen_y(double); - long get_value_x(int); - double get_value_y(int); - - void selection_update(); - - QVector field_visibility; - - QVector selected_keys; - QVector selected_keys_fields; - QVector selected_keys_old_vals; - QVector selected_keys_old_doubles; - - double old_pre_handle_x; - double old_pre_handle_y; - double old_post_handle_x; - double old_post_handle_y; - - int handle_field; - int handle_index; - - bool moved_keys; - - int current_handle; - - void draw_lines(QPainter &p, bool vert); - void draw_line_text(QPainter &p, bool vert, int line_no, int line_pos, int next_line_pos); - - NodeIO* row; - - bool rect_select; - int rect_select_x; - int rect_select_y; - int rect_select_w; - int rect_select_h; - int rect_select_offset; - - long visible_in; - - bool click_add; - bool click_add_proc; - EffectField* click_add_field; - int click_add_key; - int click_add_type; -private slots: - void show_context_menu(const QPoint& pos); - void reset_view(); - void set_view_to_selection(); - void set_view_to_all(); - void set_view_to_rect(double x1, double y1, double x2, double y2); -}; - -#endif // GRAPHVIEW_H +/*** + + 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 GRAPHVIEW_H +#define GRAPHVIEW_H + +#include +#include + +#include "nodes/nodeio.h" +#include "effects/effectfields.h" + +QColor get_curve_color(int index, int length); + +class GraphView : public QWidget { + Q_OBJECT +public: + GraphView(QWidget* parent = nullptr); + + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void wheelEvent(QWheelEvent *event); + + void set_row(NodeIO* r); + + void set_selected_keyframe_type(int type); + void set_field_visibility(int field, bool b); + + void delete_selected_keys(); + void select_all(); +signals: + void x_scroll_changed(int); + void y_scroll_changed(int); + void zoom_changed(double, double); + void selection_changed(bool, int); +private: + int x_scroll; + int y_scroll; + bool mousedown; + int start_x; + int start_y; + + double x_zoom; + double y_zoom; + + void set_scroll_x(int s); + void set_scroll_y(int s); + void set_zoom(double xz, double yz); + + int get_screen_x(double); + int get_screen_y(double); + long get_value_x(int); + double get_value_y(int); + + void selection_update(); + + QVector field_visibility; + + QVector selected_keys; + QVector selected_keys_fields; + QVector selected_keys_old_vals; + QVector selected_keys_old_doubles; + + double old_pre_handle_x; + double old_pre_handle_y; + double old_post_handle_x; + double old_post_handle_y; + + int handle_field; + int handle_index; + + bool moved_keys; + + int current_handle; + + void draw_lines(QPainter &p, bool vert); + void draw_line_text(QPainter &p, bool vert, int line_no, int line_pos, int next_line_pos); + + NodeIO* row; + + bool rect_select; + int rect_select_x; + int rect_select_y; + int rect_select_w; + int rect_select_h; + int rect_select_offset; + + long visible_in; + + bool click_add; + bool click_add_proc; + EffectField* click_add_field; + int click_add_key; + int click_add_type; +private slots: + void show_context_menu(const QPoint& pos); + void reset_view(); + void set_view_to_selection(); + void set_view_to_all(); + void set_view_to_rect(double x1, double y1, double x2, double y2); +}; + +#endif // GRAPHVIEW_H diff --git a/ui/icons.cpp b/ui/icons.cpp index 4e2342cfd..da6345668 100644 --- a/ui/icons.cpp +++ b/ui/icons.cpp @@ -1,114 +1,114 @@ -/*** - - 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 - -#include "global/global.h" -#include "global/config.h" - -QIcon olive::icon::LeftArrow; -QIcon olive::icon::RightArrow; -QIcon olive::icon::UpArrow; -QIcon olive::icon::DownArrow; -QIcon olive::icon::Diamond; -QIcon olive::icon::Clock; - -QIcon olive::icon::MediaVideo; -QIcon olive::icon::MediaAudio; -QIcon olive::icon::MediaImage; -QIcon olive::icon::MediaError; -QIcon olive::icon::MediaSequence; -QIcon olive::icon::MediaFolder; - -QIcon olive::icon::ViewerGoToStart; -QIcon olive::icon::ViewerPrevFrame; -QIcon olive::icon::ViewerPlay; -QIcon olive::icon::ViewerPause; -QIcon olive::icon::ViewerNextFrame; -QIcon olive::icon::ViewerGoToEnd; - -QIcon olive::icon::CreateIconFromSVG(const QString &path, bool create_disabled) -{ - QIcon icon; - - QPainter p; - - // Draw the icon as a solid color - QPixmap normal(path); - - // Color the icon dark if the user is using a dark theme - if (olive::styling::UseDarkIcons()) { - p.begin(&normal); - p.setCompositionMode(QPainter::CompositionMode_SourceIn); - p.fillRect(normal.rect(), QColor(32, 32, 32)); - p.end(); - } - - icon.addPixmap(normal, QIcon::Normal, QIcon::On); - - if (create_disabled) { - - // Create semi-transparent disabled icon - QPixmap disabled(normal.size()); - disabled.fill(Qt::transparent); - - // draw semi-transparent version of icon for the disabled variant - p.begin(&disabled); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setOpacity(0.5); - p.drawPixmap(0, 0, normal); - p.end(); - - icon.addPixmap(disabled, QIcon::Disabled, QIcon::On); - - } - - return icon; -} - -void olive::icon::Initialize() -{ - qInfo() << "Initializing icons"; - - LeftArrow = CreateIconFromSVG(":/icons/tri-left.svg", false); - RightArrow = CreateIconFromSVG(":/icons/tri-right.svg", false); - UpArrow = CreateIconFromSVG(":/icons/tri-up.svg", false); - DownArrow = CreateIconFromSVG(":/icons/tri-down.svg", false); - Diamond = CreateIconFromSVG(":/icons/diamond.svg", false); - Clock = CreateIconFromSVG(":/icons/clock.svg", false); - - MediaVideo = CreateIconFromSVG(":/icons/videosource.svg"); - MediaAudio = CreateIconFromSVG(":/icons/audiosource.svg"); - MediaImage = CreateIconFromSVG(":/icons/imagesource.svg", false); - MediaError = CreateIconFromSVG(":/icons/error.svg", false); - MediaSequence = CreateIconFromSVG(":/icons/sequence.svg", false); - MediaFolder = CreateIconFromSVG(":/icons/folder.svg", false); - - ViewerGoToStart = CreateIconFromSVG(QStringLiteral(":/icons/prev.svg")); - ViewerPrevFrame = CreateIconFromSVG(QStringLiteral(":/icons/rew.svg")); - ViewerPlay = CreateIconFromSVG(QStringLiteral(":/icons/play.svg")); - ViewerPause = CreateIconFromSVG(":/icons/pause.svg", false); - ViewerNextFrame = CreateIconFromSVG(QStringLiteral(":/icons/ff.svg")); - ViewerGoToEnd = CreateIconFromSVG(QStringLiteral(":/icons/next.svg")); - - qInfo() << "Finished initializing icons"; -} +/*** + + 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 + +#include "global/global.h" +#include "global/config.h" + +QIcon olive::icon::LeftArrow; +QIcon olive::icon::RightArrow; +QIcon olive::icon::UpArrow; +QIcon olive::icon::DownArrow; +QIcon olive::icon::Diamond; +QIcon olive::icon::Clock; + +QIcon olive::icon::MediaVideo; +QIcon olive::icon::MediaAudio; +QIcon olive::icon::MediaImage; +QIcon olive::icon::MediaError; +QIcon olive::icon::MediaSequence; +QIcon olive::icon::MediaFolder; + +QIcon olive::icon::ViewerGoToStart; +QIcon olive::icon::ViewerPrevFrame; +QIcon olive::icon::ViewerPlay; +QIcon olive::icon::ViewerPause; +QIcon olive::icon::ViewerNextFrame; +QIcon olive::icon::ViewerGoToEnd; + +QIcon olive::icon::CreateIconFromSVG(const QString &path, bool create_disabled) +{ + QIcon icon; + + QPainter p; + + // Draw the icon as a solid color + QPixmap normal(path); + + // Color the icon dark if the user is using a dark theme + if (olive::styling::UseDarkIcons()) { + p.begin(&normal); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(normal.rect(), QColor(32, 32, 32)); + p.end(); + } + + icon.addPixmap(normal, QIcon::Normal, QIcon::On); + + if (create_disabled) { + + // Create semi-transparent disabled icon + QPixmap disabled(normal.size()); + disabled.fill(Qt::transparent); + + // draw semi-transparent version of icon for the disabled variant + p.begin(&disabled); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setOpacity(0.5); + p.drawPixmap(0, 0, normal); + p.end(); + + icon.addPixmap(disabled, QIcon::Disabled, QIcon::On); + + } + + return icon; +} + +void olive::icon::Initialize() +{ + qInfo() << "Initializing icons"; + + LeftArrow = CreateIconFromSVG(":/icons/tri-left.svg", false); + RightArrow = CreateIconFromSVG(":/icons/tri-right.svg", false); + UpArrow = CreateIconFromSVG(":/icons/tri-up.svg", false); + DownArrow = CreateIconFromSVG(":/icons/tri-down.svg", false); + Diamond = CreateIconFromSVG(":/icons/diamond.svg", false); + Clock = CreateIconFromSVG(":/icons/clock.svg", false); + + MediaVideo = CreateIconFromSVG(":/icons/videosource.svg"); + MediaAudio = CreateIconFromSVG(":/icons/audiosource.svg"); + MediaImage = CreateIconFromSVG(":/icons/imagesource.svg", false); + MediaError = CreateIconFromSVG(":/icons/error.svg", false); + MediaSequence = CreateIconFromSVG(":/icons/sequence.svg", false); + MediaFolder = CreateIconFromSVG(":/icons/folder.svg", false); + + ViewerGoToStart = CreateIconFromSVG(QStringLiteral(":/icons/prev.svg")); + ViewerPrevFrame = CreateIconFromSVG(QStringLiteral(":/icons/rew.svg")); + ViewerPlay = CreateIconFromSVG(QStringLiteral(":/icons/play.svg")); + ViewerPause = CreateIconFromSVG(":/icons/pause.svg", false); + ViewerNextFrame = CreateIconFromSVG(QStringLiteral(":/icons/ff.svg")); + ViewerGoToEnd = CreateIconFromSVG(QStringLiteral(":/icons/next.svg")); + + qInfo() << "Finished initializing icons"; +} diff --git a/ui/icons.h b/ui/icons.h index e78180dbd..9ace7f109 100644 --- a/ui/icons.h +++ b/ui/icons.h @@ -1,66 +1,66 @@ -/*** - - 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 - -#include - -namespace olive { - namespace icon { - extern QIcon LeftArrow; - extern QIcon RightArrow; - extern QIcon UpArrow; - extern QIcon DownArrow; - extern QIcon Diamond; - extern QIcon Clock; - - extern QIcon MediaVideo; - extern QIcon MediaAudio; - extern QIcon MediaImage; - extern QIcon MediaError; - extern QIcon MediaSequence; - extern QIcon MediaFolder; - - extern QIcon ViewerGoToStart; - extern QIcon ViewerPrevFrame; - extern QIcon ViewerPlay; - extern QIcon ViewerPause; - extern QIcon ViewerNextFrame; - extern QIcon ViewerGoToEnd; - - void Initialize(); - - /** - * @brief Converts an SVG into a QIcon with a semi-transparent for the QIcon::Disabled property - * - * @param path - * - * Path to SVG file - * - * @param create_disabled - * - * Create a semi-transparent disabled option. - */ - QIcon CreateIconFromSVG(const QString &path, bool create_disabled = true); - } -} - -#endif // ICONS_H +/*** + + 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 + +#include + +namespace olive { + namespace icon { + extern QIcon LeftArrow; + extern QIcon RightArrow; + extern QIcon UpArrow; + extern QIcon DownArrow; + extern QIcon Diamond; + extern QIcon Clock; + + extern QIcon MediaVideo; + extern QIcon MediaAudio; + extern QIcon MediaImage; + extern QIcon MediaError; + extern QIcon MediaSequence; + extern QIcon MediaFolder; + + extern QIcon ViewerGoToStart; + extern QIcon ViewerPrevFrame; + extern QIcon ViewerPlay; + extern QIcon ViewerPause; + extern QIcon ViewerNextFrame; + extern QIcon ViewerGoToEnd; + + void Initialize(); + + /** + * @brief Converts an SVG into a QIcon with a semi-transparent for the QIcon::Disabled property + * + * @param path + * + * Path to SVG file + * + * @param create_disabled + * + * Create a semi-transparent disabled option. + */ + QIcon CreateIconFromSVG(const QString &path, bool create_disabled = true); + } +} + +#endif // ICONS_H diff --git a/ui/keyframedrawing.cpp b/ui/keyframedrawing.cpp index 9f0d3d069..951565611 100644 --- a/ui/keyframedrawing.cpp +++ b/ui/keyframedrawing.cpp @@ -1,64 +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 "keyframedrawing.h" - -#include "nodes/oldeffectnode.h" -#include "timeline/clip.h" - -#define KEYFRAME_POINT_COUNT 4 - -// routine for drawing a keyframe onscreen -void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r, int g, int b) { - if (darker) { - r *= 0.625; - g *= 0.625; - b *= 0.625; - } - p.setPen(QColor(0, 0, 0)); - p.setBrush(QColor(r, g, b)); - - switch (type) { - case EFFECT_KEYFRAME_LINEAR: - { - QPoint points[KEYFRAME_POINT_COUNT] = {QPoint(x-KEYFRAME_SIZE, y), QPoint(x, y-KEYFRAME_SIZE), QPoint(x+KEYFRAME_SIZE, y), QPoint(x, y+KEYFRAME_SIZE)}; - p.drawPolygon(points, KEYFRAME_POINT_COUNT); - } - break; - case EFFECT_KEYFRAME_BEZIER: - p.drawEllipse(QPoint(x, y), KEYFRAME_SIZE, KEYFRAME_SIZE); - break; - case EFFECT_KEYFRAME_HOLD: - p.drawRect(QRect(x - KEYFRAME_SIZE, y - KEYFRAME_SIZE, KEYFRAME_SIZE*2, KEYFRAME_SIZE*2)); - break; - } - - p.setBrush(Qt::NoBrush); -} - -// adjusts keyframe's internal time (in clip time) to timeline time -long adjust_row_keyframe(NodeIO* row, long time, long visible_in) { - return 0; - /* FIXME - return time - - row->GetParentEffect()->parent_clip->clip_in() - + (row->GetParentEffect()->parent_clip->timeline_in() - visible_in); - */ -} +/*** + + 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 "keyframedrawing.h" + +#include "nodes/oldeffectnode.h" +#include "timeline/clip.h" + +#define KEYFRAME_POINT_COUNT 4 + +// routine for drawing a keyframe onscreen +void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r, int g, int b) { + if (darker) { + r *= 0.625; + g *= 0.625; + b *= 0.625; + } + p.setPen(QColor(0, 0, 0)); + p.setBrush(QColor(r, g, b)); + + switch (type) { + case EFFECT_KEYFRAME_LINEAR: + { + QPoint points[KEYFRAME_POINT_COUNT] = {QPoint(x-KEYFRAME_SIZE, y), QPoint(x, y-KEYFRAME_SIZE), QPoint(x+KEYFRAME_SIZE, y), QPoint(x, y+KEYFRAME_SIZE)}; + p.drawPolygon(points, KEYFRAME_POINT_COUNT); + } + break; + case EFFECT_KEYFRAME_BEZIER: + p.drawEllipse(QPoint(x, y), KEYFRAME_SIZE, KEYFRAME_SIZE); + break; + case EFFECT_KEYFRAME_HOLD: + p.drawRect(QRect(x - KEYFRAME_SIZE, y - KEYFRAME_SIZE, KEYFRAME_SIZE*2, KEYFRAME_SIZE*2)); + break; + } + + p.setBrush(Qt::NoBrush); +} + +// adjusts keyframe's internal time (in clip time) to timeline time +long adjust_row_keyframe(NodeIO* row, long time, long visible_in) { + return 0; + /* FIXME + return time + - row->GetParentEffect()->parent_clip->clip_in() + + (row->GetParentEffect()->parent_clip->timeline_in() - visible_in); + */ +} diff --git a/ui/keyframedrawing.h b/ui/keyframedrawing.h index 9d5eff617..319704ddf 100644 --- a/ui/keyframedrawing.h +++ b/ui/keyframedrawing.h @@ -1,34 +1,34 @@ -/*** - - 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 KEYFRAMEDRAWING_H -#define KEYFRAMEDRAWING_H - -#include - -#define KEYFRAME_SIZE 6 -#define KEYFRAME_COLOR 160 - -class NodeIO; - -void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r = KEYFRAME_COLOR, int g = KEYFRAME_COLOR, int b = KEYFRAME_COLOR); -long adjust_row_keyframe(NodeIO* row, long time, long visible_in); - -#endif // KEYFRAMEDRAWING_H +/*** + + 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 KEYFRAMEDRAWING_H +#define KEYFRAMEDRAWING_H + +#include + +#define KEYFRAME_SIZE 6 +#define KEYFRAME_COLOR 160 + +class NodeIO; + +void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r = KEYFRAME_COLOR, int g = KEYFRAME_COLOR, int b = KEYFRAME_COLOR); +long adjust_row_keyframe(NodeIO* row, long time, long visible_in); + +#endif // KEYFRAMEDRAWING_H diff --git a/ui/keyframenavigator.cpp b/ui/keyframenavigator.cpp index 6a50d4aeb..0ddd5b33b 100644 --- a/ui/keyframenavigator.cpp +++ b/ui/keyframenavigator.cpp @@ -1,98 +1,98 @@ -/*** - - 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 "keyframenavigator.h" - -#include -#include -#include -#include -#include - -#include "ui/icons.h" - -KeyframeNavigator::KeyframeNavigator(QWidget *parent, bool addLeftPad) : QWidget(parent) { - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); - - key_controls = new QHBoxLayout(this); - key_controls->setSpacing(0); - key_controls->setMargin(0); - - if (addLeftPad) { - key_controls->addStretch(); - } - - QSizePolicy button_size_policy; - button_size_policy.setRetainSizeWhenHidden(true); - - left_key_nav = new QPushButton(this); - left_key_nav->setSizePolicy(button_size_policy); - left_key_nav->setIcon(olive::icon::LeftArrow); - left_key_nav->setIconSize(left_key_nav->iconSize()*0.5); - left_key_nav->setVisible(false); - key_controls->addWidget(left_key_nav); - connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_previous_key())); - connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - - key_addremove = new QPushButton(this); - key_addremove->setSizePolicy(button_size_policy); - key_addremove->setIcon(olive::icon::Diamond); - key_addremove->setIconSize(key_addremove->iconSize()*0.5); - key_addremove->setVisible(false); - key_controls->addWidget(key_addremove); - connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(toggle_key())); - connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - - right_key_nav = new QPushButton(this); - right_key_nav->setSizePolicy(button_size_policy); - right_key_nav->setIcon(olive::icon::RightArrow); - right_key_nav->setIconSize(right_key_nav->iconSize()*0.5); - right_key_nav->setVisible(false); - key_controls->addWidget(right_key_nav); - connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_next_key())); - connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - - keyframe_enable = new QPushButton(this); - keyframe_enable->setIcon(olive::icon::Clock); - keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); - keyframe_enable->setIconSize(keyframe_enable->iconSize()*0.75); - keyframe_enable->setCheckable(true); - keyframe_enable->setToolTip(tr("Enable Keyframes")); - connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(keyframe_enabled_changed(bool))); - connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool))); - connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - key_controls->addWidget(keyframe_enable); -} - -KeyframeNavigator::~KeyframeNavigator() {} - -void KeyframeNavigator::enable_keyframes(bool b) { - keyframe_enable->setChecked(b); -} - -void KeyframeNavigator::enable_keyframe_toggle(bool b) { - keyframe_enable->setVisible(b); -} - -void KeyframeNavigator::keyframe_ui_enabled(bool enabled) { - left_key_nav->setVisible(enabled); - key_addremove->setVisible(enabled); - right_key_nav->setVisible(enabled); -} +/*** + + 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 "keyframenavigator.h" + +#include +#include +#include +#include +#include + +#include "ui/icons.h" + +KeyframeNavigator::KeyframeNavigator(QWidget *parent, bool addLeftPad) : QWidget(parent) { + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + + key_controls = new QHBoxLayout(this); + key_controls->setSpacing(0); + key_controls->setMargin(0); + + if (addLeftPad) { + key_controls->addStretch(); + } + + QSizePolicy button_size_policy; + button_size_policy.setRetainSizeWhenHidden(true); + + left_key_nav = new QPushButton(this); + left_key_nav->setSizePolicy(button_size_policy); + left_key_nav->setIcon(olive::icon::LeftArrow); + left_key_nav->setIconSize(left_key_nav->iconSize()*0.5); + left_key_nav->setVisible(false); + key_controls->addWidget(left_key_nav); + connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_previous_key())); + connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); + + key_addremove = new QPushButton(this); + key_addremove->setSizePolicy(button_size_policy); + key_addremove->setIcon(olive::icon::Diamond); + key_addremove->setIconSize(key_addremove->iconSize()*0.5); + key_addremove->setVisible(false); + key_controls->addWidget(key_addremove); + connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(toggle_key())); + connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); + + right_key_nav = new QPushButton(this); + right_key_nav->setSizePolicy(button_size_policy); + right_key_nav->setIcon(olive::icon::RightArrow); + right_key_nav->setIconSize(right_key_nav->iconSize()*0.5); + right_key_nav->setVisible(false); + key_controls->addWidget(right_key_nav); + connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_next_key())); + connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); + + keyframe_enable = new QPushButton(this); + keyframe_enable->setIcon(olive::icon::Clock); + keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + keyframe_enable->setIconSize(keyframe_enable->iconSize()*0.75); + keyframe_enable->setCheckable(true); + keyframe_enable->setToolTip(tr("Enable Keyframes")); + connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(keyframe_enabled_changed(bool))); + connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool))); + connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); + key_controls->addWidget(keyframe_enable); +} + +KeyframeNavigator::~KeyframeNavigator() {} + +void KeyframeNavigator::enable_keyframes(bool b) { + keyframe_enable->setChecked(b); +} + +void KeyframeNavigator::enable_keyframe_toggle(bool b) { + keyframe_enable->setVisible(b); +} + +void KeyframeNavigator::keyframe_ui_enabled(bool enabled) { + left_key_nav->setVisible(enabled); + key_addremove->setVisible(enabled); + right_key_nav->setVisible(enabled); +} diff --git a/ui/keyframenavigator.h b/ui/keyframenavigator.h index 85d718519..ec0842bfc 100644 --- a/ui/keyframenavigator.h +++ b/ui/keyframenavigator.h @@ -1,55 +1,55 @@ -/*** - - 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 KEYFRAMENAVIGATOR_H -#define KEYFRAMENAVIGATOR_H - -#include - -class QHBoxLayout; -class QPushButton; - -class KeyframeNavigator : public QWidget -{ - Q_OBJECT -public: - KeyframeNavigator(QWidget* parent = nullptr, bool addLeftPad = true); - ~KeyframeNavigator(); - - void enable_keyframe_toggle(bool); -public slots: - void enable_keyframes(bool); -signals: - void goto_previous_key(); - void toggle_key(); - void goto_next_key(); - void keyframe_enabled_changed(bool); - void clicked(); -private slots: - void keyframe_ui_enabled(bool); -private: - QHBoxLayout* key_controls; - QPushButton* left_key_nav; - QPushButton* key_addremove; - QPushButton* right_key_nav; - QPushButton* keyframe_enable; -}; - -#endif // KEYFRAMENAVIGATOR_H +/*** + + 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 KEYFRAMENAVIGATOR_H +#define KEYFRAMENAVIGATOR_H + +#include + +class QHBoxLayout; +class QPushButton; + +class KeyframeNavigator : public QWidget +{ + Q_OBJECT +public: + KeyframeNavigator(QWidget* parent = nullptr, bool addLeftPad = true); + ~KeyframeNavigator(); + + void enable_keyframe_toggle(bool); +public slots: + void enable_keyframes(bool); +signals: + void goto_previous_key(); + void toggle_key(); + void goto_next_key(); + void keyframe_enabled_changed(bool); + void clicked(); +private slots: + void keyframe_ui_enabled(bool); +private: + QHBoxLayout* key_controls; + QPushButton* left_key_nav; + QPushButton* key_addremove; + QPushButton* right_key_nav; + QPushButton* keyframe_enable; +}; + +#endif // KEYFRAMENAVIGATOR_H diff --git a/ui/keyframeview.cpp b/ui/keyframeview.cpp index 594f34cbc..3e7ecac77 100644 --- a/ui/keyframeview.cpp +++ b/ui/keyframeview.cpp @@ -1,450 +1,450 @@ -/*** - - 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 "keyframeview.h" - -#include -#include - -#include "nodes/oldeffectnode.h" -#include "ui/collapsiblewidget.h" -#include "panels/panels.h" -#include "timeline/clip.h" -#include "panels/timeline.h" -#include "ui/timelineheader.h" -#include "undo/undo.h" -#include "undo/undostack.h" -#include "panels/viewer.h" -#include "ui/viewerwidget.h" -#include "timeline/sequence.h" -#include "panels/grapheditor.h" -#include "ui/keyframedrawing.h" -#include "ui/clickablelabel.h" -#include "ui/resizablescrollbar.h" -#include "ui/rectangleselect.h" -#include "effects/keyframe.h" -#include "ui/graphview.h" -#include "ui/menu.h" -#include "global/math.h" - -KeyframeView::KeyframeView(QWidget *parent) : - QWidget(parent), - visible_in(0), - visible_out(0), - mousedown(false), - dragging(false), - keys_selected(false), - select_rect(false), - x_scroll(0), - y_scroll(0), - scroll_drag(false) -{ - setFocusPolicy(Qt::ClickFocus); - setMouseTracking(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); -} - -void KeyframeView::SetEffects(const QVector &open_effects) -{ - open_effects_ = open_effects; -} - -void KeyframeView::show_context_menu(const QPoint& pos) { - if (selected_fields.size() > 0) { - Menu menu(this); - - QAction* linear = menu.addAction(tr("Linear")); - linear->setData(EFFECT_KEYFRAME_LINEAR); - QAction* bezier = menu.addAction(tr("Bezier")); - bezier->setData(EFFECT_KEYFRAME_BEZIER); - QAction* hold = menu.addAction(tr("Hold")); - hold->setData(EFFECT_KEYFRAME_HOLD); - menu.addSeparator(); - menu.addAction("Graph Editor"); - - connect(&menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_set_key_type(QAction*))); - menu.exec(mapToGlobal(pos)); - } -} - -void KeyframeView::menu_set_key_type(QAction* a) { - if (a->data().isNull()) { - // load graph editor - panel_graph_editor->show(); - } else { - ComboAction* ca = new ComboAction(); - for (int i=0;iappend(new SetInt(&f->keyframes[selected_keyframes.at(i)].type, a->data().toInt())); - } - olive::undo_stack.push(ca); - update_ui(false); - } -} - -void KeyframeView::paintEvent(QPaintEvent*) { - QPainter p(this); - - rowY.clear(); - rows.clear(); - - if (!open_effects_.isEmpty()) { - visible_in = LONG_MAX; - visible_out = 0; - - for (int i=0;iGetEffect()->parent_clip; - visible_in = qMin(visible_in, c->timeline_in()); - visible_out = qMax(visible_out, c->timeline_out()); - } - - for (int j=0;jGetEffect(); - - if (container->IsExpanded()) { - for (int j=0;jParameterCount();j++) { - NodeIO* row = e->Parameter(j); - - int keyframe_y = container->GetRowY(j, this); - - QVector key_times; - - for (int l=0;lFieldCount();l++) { - EffectField* f = row->Field(l); - for (int k=0;kkeyframes.size();k++) { - if (!key_times.contains(f->keyframes.at(k).time)) { - bool keyframe_selected = keyframeIsSelected(f, k); - long keyframe_frame = adjust_row_keyframe(row, f->keyframes.at(k).time, visible_in); - - // see if any other keyframes have this time - int appearances = 0; - for (int m=0;mFieldCount();m++) { - EffectField* compf = row->Field(m); - for (int n=0;nkeyframes.size();n++) { - if (f->keyframes.at(k).time == compf->keyframes.at(n).time) { - appearances++; - } - } - } - - if (appearances != row->FieldCount()) { - QColor cc = get_curve_color(l, row->FieldCount()); - draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected, cc.red(), cc.green(), cc.blue()); - } else { - draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected); - } - - key_times.append(f->keyframes.at(k).time); - } - } - } - - rows.append(row); - rowY.append(keyframe_y); - } - } - } - - int max_width = getScreenPointFromFrame(panel_effect_controls->zoom, visible_out - visible_in); - if (max_width < width()) { - p.fillRect(QRect(max_width, 0, width(), height()), QColor(0, 0, 0, 64)); - } - panel_effect_controls->horizontalScrollBar->setMaximum(qMax(max_width - width(), 0)); - header->set_visible_in(visible_in); - - 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); - } - p.drawLine(playhead_x, 0, playhead_x, height()); - } - - if (select_rect) { - olive::ui::DrawSelectionRectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); - } - - /*if (mouseover && mouseover_row < rowY.size()) { - draw_keyframe(p, getScreenPointFromFrame(panel_effect_controls->zoom, mouseover_frame - visible_in), rowY.at(mouseover_row), true); - }*/ -} - -void KeyframeView::wheelEvent(QWheelEvent *e) { - emit wheel_event_signal(e); -} - -bool KeyframeView::keyframeIsSelected(EffectField *field, int keyframe) { - for (int i=0;iupdate_panel(); - update(); -} - -void KeyframeView::delete_selected_keyframes() { - delete_keyframes(selected_fields, selected_keyframes); -} - -void KeyframeView::set_x_scroll(int s) { - x_scroll = s; - update_keys(); -} - -void KeyframeView::set_y_scroll(int s) { - y_scroll = s; - update_keys(); -} - -void KeyframeView::resize_move(double d) { - panel_effect_controls->zoom *= d; - header->update_zoom(panel_effect_controls->zoom); - update(); -} - -void KeyframeView::mousePressEvent(QMouseEvent *event) { - rect_select_x = event->x(); - rect_select_y = event->y(); - rect_select_w = 0; - rect_select_h = 0; - - if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) { - scroll_drag = true; - return; - } - - old_key_vals.clear(); - - int mouse_x = event->x() + x_scroll; - int mouse_y = event->y(); - int row_index = -1; - int field_index = -1; - int keyframe_index = -1; - long frame_diff = 0; - long frame_min = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x-KEYFRAME_SIZE); - drag_frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x); - long frame_max = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x+KEYFRAME_SIZE); - for (int i=0;i rowY.at(i)-KEYFRAME_SIZE-KEYFRAME_SIZE && mouse_y < rowY.at(i)+KEYFRAME_SIZE+KEYFRAME_SIZE) { - NodeIO* row = rows.at(i); - - row->FocusRow(); - - for (int k=0;kFieldCount();k++) { - EffectField* f = row->Field(k); - for (int j=0;jkeyframes.size();j++) { - /* FIXME - long eval_keyframe_time = f->keyframes.at(j).time-row->GetParentEffect()->parent_clip->clip_in()+(row->GetParentEffect()->parent_clip->timeline_in()-visible_in); - if (eval_keyframe_time >= frame_min && eval_keyframe_time <= frame_max) { - long eval_frame_diff = qAbs(eval_keyframe_time - drag_frame_start); - if (keyframe_index == -1 || eval_frame_diff < frame_diff) { - row_index = i; - field_index = k; - keyframe_index = j; - frame_diff = eval_frame_diff; - } - } - */ - } - } - break; - } - } - bool already_selected = false; - keys_selected = false; - if (keyframe_index > -1) { - already_selected = keyframeIsSelected(rows.at(row_index)->Field(field_index), keyframe_index); - } else { - select_rect = true; - } - if (!already_selected) { - if (!(event->modifiers() & Qt::ShiftModifier)) { - selected_fields.clear(); - selected_keyframes.clear(); - } - if (keyframe_index > -1) { - selected_fields.append(rows.at(row_index)->Field(field_index)); - selected_keyframes.append(keyframe_index); - - // find other field with keyframes at the same time - long comp_time = rows.at(row_index)->Field(field_index)->keyframes.at(keyframe_index).time; - for (int i=0;iFieldCount();i++) { - if (i != field_index) { - EffectField* f = rows.at(row_index)->Field(i); - for (int j=0;jkeyframes.size();j++) { - if (f->keyframes.at(j).time == comp_time) { - selected_fields.append(f); - selected_keyframes.append(j); - } - } - } - } - } - } - - if (selected_fields.size() > 0) { - for (int i=0;ikeyframes.at(selected_keyframes.at(i)).time); - } - keys_selected = true; - } - - rect_select_offset = selected_fields.size(); - - update_keys(); - - if (event->button() == Qt::LeftButton) { - mousedown = true; - } -} - -void KeyframeView::mouseMoveEvent(QMouseEvent* event) { - if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { - setCursor(Qt::OpenHandCursor); - } else { - unsetCursor(); - } - if (scroll_drag) { - panel_effect_controls->horizontalScrollBar->setValue(panel_effect_controls->horizontalScrollBar->value() + rect_select_x - event->pos().x()); - panel_effect_controls->verticalScrollBar->setValue(panel_effect_controls->verticalScrollBar->value() + rect_select_y - event->pos().y()); - rect_select_x = event->pos().x(); - rect_select_y = event->pos().y(); - } else if (mousedown) { - int mouse_x = event->x() + x_scroll; - - long current_frame = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x); - panel_effect_controls->scroll_to_frame(current_frame + visible_in); - - if (select_rect) { - // do a rect select - selected_fields.resize(rect_select_offset); - selected_keyframes.resize(rect_select_offset); - - rect_select_w = event->x() - rect_select_x; - rect_select_h = event->y() - rect_select_y; - - int min_row = qMin(rect_select_y, event->y())-KEYFRAME_SIZE; - int max_row = qMax(rect_select_y, event->y())+KEYFRAME_SIZE; - - long frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, rect_select_x+x_scroll); - long min_frame = qMin(frame_start, current_frame)-KEYFRAME_SIZE; - long max_frame = qMax(frame_start, current_frame)+KEYFRAME_SIZE; - - for (int i=0;i= min_row && rowY.at(i) <= max_row) { - NodeIO* row = rows.at(i); - for (int k=0;kFieldCount();k++) { - EffectField* field = row->Field(k); - for (int j=0;jkeyframes.size();j++) { - long keyframe_frame = adjust_row_keyframe(row, field->keyframes.at(j).time, visible_in); - if (!keyframeIsSelected(field, j) && keyframe_frame >= min_frame && keyframe_frame <= max_frame) { - selected_fields.append(field); - selected_keyframes.append(j); - } - } - } - } - } - - update_keys(); - } else if (keys_selected) { - // move keyframes - long frame_diff = current_frame - drag_frame_start; - - // snapping to playhead - 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 (olive::timeline::SnapToPoint(c->track()->sequence()->playhead, &key_eval, header->get_zoom())) { - frame_diff += (key_eval - key_time); - break; - } - */ - } - } - - // validate frame_diff (make sure no keyframes overlap each other) - for (int i=0;ikeyframes.size();j++) { - while (!keyframeIsSelected(field, j) && field->keyframes.at(j).time == eval_key + frame_diff) { - if (last_frame_diff > frame_diff) { - frame_diff++; - olive::timeline::snapped = false; - } else { - frame_diff--; - olive::timeline::snapped = false; - } - } - } - } - - // apply frame_diffs - for (int i=0;ikeyframes[selected_keyframes.at(i)].time = old_key_vals.at(i) + frame_diff; - } - - last_frame_diff = frame_diff; - - dragging = true; - - update_ui(false); - } - } -} - -void KeyframeView::mouseReleaseEvent(QMouseEvent*) { - if (dragging) { - ComboAction* ca = new ComboAction(); - for (int i=0;iappend(new SetDouble( - &selected_fields.at(i)->keyframes[selected_keyframes.at(i)].time, - old_key_vals.at(i), - selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time - )); - } - olive::undo_stack.push(ca); - } - - select_rect = false; - dragging = false; - mousedown = false; - scroll_drag = false; - olive::timeline::snapped = false; - update_ui(false); -} +/*** + + 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 "keyframeview.h" + +#include +#include + +#include "nodes/oldeffectnode.h" +#include "ui/collapsiblewidget.h" +#include "panels/panels.h" +#include "timeline/clip.h" +#include "panels/timeline.h" +#include "ui/timelineheader.h" +#include "undo/undo.h" +#include "undo/undostack.h" +#include "panels/viewer.h" +#include "ui/viewerwidget.h" +#include "timeline/sequence.h" +#include "panels/grapheditor.h" +#include "ui/keyframedrawing.h" +#include "ui/clickablelabel.h" +#include "ui/resizablescrollbar.h" +#include "ui/rectangleselect.h" +#include "effects/keyframe.h" +#include "ui/graphview.h" +#include "ui/menu.h" +#include "global/math.h" + +KeyframeView::KeyframeView(QWidget *parent) : + QWidget(parent), + visible_in(0), + visible_out(0), + mousedown(false), + dragging(false), + keys_selected(false), + select_rect(false), + x_scroll(0), + y_scroll(0), + scroll_drag(false) +{ + setFocusPolicy(Qt::ClickFocus); + setMouseTracking(true); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); +} + +void KeyframeView::SetEffects(const QVector &open_effects) +{ + open_effects_ = open_effects; +} + +void KeyframeView::show_context_menu(const QPoint& pos) { + if (selected_fields.size() > 0) { + Menu menu(this); + + QAction* linear = menu.addAction(tr("Linear")); + linear->setData(EFFECT_KEYFRAME_LINEAR); + QAction* bezier = menu.addAction(tr("Bezier")); + bezier->setData(EFFECT_KEYFRAME_BEZIER); + QAction* hold = menu.addAction(tr("Hold")); + hold->setData(EFFECT_KEYFRAME_HOLD); + menu.addSeparator(); + menu.addAction("Graph Editor"); + + connect(&menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_set_key_type(QAction*))); + menu.exec(mapToGlobal(pos)); + } +} + +void KeyframeView::menu_set_key_type(QAction* a) { + if (a->data().isNull()) { + // load graph editor + panel_graph_editor->show(); + } else { + ComboAction* ca = new ComboAction(); + for (int i=0;iappend(new SetInt(&f->keyframes[selected_keyframes.at(i)].type, a->data().toInt())); + } + olive::undo_stack.push(ca); + update_ui(false); + } +} + +void KeyframeView::paintEvent(QPaintEvent*) { + QPainter p(this); + + rowY.clear(); + rows.clear(); + + if (!open_effects_.isEmpty()) { + visible_in = LONG_MAX; + visible_out = 0; + + for (int i=0;iGetEffect()->parent_clip; + visible_in = qMin(visible_in, c->timeline_in()); + visible_out = qMax(visible_out, c->timeline_out()); + } + + for (int j=0;jGetEffect(); + + if (container->IsExpanded()) { + for (int j=0;jParameterCount();j++) { + NodeIO* row = e->Parameter(j); + + int keyframe_y = container->GetRowY(j, this); + + QVector key_times; + + for (int l=0;lFieldCount();l++) { + EffectField* f = row->Field(l); + for (int k=0;kkeyframes.size();k++) { + if (!key_times.contains(f->keyframes.at(k).time)) { + bool keyframe_selected = keyframeIsSelected(f, k); + long keyframe_frame = adjust_row_keyframe(row, f->keyframes.at(k).time, visible_in); + + // see if any other keyframes have this time + int appearances = 0; + for (int m=0;mFieldCount();m++) { + EffectField* compf = row->Field(m); + for (int n=0;nkeyframes.size();n++) { + if (f->keyframes.at(k).time == compf->keyframes.at(n).time) { + appearances++; + } + } + } + + if (appearances != row->FieldCount()) { + QColor cc = get_curve_color(l, row->FieldCount()); + draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected, cc.red(), cc.green(), cc.blue()); + } else { + draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected); + } + + key_times.append(f->keyframes.at(k).time); + } + } + } + + rows.append(row); + rowY.append(keyframe_y); + } + } + } + + int max_width = getScreenPointFromFrame(panel_effect_controls->zoom, visible_out - visible_in); + if (max_width < width()) { + p.fillRect(QRect(max_width, 0, width(), height()), QColor(0, 0, 0, 64)); + } + panel_effect_controls->horizontalScrollBar->setMaximum(qMax(max_width - width(), 0)); + header->set_visible_in(visible_in); + + 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); + } + p.drawLine(playhead_x, 0, playhead_x, height()); + } + + if (select_rect) { + olive::ui::DrawSelectionRectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); + } + + /*if (mouseover && mouseover_row < rowY.size()) { + draw_keyframe(p, getScreenPointFromFrame(panel_effect_controls->zoom, mouseover_frame - visible_in), rowY.at(mouseover_row), true); + }*/ +} + +void KeyframeView::wheelEvent(QWheelEvent *e) { + emit wheel_event_signal(e); +} + +bool KeyframeView::keyframeIsSelected(EffectField *field, int keyframe) { + for (int i=0;iupdate_panel(); + update(); +} + +void KeyframeView::delete_selected_keyframes() { + delete_keyframes(selected_fields, selected_keyframes); +} + +void KeyframeView::set_x_scroll(int s) { + x_scroll = s; + update_keys(); +} + +void KeyframeView::set_y_scroll(int s) { + y_scroll = s; + update_keys(); +} + +void KeyframeView::resize_move(double d) { + panel_effect_controls->zoom *= d; + header->update_zoom(panel_effect_controls->zoom); + update(); +} + +void KeyframeView::mousePressEvent(QMouseEvent *event) { + rect_select_x = event->x(); + rect_select_y = event->y(); + rect_select_w = 0; + rect_select_h = 0; + + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) { + scroll_drag = true; + return; + } + + old_key_vals.clear(); + + int mouse_x = event->x() + x_scroll; + int mouse_y = event->y(); + int row_index = -1; + int field_index = -1; + int keyframe_index = -1; + long frame_diff = 0; + long frame_min = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x-KEYFRAME_SIZE); + drag_frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x); + long frame_max = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x+KEYFRAME_SIZE); + for (int i=0;i rowY.at(i)-KEYFRAME_SIZE-KEYFRAME_SIZE && mouse_y < rowY.at(i)+KEYFRAME_SIZE+KEYFRAME_SIZE) { + NodeIO* row = rows.at(i); + + row->FocusRow(); + + for (int k=0;kFieldCount();k++) { + EffectField* f = row->Field(k); + for (int j=0;jkeyframes.size();j++) { + /* FIXME + long eval_keyframe_time = f->keyframes.at(j).time-row->GetParentEffect()->parent_clip->clip_in()+(row->GetParentEffect()->parent_clip->timeline_in()-visible_in); + if (eval_keyframe_time >= frame_min && eval_keyframe_time <= frame_max) { + long eval_frame_diff = qAbs(eval_keyframe_time - drag_frame_start); + if (keyframe_index == -1 || eval_frame_diff < frame_diff) { + row_index = i; + field_index = k; + keyframe_index = j; + frame_diff = eval_frame_diff; + } + } + */ + } + } + break; + } + } + bool already_selected = false; + keys_selected = false; + if (keyframe_index > -1) { + already_selected = keyframeIsSelected(rows.at(row_index)->Field(field_index), keyframe_index); + } else { + select_rect = true; + } + if (!already_selected) { + if (!(event->modifiers() & Qt::ShiftModifier)) { + selected_fields.clear(); + selected_keyframes.clear(); + } + if (keyframe_index > -1) { + selected_fields.append(rows.at(row_index)->Field(field_index)); + selected_keyframes.append(keyframe_index); + + // find other field with keyframes at the same time + long comp_time = rows.at(row_index)->Field(field_index)->keyframes.at(keyframe_index).time; + for (int i=0;iFieldCount();i++) { + if (i != field_index) { + EffectField* f = rows.at(row_index)->Field(i); + for (int j=0;jkeyframes.size();j++) { + if (f->keyframes.at(j).time == comp_time) { + selected_fields.append(f); + selected_keyframes.append(j); + } + } + } + } + } + } + + if (selected_fields.size() > 0) { + for (int i=0;ikeyframes.at(selected_keyframes.at(i)).time); + } + keys_selected = true; + } + + rect_select_offset = selected_fields.size(); + + update_keys(); + + if (event->button() == Qt::LeftButton) { + mousedown = true; + } +} + +void KeyframeView::mouseMoveEvent(QMouseEvent* event) { + if (olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { + setCursor(Qt::OpenHandCursor); + } else { + unsetCursor(); + } + if (scroll_drag) { + panel_effect_controls->horizontalScrollBar->setValue(panel_effect_controls->horizontalScrollBar->value() + rect_select_x - event->pos().x()); + panel_effect_controls->verticalScrollBar->setValue(panel_effect_controls->verticalScrollBar->value() + rect_select_y - event->pos().y()); + rect_select_x = event->pos().x(); + rect_select_y = event->pos().y(); + } else if (mousedown) { + int mouse_x = event->x() + x_scroll; + + long current_frame = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x); + panel_effect_controls->scroll_to_frame(current_frame + visible_in); + + if (select_rect) { + // do a rect select + selected_fields.resize(rect_select_offset); + selected_keyframes.resize(rect_select_offset); + + rect_select_w = event->x() - rect_select_x; + rect_select_h = event->y() - rect_select_y; + + int min_row = qMin(rect_select_y, event->y())-KEYFRAME_SIZE; + int max_row = qMax(rect_select_y, event->y())+KEYFRAME_SIZE; + + long frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, rect_select_x+x_scroll); + long min_frame = qMin(frame_start, current_frame)-KEYFRAME_SIZE; + long max_frame = qMax(frame_start, current_frame)+KEYFRAME_SIZE; + + for (int i=0;i= min_row && rowY.at(i) <= max_row) { + NodeIO* row = rows.at(i); + for (int k=0;kFieldCount();k++) { + EffectField* field = row->Field(k); + for (int j=0;jkeyframes.size();j++) { + long keyframe_frame = adjust_row_keyframe(row, field->keyframes.at(j).time, visible_in); + if (!keyframeIsSelected(field, j) && keyframe_frame >= min_frame && keyframe_frame <= max_frame) { + selected_fields.append(field); + selected_keyframes.append(j); + } + } + } + } + } + + update_keys(); + } else if (keys_selected) { + // move keyframes + long frame_diff = current_frame - drag_frame_start; + + // snapping to playhead + 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 (olive::timeline::SnapToPoint(c->track()->sequence()->playhead, &key_eval, header->get_zoom())) { + frame_diff += (key_eval - key_time); + break; + } + */ + } + } + + // validate frame_diff (make sure no keyframes overlap each other) + for (int i=0;ikeyframes.size();j++) { + while (!keyframeIsSelected(field, j) && field->keyframes.at(j).time == eval_key + frame_diff) { + if (last_frame_diff > frame_diff) { + frame_diff++; + olive::timeline::snapped = false; + } else { + frame_diff--; + olive::timeline::snapped = false; + } + } + } + } + + // apply frame_diffs + for (int i=0;ikeyframes[selected_keyframes.at(i)].time = old_key_vals.at(i) + frame_diff; + } + + last_frame_diff = frame_diff; + + dragging = true; + + update_ui(false); + } + } +} + +void KeyframeView::mouseReleaseEvent(QMouseEvent*) { + if (dragging) { + ComboAction* ca = new ComboAction(); + for (int i=0;iappend(new SetDouble( + &selected_fields.at(i)->keyframes[selected_keyframes.at(i)].time, + old_key_vals.at(i), + selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time + )); + } + olive::undo_stack.push(ca); + } + + select_rect = false; + dragging = false; + mousedown = false; + scroll_drag = false; + olive::timeline::snapped = false; + update_ui(false); +} diff --git a/ui/keyframeview.h b/ui/keyframeview.h index 783e17d22..2211afbb0 100644 --- a/ui/keyframeview.h +++ b/ui/keyframeview.h @@ -1,92 +1,92 @@ -/*** - - 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 KEYFRAMEVIEW_H -#define KEYFRAMEVIEW_H - -#include -#include - -#include "ui/effectui.h" - -class Clip; -class OldEffectNode; -class NodeIO; -class EffectField; -class TimelineHeader; - -class KeyframeView : public QWidget { - Q_OBJECT -public: - KeyframeView(QWidget* parent = nullptr); - - void SetEffects(const QVector& open_effects); - - void delete_selected_keyframes(); - - TimelineHeader* header; - - long visible_in; - long visible_out; -signals: - void wheel_event_signal(QWheelEvent*); -public slots: - void set_x_scroll(int); - void set_y_scroll(int); - void resize_move(double d); -private: - QVector open_effects_; - - QVector selected_fields; - QVector selected_keyframes; - QVector rowY; - QVector rows; - QVector old_key_vals; - void mousePressEvent(QMouseEvent* event); - void mouseMoveEvent(QMouseEvent* event); - void mouseReleaseEvent(QMouseEvent *event); - void paintEvent(QPaintEvent *event); - void wheelEvent(QWheelEvent* e); - bool mousedown; - bool dragging; - bool keys_selected; - bool select_rect; - bool scroll_drag; - - bool keyframeIsSelected(EffectField *field, int keyframe); - - long drag_frame_start; - long last_frame_diff; - int rect_select_x; - int rect_select_y; - int rect_select_w; - int rect_select_h; - int rect_select_offset; - - int x_scroll; - int y_scroll; - - void update_keys(); -private slots: - void show_context_menu(const QPoint& pos); - void menu_set_key_type(QAction*); -}; - -#endif // KEYFRAMEVIEW_H +/*** + + 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 KEYFRAMEVIEW_H +#define KEYFRAMEVIEW_H + +#include +#include + +#include "ui/effectui.h" + +class Clip; +class OldEffectNode; +class NodeIO; +class EffectField; +class TimelineHeader; + +class KeyframeView : public QWidget { + Q_OBJECT +public: + KeyframeView(QWidget* parent = nullptr); + + void SetEffects(const QVector& open_effects); + + void delete_selected_keyframes(); + + TimelineHeader* header; + + long visible_in; + long visible_out; +signals: + void wheel_event_signal(QWheelEvent*); +public slots: + void set_x_scroll(int); + void set_y_scroll(int); + void resize_move(double d); +private: + QVector open_effects_; + + QVector selected_fields; + QVector selected_keyframes; + QVector rowY; + QVector rows; + QVector old_key_vals; + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent *event); + void paintEvent(QPaintEvent *event); + void wheelEvent(QWheelEvent* e); + bool mousedown; + bool dragging; + bool keys_selected; + bool select_rect; + bool scroll_drag; + + bool keyframeIsSelected(EffectField *field, int keyframe); + + long drag_frame_start; + long last_frame_diff; + int rect_select_x; + int rect_select_y; + int rect_select_w; + int rect_select_h; + int rect_select_offset; + + int x_scroll; + int y_scroll; + + void update_keys(); +private slots: + void show_context_menu(const QPoint& pos); + void menu_set_key_type(QAction*); +}; + +#endif // KEYFRAMEVIEW_H diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 7828cfc2f..a32f4dd7b 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -1,1230 +1,1230 @@ -/*** - - 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 "mainwindow.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "global/global.h" -#include "ui/menuhelper.h" -#include "project/projectelements.h" -#include "timeline/clip.h" -#include "global/config.h" -#include "global/path.h" -#include "global/debug.h" -#include "project/proxygenerator.h" -#include "project/projectfilter.h" -#include "ui/sourcetable.h" -#include "ui/viewerwidget.h" -#include "ui/sourceiconview.h" -#include "ui/timelineheader.h" -#include "ui/icons.h" -#include "ui/cursors.h" -#include "ui/focusfilter.h" -#include "panels/panels.h" -#include "dialogs/debugdialog.h" -#include "rendering/audio.h" -#include "rendering/renderfunctions.h" -#include "undo/undostack.h" -#include "effects/effectloaders.h" - -MainWindow* olive::MainWindow; - -void MainWindow::setup_layout(bool reset) { - // load panels from file - if (!reset) { - QFile panel_config(get_config_dir().filePath("layout")); - if (panel_config.exists() && panel_config.open(QFile::ReadOnly)) { - - // default to resetting unless we find layout data in the XML file - reset = true; - - // read XML layout file - QXmlStreamReader stream(&panel_config); - - // loop through XML for all data - while (!stream.atEnd()) { - stream.readNext(); - - if (stream.name() == "panels" && stream.isStartElement()) { - - // element contains MainWindow layout data to restore - stream.readNext(); - restoreState(QByteArray::fromBase64(stream.text().toUtf8()), 0); - reset = false; - - } else if (stream.name() == "panel" && stream.isStartElement()) { - - // element contains layout data specific to a panel, we'll find the panel and load it - - // get panel name from XML attribute - QString panel_name; - const QXmlStreamAttributes& attributes = stream.attributes(); - for (int i=0;iobjectName() == panel_name) { - - // found the panel, so we can load its state - stream.readNext(); - panel->LoadLayoutState(QByteArray::fromBase64(stream.text().toUtf8())); - - // we found it, no more need to loop through panels - found_panel = true; - - break; - - } - - } - - if (!found_panel) { - qWarning() << "Panel specified in layout data doesn't exist. Layout wasn't loaded."; - } - - } - - } - - } - - panel_config.close(); - } else { - reset = true; - } - } - - if (reset) { - // remove all panels from the main window - for (int i=0;iraise(); - addDockWidget(Qt::TopDockWidgetArea, panel_sequence_viewer); - addDockWidget(Qt::BottomDockWidgetArea, panel_timeline.first()); - - panel_project.first()->show(); - panel_effect_controls->show(); - panel_footage_viewer->show(); - panel_sequence_viewer->show(); - panel_timeline.first()->show(); - panel_graph_editor->hide(); - panel_node_editor->hide(); - - panel_project.first()->setFloating(false); - panel_effect_controls->setFloating(false); - panel_footage_viewer->setFloating(false); - panel_sequence_viewer->setFloating(false); - panel_timeline.first()->setFloating(false); - panel_graph_editor->setFloating(true); - panel_node_editor->setFloating(true); - - resizeDocks({panel_project.first(), panel_footage_viewer, panel_sequence_viewer}, - {width()/3, width()/3, width()/3}, - Qt::Horizontal); - - resizeDocks({panel_project.first(), panel_timeline.first()}, - {height()/2, height()/2}, - Qt::Vertical); - } - - layout()->update(); -} - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - first_show(true) -{ - EffectInit::StartLoading(); - - olive::cursor::Initialize(); - - open_debug_file(); - - olive::DebugDialog = new DebugDialog(this); - - olive::MainWindow = this; - - QWidget* centralWidget = new QWidget(this); - centralWidget->setMaximumSize(QSize(0, 0)); - setCentralWidget(centralWidget); - - setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); - - setDockNestingEnabled(true); - - layout()->invalidate(); - - QString data_dir = get_data_path(); - if (!data_dir.isEmpty()) { - QDir dir(data_dir); - dir.mkpath("."); - if (dir.exists()) { - qint64 a_month_ago = QDateTime::currentMSecsSinceEpoch() - 2592000000; - qint64 a_week_ago = QDateTime::currentMSecsSinceEpoch() - 604800000; - - // TODO put delete functions in another thread? - - // delete auto-recoveries older than 7 days - QStringList old_autorecoveries = dir.entryList(QStringList("autorecovery.ove.*"), QDir::Files); - int deleted_ars = 0; - for (int i=0;i 0) qInfo() << "Deleted" << deleted_ars << "autorecovery" << ((deleted_ars == 1) ? "file that was" : "files that were") << "older than 7 days"; - - // delete previews older than 30 days - QDir preview_dir = QDir(dir.filePath("previews")); - if (preview_dir.exists()) { - deleted_ars = 0; - QStringList old_prevs = preview_dir.entryList(QDir::Files); - for (int i=0;i 0) qInfo() << "Deleted" << deleted_ars << "preview" << ((deleted_ars == 1) ? "file that was" : "files that were") << "last read over 30 days ago"; - } - - // search for open recents list - olive::Global->load_recent_projects(); - } - } - QString config_path = get_config_path(); - if (!config_path.isEmpty()) { - QDir config_dir(config_path); - config_dir.mkpath("."); - QString config_fn = config_dir.filePath("config.xml"); - if (QFileInfo::exists(config_fn)) { - olive::config.load(config_fn); - } - } - - Restyle(); - - 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 - setup_menus(); - - QStatusBar* statusBar = new QStatusBar(this); - statusBar->showMessage(tr("Welcome to %1").arg(olive::AppName)); - setStatusBar(statusBar); - - olive::Global->check_for_autorecovery_file(); - - // lock panels if the config says so - set_panels_locked(olive::config.locked_panels); - - // set up output audio device - init_audio(); - - // start omnipotent proxy generator process - olive::proxy_generator.start(); - - // load preferred language from file - olive::Global->load_translation_from_config(); - - // set default strings - Retranslate(); -} - -MainWindow::~MainWindow() { - free_panels(); - close_debug_file(); -} - -void kbd_shortcut_processor(QByteArray& file, QMenu* menu, bool save, bool first) { - QList actions = menu->actions(); - for (int i=0;imenu() != nullptr) { - kbd_shortcut_processor(file, a->menu(), save, first); - } else if (!a->isSeparator()) { - if (save) { - // saving custom shortcuts - if (!a->property("default").isNull()) { - QKeySequence defks(a->property("default").toString()); - if (a->shortcut() != defks) { - // custom shortcut - if (!file.isEmpty()) file.append('\n'); - file.append(a->property("id").toString()); - file.append('\t'); - file.append(a->shortcut().toString()); - } - } - } else { - // loading custom shortcuts - if (first) { - // store default shortcut - a->setProperty("default", a->shortcut().toString()); - } else { - // restore default shortcut - a->setShortcut(a->property("default").toString()); - } - if (!a->property("id").isNull()) { - QString comp_str = a->property("id").toString(); - int shortcut_index = file.indexOf(comp_str); - if (shortcut_index == 0 || (shortcut_index > 0 && file.at(shortcut_index-1) == '\n')) { - shortcut_index += comp_str.size() + 1; - QString shortcut; - while (shortcut_index < file.size() && file.at(shortcut_index) != '\n') { - shortcut.append(file.at(shortcut_index)); - shortcut_index++; - } - QKeySequence ks(shortcut); - if (!ks.isEmpty()) { - a->setShortcut(ks); - } - } - } - } - } - } -} - -void MainWindow::load_shortcuts(const QString& fn) { - QByteArray shortcut_bytes; - QFile shortcut_path(fn); - if (shortcut_path.exists() && shortcut_path.open(QFile::ReadOnly)) { - shortcut_bytes = shortcut_path.readAll(); - shortcut_path.close(); - } - QList menus = menuBar()->actions(); - for (int i=0;imenu(); - kbd_shortcut_processor(shortcut_bytes, menu, false, true); - } -} - -void MainWindow::save_shortcuts(const QString& fn) { - // save main menu actions - QList menus = menuBar()->actions(); - QByteArray shortcut_file; - for (int i=0;imenu(); - kbd_shortcut_processor(shortcut_file, menu, true, false); - } - QFile shortcut_file_io(fn); - if (shortcut_file_io.open(QFile::WriteOnly)) { - shortcut_file_io.write(shortcut_file); - shortcut_file_io.close(); - } else { - qCritical() << "Failed to save shortcut file"; - } -} - -bool MainWindow::load_css_from_file(const QString &fn) { - QFile css_file(fn); - if (css_file.exists() && css_file.open(QFile::ReadOnly)) { - setStyleSheet(css_file.readAll()); - css_file.close(); - return true; - } - return false; -} - -void MainWindow::Restyle() -{ - // Set up UI style - if (!olive::styling::UseNativeUI()) { - qApp->setStyle(QStyleFactory::create("Fusion")); - - // Set up whether to load custom CSS or default CSS+palette - if (!olive::config.css_path.isEmpty() - && load_css_from_file(olive::config.css_path)) { - - qApp->setPalette(qApp->style()->standardPalette()); - - } else { - - // set default palette - QPalette palette; - - if (olive::config.style == olive::styling::kOliveDefaultLight) { - - palette.setColor(QPalette::Window, QColor(208, 208, 208)); - palette.setColor(QPalette::WindowText, Qt::black); - palette.setColor(QPalette::Base, QColor(240, 240, 240)); - palette.setColor(QPalette::AlternateBase, QColor(208, 208, 208)); - palette.setColor(QPalette::ToolTipBase, QColor(255, 255, 255)); - palette.setColor(QPalette::ToolTipText, Qt::black); - palette.setColor(QPalette::Text, Qt::black); - palette.setColor(QPalette::Button, QColor(208, 208, 208)); - palette.setColor(QPalette::ButtonText, Qt::black); - palette.setColor(QPalette::BrightText, Qt::red); - palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(208, 208, 208)); - palette.setColor(QPalette::Link, QColor(42, 130, 218)); - palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - palette.setColor(QPalette::HighlightedText, Qt::white); - - /* Olive Mid - palette.setColor(QPalette::Window, QColor(128, 128, 128)); - palette.setColor(QPalette::WindowText, Qt::black); - palette.setColor(QPalette::Base, QColor(192, 192, 192)); - palette.setColor(QPalette::AlternateBase, QColor(128, 128, 128)); - palette.setColor(QPalette::ToolTipBase, QColor(192, 192, 192)); - palette.setColor(QPalette::ToolTipText, Qt::black); - palette.setColor(QPalette::Text, Qt::black); - palette.setColor(QPalette::Button, QColor(128, 128, 128)); - palette.setColor(QPalette::ButtonText, Qt::black); - palette.setColor(QPalette::BrightText, Qt::red); - palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); - palette.setColor(QPalette::Link, QColor(42, 130, 218)); - palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - palette.setColor(QPalette::HighlightedText, Qt::black); - */ - - } else { - - palette.setColor(QPalette::Window, QColor(53,53,53)); - palette.setColor(QPalette::WindowText, Qt::white); - palette.setColor(QPalette::Base, QColor(25,25,25)); - palette.setColor(QPalette::AlternateBase, QColor(53,53,53)); - palette.setColor(QPalette::ToolTipBase, QColor(25,25,25)); - palette.setColor(QPalette::ToolTipText, Qt::white); - palette.setColor(QPalette::Text, Qt::white); - palette.setColor(QPalette::Button, QColor(53,53,53)); - palette.setColor(QPalette::ButtonText, Qt::white); - palette.setColor(QPalette::BrightText, Qt::red); - palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); - palette.setColor(QPalette::Link, QColor(42, 130, 218)); - palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - palette.setColor(QPalette::HighlightedText, Qt::white); - - // set default CSS - QString stylesheet = "QPushButton::checked { background: rgb(25, 25, 25); }"; - - // Windows menus have the option of being native, so we may not need this CSS -#ifdef Q_OS_WIN - if (!olive::config.use_native_menu_styling) { -#endif - stylesheet.append("QMenu::separator { background: #404040; }"); -#ifdef Q_OS_WIN - } -#endif - setStyleSheet(stylesheet); - - } - - qApp->setPalette(palette); - - } - } -} - -void MainWindow::editMenu_About_To_Be_Shown() { - 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::config.use_native_menu_styling) { - OliveGlobal::SetNativeStyling(menuBar); - } - - setMenuBar(menuBar); - - olive::MenuHelper.InitializeSharedMenus(); - - // INITIALIZE FILE MENU - - file_menu = MenuHelper::create_submenu(menuBar, this, SLOT(fileMenu_About_To_Be_Shown())); - - new_menu = MenuHelper::create_submenu(file_menu); - olive::MenuHelper.make_new_menu(new_menu); - - open_project = MenuHelper::create_menu_action(file_menu, "openproj", olive::Global.get(), SLOT(OpenProject()), QKeySequence("Ctrl+O")); - - open_recent = MenuHelper::create_submenu(file_menu); - - 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")); - - save_project_as = MenuHelper::create_menu_action(file_menu, "saveprojas", olive::Global.get(), SLOT(save_project_as()), QKeySequence("Ctrl+Shift+S")); - - file_menu->addSeparator(); - - import_action = MenuHelper::create_menu_action(file_menu, "import", olive::Global.get(), SLOT(open_import_dialog()), QKeySequence("Ctrl+I")); - - file_menu->addSeparator(); - - export_action = MenuHelper::create_menu_action(file_menu, "export", olive::Global.get(), SLOT(open_export_dialog()), QKeySequence("Ctrl+M")); - - file_menu->addSeparator(); - - exit_action = MenuHelper::create_menu_action(file_menu, "exit", this, SLOT(close())); - - // INITIALIZE EDIT MENU - - edit_menu = MenuHelper::create_submenu(menuBar, this, SLOT(editMenu_About_To_Be_Shown())); - - undo_action = MenuHelper::create_menu_action(edit_menu, "undo", olive::Global.get(), SLOT(undo()), QKeySequence("Ctrl+Z")); - redo_action = MenuHelper::create_menu_action(edit_menu, "redo", olive::Global.get(), SLOT(redo()), QKeySequence("Ctrl+Shift+Z")); - - edit_menu->addSeparator(); - - olive::MenuHelper.make_edit_functions_menu(edit_menu); - - 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.first(), SLOT(deselect()), QKeySequence("Ctrl+Shift+A")); - - edit_menu->addSeparator(); - - olive::MenuHelper.make_clip_functions_menu(edit_menu); - - edit_menu->addSeparator(); - - 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.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(); - - setedit_marker_ = MenuHelper::create_menu_action(edit_menu, "marker", &olive::FocusFilter, SLOT(set_marker()), QKeySequence("M")); - - // INITIALIZE VIEW MENU - - view_menu = MenuHelper::create_submenu(menuBar, this, SLOT(viewMenu_About_To_Be_Shown())); - - 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.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.first(), SLOT(toggle_show_all()), QKeySequence("\\")); - show_all->setCheckable(true); - - view_menu->addSeparator(); - - 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::config.rectified_waveforms)); - - view_menu->addSeparator(); - - QActionGroup* frame_view_mode_group = new QActionGroup(this); - - frames_action = MenuHelper::create_menu_action(view_menu, "modeframes", &olive::MenuHelper, SLOT(set_timecode_view())); - frames_action->setData(olive::kTimecodeFrames); - frames_action->setCheckable(true); - frame_view_mode_group->addAction(frames_action); - - drop_frame_action = MenuHelper::create_menu_action(view_menu, "modedropframe", &olive::MenuHelper, SLOT(set_timecode_view())); - drop_frame_action->setData(olive::kTimecodeDrop); - drop_frame_action->setCheckable(true); - frame_view_mode_group->addAction(drop_frame_action); - - nondrop_frame_action = MenuHelper::create_menu_action(view_menu, "modenondropframe", &olive::MenuHelper, SLOT(set_timecode_view())); - nondrop_frame_action->setData(olive::kTimecodeNonDrop); - nondrop_frame_action->setCheckable(true); - frame_view_mode_group->addAction(nondrop_frame_action); - - milliseconds_action = MenuHelper::create_menu_action(view_menu, "milliseconds", &olive::MenuHelper, SLOT(set_timecode_view())); - milliseconds_action->setData(olive::kTimecodeMilliseconds); - milliseconds_action->setCheckable(true); - frame_view_mode_group->addAction(milliseconds_action); - - view_menu->addSeparator(); - - title_safe_area_menu = MenuHelper::create_submenu(view_menu); - - QActionGroup* title_safe_group = new QActionGroup(this); - - title_safe_off = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafeoff", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); - title_safe_off->setCheckable(true); - title_safe_off->setData(qSNaN()); - title_safe_group->addAction(title_safe_off); - - title_safe_default = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafedefault", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); - title_safe_default->setCheckable(true); - title_safe_default->setData(0.0); - title_safe_group->addAction(title_safe_default); - - title_safe_43 = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafe43", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); - title_safe_43->setCheckable(true); - title_safe_43->setData(4.0/3.0); - title_safe_group->addAction(title_safe_43); - - title_safe_169 = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafe169", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); - title_safe_169->setCheckable(true); - title_safe_169->setData(16.0/9.0); - title_safe_group->addAction(title_safe_169); - - title_safe_custom = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafecustom", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); - title_safe_custom->setCheckable(true); - title_safe_custom->setData(-1.0); - title_safe_group->addAction(title_safe_custom); - - view_menu->addSeparator(); - - full_screen = MenuHelper::create_menu_action(view_menu, "fullscreen", this, SLOT(toggle_full_screen()), QKeySequence("F11")); - full_screen->setCheckable(true); - - full_screen_viewer_ = MenuHelper::create_menu_action(view_menu, "fullscreenviewer", &olive::FocusFilter, SLOT(set_viewer_fullscreen())); - - // INITIALIZE PLAYBACK MENU - - playback_menu = MenuHelper::create_submenu(menuBar, this, SLOT(playbackMenu_About_To_Be_Shown())); - - go_to_start_ = MenuHelper::create_menu_action(playback_menu, "gotostart", &olive::FocusFilter, SLOT(go_to_start()), QKeySequence("Home")); - previous_frame_ = MenuHelper::create_menu_action(playback_menu, "prevframe", &olive::FocusFilter, SLOT(prev_frame()), QKeySequence("Left")); - playpause_ = MenuHelper::create_menu_action(playback_menu, "playpause", &olive::FocusFilter, SLOT(playpause()), QKeySequence("Space")); - play_in_to_out_ = MenuHelper::create_menu_action(playback_menu, "playintoout", &olive::FocusFilter, SLOT(play_in_to_out()), QKeySequence("Shift+Space")); - next_frame_ = MenuHelper::create_menu_action(playback_menu, "nextframe", &olive::FocusFilter, SLOT(next_frame()), QKeySequence("Right")); - go_to_end_ = MenuHelper::create_menu_action(playback_menu, "gotoend", &olive::FocusFilter, SLOT(go_to_end()), QKeySequence("End")); - - playback_menu->addSeparator(); - - 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(); - - go_to_in_point_ = MenuHelper::create_menu_action(playback_menu, "gotoin", &olive::FocusFilter, SLOT(go_to_in()), QKeySequence("Shift+I")); - go_to_out_point_ = MenuHelper::create_menu_action(playback_menu, "gotoout", &olive::FocusFilter, SLOT(go_to_out()), QKeySequence("Shift+O")); - - playback_menu->addSeparator(); - - shuttle_left_ = MenuHelper::create_menu_action(playback_menu, "decspeed", &olive::FocusFilter, SLOT(decrease_speed()), QKeySequence("J")); - shuttle_stop_ = MenuHelper::create_menu_action(playback_menu, "pause", &olive::FocusFilter, SLOT(pause()), QKeySequence("K")); - shuttle_right_ = MenuHelper::create_menu_action(playback_menu, "incspeed", &olive::FocusFilter, SLOT(increase_speed()), QKeySequence("L")); - - playback_menu->addSeparator(); - - 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::config.loop)); - - // INITIALIZE WINDOW MENU - - window_menu = MenuHelper::create_submenu(menuBar, this, SLOT(windowMenu_About_To_Be_Shown())); - - 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.first())); - - window_effectcontrols_action = MenuHelper::create_menu_action(window_menu, "paneleffectcontrols", this, SLOT(toggle_panel_visibility())); - window_effectcontrols_action->setCheckable(true); - window_effectcontrols_action->setData(reinterpret_cast(panel_effect_controls)); - - 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.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)); - - window_sequenceviewer_action = MenuHelper::create_menu_action(window_menu, "panelsequenceviewer", this, SLOT(toggle_panel_visibility())); - window_sequenceviewer_action->setCheckable(true); - window_sequenceviewer_action->setData(reinterpret_cast(panel_sequence_viewer)); - - window_menu->addSeparator(); - - maximize_panel_ = MenuHelper::create_menu_action(window_menu, "maximizepanel", this, SLOT(maximize_panel()), QKeySequence("`")); - - lock_panels_ = MenuHelper::create_menu_action(window_menu, "lockpanels", this, SLOT(set_panels_locked(bool))); - lock_panels_->setCheckable(true); - - window_menu->addSeparator(); - - reset_default_layout_ = MenuHelper::create_menu_action(window_menu, "resetdefaultlayout", this, SLOT(reset_layout())); - - // INITIALIZE TOOLS MENU - - tools_menu = MenuHelper::create_submenu(menuBar, this, SLOT(toolMenu_About_To_Be_Shown())); - tools_menu->setToolTipsVisible(true); - - QActionGroup* tools_group = new QActionGroup(this); - - 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.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.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.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.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.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.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.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.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.first()->snappingButton)); - - tools_menu->addSeparator(); - - autocut_silence_ = MenuHelper::create_menu_action(tools_menu, "autocutsilence", olive::Global.get(), SLOT(open_autocut_silence_dialog())); - - tools_menu->addSeparator(); - - QActionGroup* autoscroll_group = new QActionGroup(this); - - no_autoscroll = MenuHelper::create_menu_action(tools_menu, "autoscrollno", &olive::MenuHelper, SLOT(set_autoscroll())); - no_autoscroll->setData(olive::AUTOSCROLL_NO_SCROLL); - no_autoscroll->setCheckable(true); - autoscroll_group->addAction(no_autoscroll); - - page_autoscroll = MenuHelper::create_menu_action(tools_menu, "autoscrollpage", &olive::MenuHelper, SLOT(set_autoscroll())); - page_autoscroll->setData(olive::AUTOSCROLL_PAGE_SCROLL); - page_autoscroll->setCheckable(true); - autoscroll_group->addAction(page_autoscroll); - - smooth_autoscroll = MenuHelper::create_menu_action(tools_menu, "autoscrollsmooth", &olive::MenuHelper, SLOT(set_autoscroll())); - smooth_autoscroll->setData(olive::AUTOSCROLL_SMOOTH_SCROLL); - smooth_autoscroll->setCheckable(true); - autoscroll_group->addAction(smooth_autoscroll); - - tools_menu->addSeparator(); - - preferences_action_ = MenuHelper::create_menu_action(tools_menu, "prefs", olive::Global.get(), SLOT(open_preferences()), QKeySequence("Ctrl+,")); - -#ifdef QT_DEBUG - clear_undo_action_ = MenuHelper::create_menu_action(tools_menu, "clearundo", olive::Global.get(), SLOT(clear_undo_stack())); -#endif - - // INITIALIZE HELP MENU - - help_menu = MenuHelper::create_submenu(menuBar); - - action_search_ = MenuHelper::create_menu_action(help_menu, "actionsearch", olive::Global.get(), SLOT(open_action_search()), QKeySequence("/")); - - help_menu->addSeparator(); - - debug_log_ = MenuHelper::create_menu_action(help_menu, "debuglog", olive::Global.get(), SLOT(open_debug_log())); - - help_menu->addSeparator(); - - about_action_ = MenuHelper::create_menu_action(help_menu, "about", olive::Global.get(), SLOT(open_about_dialog())); - - load_shortcuts(get_config_path() + "/shortcuts"); -} - -void MainWindow::Retranslate() -{ - file_menu->setTitle(tr("&File")); - new_menu->setTitle(tr("&New")); - open_project->setText(tr("&Open Project")); - clear_open_recent_action->setText(tr("Clear Recent List")); - open_recent->setTitle(tr("Open Recent")); - save_project->setText(tr("&Save Project")); - save_project_as->setText(tr("Save Project &As")); - import_action->setText(tr("&Import...")); - export_action->setText(tr("&Export...")); - exit_action->setText(tr("E&xit")); - - edit_menu->setTitle(tr("&Edit")); - undo_action->setText(tr("&Undo")); - redo_action->setText(tr("Redo")); - select_all_action->setText(tr("Select &All")); - deselect_all_action->setText(tr("Deselect All")); - ripple_to_in_point_->setText(tr("Ripple to In Point")); - ripple_to_out_point_->setText(tr("Ripple to Out Point")); - edit_to_in_point_->setText(tr("Edit to In Point")); - edit_to_out_point_->setText(tr("Edit to Out Point")); - delete_inout_point_->setText(tr("Delete In/Out Point")); - ripple_delete_inout_point_->setText(tr("Ripple Delete In/Out Point")); - setedit_marker_->setText(tr("Set/Edit Marker")); - - view_menu->setTitle(tr("&View")); - zoom_in_->setText(tr("Zoom In")); - zoom_out_->setText(tr("Zoom Out")); - increase_track_height_->setText(tr("Increase Track Height")); - decrease_track_height_->setText(tr("Decrease Track Height")); - show_all->setText(tr("Toggle Show All")); - rectified_waveforms->setText(tr("Rectified Waveforms")); - frames_action->setText(tr("Frames")); - drop_frame_action->setText(tr("Drop Frame")); - nondrop_frame_action->setText(tr("Non-Drop Frame")); - milliseconds_action->setText(tr("Milliseconds")); - - title_safe_area_menu->setTitle(tr("Title/Action Safe Area")); - title_safe_off->setText(tr("Off")); - title_safe_default->setText(tr("Default")); - title_safe_43->setText(tr("4:3")); - title_safe_169->setText(tr("16:9")); - title_safe_custom->setText(tr("Custom")); - - full_screen->setText(tr("Full Screen")); - full_screen_viewer_->setText(tr("Full Screen Viewer")); - - playback_menu->setTitle(tr("&Playback")); - go_to_start_->setText(tr("Go to Start")); - previous_frame_->setText(tr("Previous Frame")); - playpause_->setText(tr("Play/Pause")); - play_in_to_out_->setText(tr("Play In to Out")); - next_frame_->setText(tr("Next Frame")); - go_to_end_->setText(tr("Go to End")); - - go_to_prev_cut_->setText(tr("Go to Previous Cut")); - go_to_next_cut_->setText(tr("Go to Next Cut")); - go_to_in_point_->setText(tr("Go to In Point")); - go_to_out_point_->setText(tr("Go to Out Point")); - - shuttle_left_->setText(tr("Shuttle Left")); - shuttle_stop_->setText(tr("Shuttle Stop")); - shuttle_right_->setText(tr("Shuttle Right")); - - loop_action_->setText(tr("Loop")); - - window_menu->setTitle(tr("&Window")); - - window_project_action->setText(tr("Project")); - 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")); - - maximize_panel_->setText(tr("Maximize Panel")); - lock_panels_->setText(tr("Lock Panels")); - reset_default_layout_->setText(tr("Reset to Default Layout")); - - tools_menu->setTitle(tr("&Tools")); - - pointer_tool_action->setText(tr("Pointer Tool")); - edit_tool_action->setText(tr("Edit Tool")); - ripple_tool_action->setText(tr("Ripple Tool")); - razor_tool_action->setText(tr("Razor Tool")); - slip_tool_action->setText(tr("Slip Tool")); - slide_tool_action->setText(tr("Slide Tool")); - hand_tool_action->setText(tr("Hand Tool")); - transition_tool_action->setText(tr("Transition Tool")); - snap_toggle->setText(tr("Enable Snapping")); - autocut_silence_->setText(tr("Auto-Cut Silence")); - - no_autoscroll->setText(tr("No Auto-Scroll")); - page_autoscroll->setText(tr("Page Auto-Scroll")); - smooth_autoscroll->setText(tr("Smooth Auto-Scroll")); - - preferences_action_->setText(tr("Preferences")); -#ifdef QT_DEBUG - clear_undo_action_->setText(tr("Clear Undo")); -#endif - - help_menu->setTitle(tr("&Help")); - - action_search_->setText(tr("A&ction Search")); - debug_log_->setText(tr("Debug Log")); - about_action_->setText(tr("&About...")); - - panel_sequence_viewer->set_panel_name(QCoreApplication::translate("Viewer", "Sequence Viewer: %1")); - panel_footage_viewer->set_panel_name(QCoreApplication::translate("Viewer", "Media Viewer: %1")); - - // the recommended changeEvent() and event() methods of propagating language change messages provided mixed results - // (i.e. different panels failed to translate in different sessions), so we translate them manually here - for (int i=0;iRetranslate(); - } - olive::MenuHelper.Retranslate(); - - updateTitle(); -} - -void MainWindow::updateTitle() { - setWindowTitle(QString("%1 - %2[*]").arg(olive::AppName, - (olive::ActiveProjectFilename.isEmpty()) ? - tr("") : olive::ActiveProjectFilename) - ); -} - -void MainWindow::closeEvent(QCloseEvent *e) { - if (olive::Global->can_close_project()) { - // stop proxy generator thread - olive::proxy_generator.cancel(); - - panel_graph_editor->set_row(nullptr); - panel_effect_controls->Clear(true); - - panel_footage_viewer->viewer_widget()->close_window(); - panel_sequence_viewer->viewer_widget()->close_window(); - - 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")); - } - } - if (!config_path.isEmpty()) { - QDir config_dir = QDir(config_path); - - QString config_fn = config_dir.filePath("config.xml"); - - // save settings - olive::config.save(config_fn); - - // save panel layout - QFile panel_config(get_config_dir().filePath("layout")); - if (panel_config.open(QFile::WriteOnly)) { - QXmlStreamWriter stream(&panel_config); - stream.setAutoFormatting(true); - stream.writeStartDocument(); - - stream.writeStartElement("layout"); - - stream.writeTextElement("panels", saveState(0).toBase64()); - - // if the panels have any specific layout data to save, save it now - for (int i=0;iSaveLayoutState(); - - if (!layout_data.isEmpty()) { - - // layout data is matched with the panel's objectName(), which we can't do if the panel has no name - const QString& panel_name = olive::panels.at(i)->objectName(); - if (panel_name.isEmpty()) { - qWarning() << "Panel" << i << "had layout state data but no objectName(). Layout was not saved."; - } else { - stream.writeStartElement("panel"); - - stream.writeAttribute("name", panel_name); - - stream.writeCharacters(layout_data.toBase64()); - - stream.writeEndElement(); - } - } - } - - stream.writeEndElement(); // layout - - stream.writeEndDocument(); - panel_config.close(); - } else { - qCritical() << "Failed to save layout"; - } - - save_shortcuts(config_path + "/shortcuts"); - } - - stop_audio(); - - e->accept(); - } else { - e->ignore(); - } -} - -void MainWindow::paintEvent(QPaintEvent *event) { - QMainWindow::paintEvent(event); - - if (first_show) { - // set this to false immediately to prevent anything here being called again - first_show = false; - - /** - * @brief Set up the dock widget layout on the main window - * - * For some reason, Qt didn't like this in the constructor. It would lead to several geometry issues with HiDPI - * on Windows, and also seemed to break QMainWindow::restoreState() which is why it took so long to implement - * saving/restoring panel layouts. Putting it in showEvent() didn't help either, nor did putting it in - * changeEvent() (QEvent::type() == QEvent::Polish). This is the only place it's functioned as expected. - */ - setup_layout(false); - - /** - Signal that window has finished loading. - */ - emit finished_first_paint(); - } -} - -void MainWindow::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::LanguageChange) { - - // if this was a LanguageEvent, run the retranslation function - Retranslate(); - - } else { - - // otherwise pass it to the base class - QMainWindow::changeEvent(e); - - } -} - -void MainWindow::reset_layout() { - setup_layout(true); -} - -void MainWindow::maximize_panel() { - // toggles between normal state and a state of one panel being maximized - if (temp_panel_state.isEmpty()) { - // get currently hovered panel - QDockWidget* focused_panel = get_focused_panel(true); - - // if the mouse is in fact hovering over a panel - if (focused_panel != nullptr) { - // store the current state of panels - temp_panel_state = saveState(); - - // remove all dock widgets that aren't the hovered panel - for (int i=0;isetVisible(false); - - // set it to floating - olive::panels.at(i)->setFloating(true); - } - } - } - } else { - // we must be maximized, restore previous state - restoreState(temp_panel_state); - - // clear temp panel state for next maximize call - temp_panel_state.clear(); - } -} - -void MainWindow::windowMenu_About_To_Be_Shown() { - QList window_actions = window_menu->actions(); - for (int i=0;idata().isNull()) { - a->setChecked(reinterpret_cast(a->data().value())->isVisible()); - } - } - - lock_panels_->setChecked(olive::config.locked_panels); -} - -void MainWindow::playbackMenu_About_To_Be_Shown() { - olive::MenuHelper.set_bool_action_checked(loop_action_); -} - -void MainWindow::viewMenu_About_To_Be_Shown() { - olive::MenuHelper.set_bool_action_checked(rectified_waveforms); - - 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::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.first()->showing_all); -} - -void MainWindow::toolMenu_About_To_Be_Shown() { - olive::MenuHelper.set_button_action_checked(pointer_tool_action); - olive::MenuHelper.set_button_action_checked(edit_tool_action); - olive::MenuHelper.set_button_action_checked(ripple_tool_action); - olive::MenuHelper.set_button_action_checked(razor_tool_action); - olive::MenuHelper.set_button_action_checked(slip_tool_action); - olive::MenuHelper.set_button_action_checked(slide_tool_action); - olive::MenuHelper.set_button_action_checked(hand_tool_action); - 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::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() { - QAction* action = static_cast(sender()); - QDockWidget* w = reinterpret_cast(action->data().value()); - w->setVisible(!w->isVisible()); - - // layout has changed, we're no longer in maximized panel mode, - // so we clear this byte array - temp_panel_state.clear(); -} - -void MainWindow::set_panels_locked(bool locked) -{ - for (int i=0;isetFeatures(panel->features() & ~QDockWidget::DockWidgetMovable); - - // hide the title bar (only real way to do this is to replace it with an empty QWidget) - panel->setTitleBarWidget(new QWidget(panel)); - } else { - // re-enable moving on QDockWidget - panel->setFeatures(panel->features() | QDockWidget::DockWidgetMovable); - - // set the "custom" titlebar to null so the default gets restored - panel->setTitleBarWidget(nullptr); - } - } - - olive::config.locked_panels = locked; -} - -void MainWindow::fileMenu_About_To_Be_Shown() { - if (olive::Global->recent_project_count() > 0) { - open_recent->clear(); - open_recent->setEnabled(true); - 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())); - } - open_recent->addSeparator(); - - open_recent->addAction(clear_open_recent_action); - } else { - open_recent->setEnabled(false); - } -} - -void MainWindow::toggle_full_screen() { - if (windowState() == Qt::WindowFullScreen) { - setWindowState(Qt::WindowNoState); // seems to be necessary for it to return to Maximized correctly on Linux - setWindowState(Qt::WindowMaximized); - } else { - setWindowState(Qt::WindowFullScreen); - } -} +/*** + + 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 "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "global/global.h" +#include "ui/menuhelper.h" +#include "project/projectelements.h" +#include "timeline/clip.h" +#include "global/config.h" +#include "global/path.h" +#include "global/debug.h" +#include "project/proxygenerator.h" +#include "project/projectfilter.h" +#include "ui/sourcetable.h" +#include "ui/viewerwidget.h" +#include "ui/sourceiconview.h" +#include "ui/timelineheader.h" +#include "ui/icons.h" +#include "ui/cursors.h" +#include "ui/focusfilter.h" +#include "panels/panels.h" +#include "dialogs/debugdialog.h" +#include "rendering/audio.h" +#include "rendering/renderfunctions.h" +#include "undo/undostack.h" +#include "effects/effectloaders.h" + +MainWindow* olive::MainWindow; + +void MainWindow::setup_layout(bool reset) { + // load panels from file + if (!reset) { + QFile panel_config(get_config_dir().filePath("layout")); + if (panel_config.exists() && panel_config.open(QFile::ReadOnly)) { + + // default to resetting unless we find layout data in the XML file + reset = true; + + // read XML layout file + QXmlStreamReader stream(&panel_config); + + // loop through XML for all data + while (!stream.atEnd()) { + stream.readNext(); + + if (stream.name() == "panels" && stream.isStartElement()) { + + // element contains MainWindow layout data to restore + stream.readNext(); + restoreState(QByteArray::fromBase64(stream.text().toUtf8()), 0); + reset = false; + + } else if (stream.name() == "panel" && stream.isStartElement()) { + + // element contains layout data specific to a panel, we'll find the panel and load it + + // get panel name from XML attribute + QString panel_name; + const QXmlStreamAttributes& attributes = stream.attributes(); + for (int i=0;iobjectName() == panel_name) { + + // found the panel, so we can load its state + stream.readNext(); + panel->LoadLayoutState(QByteArray::fromBase64(stream.text().toUtf8())); + + // we found it, no more need to loop through panels + found_panel = true; + + break; + + } + + } + + if (!found_panel) { + qWarning() << "Panel specified in layout data doesn't exist. Layout wasn't loaded."; + } + + } + + } + + } + + panel_config.close(); + } else { + reset = true; + } + } + + if (reset) { + // remove all panels from the main window + for (int i=0;iraise(); + addDockWidget(Qt::TopDockWidgetArea, panel_sequence_viewer); + addDockWidget(Qt::BottomDockWidgetArea, panel_timeline.first()); + + panel_project.first()->show(); + panel_effect_controls->show(); + panel_footage_viewer->show(); + panel_sequence_viewer->show(); + panel_timeline.first()->show(); + panel_graph_editor->hide(); + panel_node_editor->hide(); + + panel_project.first()->setFloating(false); + panel_effect_controls->setFloating(false); + panel_footage_viewer->setFloating(false); + panel_sequence_viewer->setFloating(false); + panel_timeline.first()->setFloating(false); + panel_graph_editor->setFloating(true); + panel_node_editor->setFloating(true); + + resizeDocks({panel_project.first(), panel_footage_viewer, panel_sequence_viewer}, + {width()/3, width()/3, width()/3}, + Qt::Horizontal); + + resizeDocks({panel_project.first(), panel_timeline.first()}, + {height()/2, height()/2}, + Qt::Vertical); + } + + layout()->update(); +} + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + first_show(true) +{ + EffectInit::StartLoading(); + + olive::cursor::Initialize(); + + open_debug_file(); + + olive::DebugDialog = new DebugDialog(this); + + olive::MainWindow = this; + + QWidget* centralWidget = new QWidget(this); + centralWidget->setMaximumSize(QSize(0, 0)); + setCentralWidget(centralWidget); + + setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); + + setDockNestingEnabled(true); + + layout()->invalidate(); + + QString data_dir = get_data_path(); + if (!data_dir.isEmpty()) { + QDir dir(data_dir); + dir.mkpath("."); + if (dir.exists()) { + qint64 a_month_ago = QDateTime::currentMSecsSinceEpoch() - 2592000000; + qint64 a_week_ago = QDateTime::currentMSecsSinceEpoch() - 604800000; + + // TODO put delete functions in another thread? + + // delete auto-recoveries older than 7 days + QStringList old_autorecoveries = dir.entryList(QStringList("autorecovery.ove.*"), QDir::Files); + int deleted_ars = 0; + for (int i=0;i 0) qInfo() << "Deleted" << deleted_ars << "autorecovery" << ((deleted_ars == 1) ? "file that was" : "files that were") << "older than 7 days"; + + // delete previews older than 30 days + QDir preview_dir = QDir(dir.filePath("previews")); + if (preview_dir.exists()) { + deleted_ars = 0; + QStringList old_prevs = preview_dir.entryList(QDir::Files); + for (int i=0;i 0) qInfo() << "Deleted" << deleted_ars << "preview" << ((deleted_ars == 1) ? "file that was" : "files that were") << "last read over 30 days ago"; + } + + // search for open recents list + olive::Global->load_recent_projects(); + } + } + QString config_path = get_config_path(); + if (!config_path.isEmpty()) { + QDir config_dir(config_path); + config_dir.mkpath("."); + QString config_fn = config_dir.filePath("config.xml"); + if (QFileInfo::exists(config_fn)) { + olive::config.load(config_fn); + } + } + + Restyle(); + + 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 + setup_menus(); + + QStatusBar* statusBar = new QStatusBar(this); + statusBar->showMessage(tr("Welcome to %1").arg(olive::AppName)); + setStatusBar(statusBar); + + olive::Global->check_for_autorecovery_file(); + + // lock panels if the config says so + set_panels_locked(olive::config.locked_panels); + + // set up output audio device + init_audio(); + + // start omnipotent proxy generator process + olive::proxy_generator.start(); + + // load preferred language from file + olive::Global->load_translation_from_config(); + + // set default strings + Retranslate(); +} + +MainWindow::~MainWindow() { + free_panels(); + close_debug_file(); +} + +void kbd_shortcut_processor(QByteArray& file, QMenu* menu, bool save, bool first) { + QList actions = menu->actions(); + for (int i=0;imenu() != nullptr) { + kbd_shortcut_processor(file, a->menu(), save, first); + } else if (!a->isSeparator()) { + if (save) { + // saving custom shortcuts + if (!a->property("default").isNull()) { + QKeySequence defks(a->property("default").toString()); + if (a->shortcut() != defks) { + // custom shortcut + if (!file.isEmpty()) file.append('\n'); + file.append(a->property("id").toString()); + file.append('\t'); + file.append(a->shortcut().toString()); + } + } + } else { + // loading custom shortcuts + if (first) { + // store default shortcut + a->setProperty("default", a->shortcut().toString()); + } else { + // restore default shortcut + a->setShortcut(a->property("default").toString()); + } + if (!a->property("id").isNull()) { + QString comp_str = a->property("id").toString(); + int shortcut_index = file.indexOf(comp_str); + if (shortcut_index == 0 || (shortcut_index > 0 && file.at(shortcut_index-1) == '\n')) { + shortcut_index += comp_str.size() + 1; + QString shortcut; + while (shortcut_index < file.size() && file.at(shortcut_index) != '\n') { + shortcut.append(file.at(shortcut_index)); + shortcut_index++; + } + QKeySequence ks(shortcut); + if (!ks.isEmpty()) { + a->setShortcut(ks); + } + } + } + } + } + } +} + +void MainWindow::load_shortcuts(const QString& fn) { + QByteArray shortcut_bytes; + QFile shortcut_path(fn); + if (shortcut_path.exists() && shortcut_path.open(QFile::ReadOnly)) { + shortcut_bytes = shortcut_path.readAll(); + shortcut_path.close(); + } + QList menus = menuBar()->actions(); + for (int i=0;imenu(); + kbd_shortcut_processor(shortcut_bytes, menu, false, true); + } +} + +void MainWindow::save_shortcuts(const QString& fn) { + // save main menu actions + QList menus = menuBar()->actions(); + QByteArray shortcut_file; + for (int i=0;imenu(); + kbd_shortcut_processor(shortcut_file, menu, true, false); + } + QFile shortcut_file_io(fn); + if (shortcut_file_io.open(QFile::WriteOnly)) { + shortcut_file_io.write(shortcut_file); + shortcut_file_io.close(); + } else { + qCritical() << "Failed to save shortcut file"; + } +} + +bool MainWindow::load_css_from_file(const QString &fn) { + QFile css_file(fn); + if (css_file.exists() && css_file.open(QFile::ReadOnly)) { + setStyleSheet(css_file.readAll()); + css_file.close(); + return true; + } + return false; +} + +void MainWindow::Restyle() +{ + // Set up UI style + if (!olive::styling::UseNativeUI()) { + qApp->setStyle(QStyleFactory::create("Fusion")); + + // Set up whether to load custom CSS or default CSS+palette + if (!olive::config.css_path.isEmpty() + && load_css_from_file(olive::config.css_path)) { + + qApp->setPalette(qApp->style()->standardPalette()); + + } else { + + // set default palette + QPalette palette; + + if (olive::config.style == olive::styling::kOliveDefaultLight) { + + palette.setColor(QPalette::Window, QColor(208, 208, 208)); + palette.setColor(QPalette::WindowText, Qt::black); + palette.setColor(QPalette::Base, QColor(240, 240, 240)); + palette.setColor(QPalette::AlternateBase, QColor(208, 208, 208)); + palette.setColor(QPalette::ToolTipBase, QColor(255, 255, 255)); + palette.setColor(QPalette::ToolTipText, Qt::black); + palette.setColor(QPalette::Text, Qt::black); + palette.setColor(QPalette::Button, QColor(208, 208, 208)); + palette.setColor(QPalette::ButtonText, Qt::black); + palette.setColor(QPalette::BrightText, Qt::red); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(208, 208, 208)); + palette.setColor(QPalette::Link, QColor(42, 130, 218)); + palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + palette.setColor(QPalette::HighlightedText, Qt::white); + + /* Olive Mid + palette.setColor(QPalette::Window, QColor(128, 128, 128)); + palette.setColor(QPalette::WindowText, Qt::black); + palette.setColor(QPalette::Base, QColor(192, 192, 192)); + palette.setColor(QPalette::AlternateBase, QColor(128, 128, 128)); + palette.setColor(QPalette::ToolTipBase, QColor(192, 192, 192)); + palette.setColor(QPalette::ToolTipText, Qt::black); + palette.setColor(QPalette::Text, Qt::black); + palette.setColor(QPalette::Button, QColor(128, 128, 128)); + palette.setColor(QPalette::ButtonText, Qt::black); + palette.setColor(QPalette::BrightText, Qt::red); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); + palette.setColor(QPalette::Link, QColor(42, 130, 218)); + palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + palette.setColor(QPalette::HighlightedText, Qt::black); + */ + + } else { + + palette.setColor(QPalette::Window, QColor(53,53,53)); + palette.setColor(QPalette::WindowText, Qt::white); + palette.setColor(QPalette::Base, QColor(25,25,25)); + palette.setColor(QPalette::AlternateBase, QColor(53,53,53)); + palette.setColor(QPalette::ToolTipBase, QColor(25,25,25)); + palette.setColor(QPalette::ToolTipText, Qt::white); + palette.setColor(QPalette::Text, Qt::white); + palette.setColor(QPalette::Button, QColor(53,53,53)); + palette.setColor(QPalette::ButtonText, Qt::white); + palette.setColor(QPalette::BrightText, Qt::red); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); + palette.setColor(QPalette::Link, QColor(42, 130, 218)); + palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + palette.setColor(QPalette::HighlightedText, Qt::white); + + // set default CSS + QString stylesheet = "QPushButton::checked { background: rgb(25, 25, 25); }"; + + // Windows menus have the option of being native, so we may not need this CSS +#ifdef Q_OS_WIN + if (!olive::config.use_native_menu_styling) { +#endif + stylesheet.append("QMenu::separator { background: #404040; }"); +#ifdef Q_OS_WIN + } +#endif + setStyleSheet(stylesheet); + + } + + qApp->setPalette(palette); + + } + } +} + +void MainWindow::editMenu_About_To_Be_Shown() { + 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::config.use_native_menu_styling) { + OliveGlobal::SetNativeStyling(menuBar); + } + + setMenuBar(menuBar); + + olive::MenuHelper.InitializeSharedMenus(); + + // INITIALIZE FILE MENU + + file_menu = MenuHelper::create_submenu(menuBar, this, SLOT(fileMenu_About_To_Be_Shown())); + + new_menu = MenuHelper::create_submenu(file_menu); + olive::MenuHelper.make_new_menu(new_menu); + + open_project = MenuHelper::create_menu_action(file_menu, "openproj", olive::Global.get(), SLOT(OpenProject()), QKeySequence("Ctrl+O")); + + open_recent = MenuHelper::create_submenu(file_menu); + + 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")); + + save_project_as = MenuHelper::create_menu_action(file_menu, "saveprojas", olive::Global.get(), SLOT(save_project_as()), QKeySequence("Ctrl+Shift+S")); + + file_menu->addSeparator(); + + import_action = MenuHelper::create_menu_action(file_menu, "import", olive::Global.get(), SLOT(open_import_dialog()), QKeySequence("Ctrl+I")); + + file_menu->addSeparator(); + + export_action = MenuHelper::create_menu_action(file_menu, "export", olive::Global.get(), SLOT(open_export_dialog()), QKeySequence("Ctrl+M")); + + file_menu->addSeparator(); + + exit_action = MenuHelper::create_menu_action(file_menu, "exit", this, SLOT(close())); + + // INITIALIZE EDIT MENU + + edit_menu = MenuHelper::create_submenu(menuBar, this, SLOT(editMenu_About_To_Be_Shown())); + + undo_action = MenuHelper::create_menu_action(edit_menu, "undo", olive::Global.get(), SLOT(undo()), QKeySequence("Ctrl+Z")); + redo_action = MenuHelper::create_menu_action(edit_menu, "redo", olive::Global.get(), SLOT(redo()), QKeySequence("Ctrl+Shift+Z")); + + edit_menu->addSeparator(); + + olive::MenuHelper.make_edit_functions_menu(edit_menu); + + 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.first(), SLOT(deselect()), QKeySequence("Ctrl+Shift+A")); + + edit_menu->addSeparator(); + + olive::MenuHelper.make_clip_functions_menu(edit_menu); + + edit_menu->addSeparator(); + + 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.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(); + + setedit_marker_ = MenuHelper::create_menu_action(edit_menu, "marker", &olive::FocusFilter, SLOT(set_marker()), QKeySequence("M")); + + // INITIALIZE VIEW MENU + + view_menu = MenuHelper::create_submenu(menuBar, this, SLOT(viewMenu_About_To_Be_Shown())); + + 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.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.first(), SLOT(toggle_show_all()), QKeySequence("\\")); + show_all->setCheckable(true); + + view_menu->addSeparator(); + + 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::config.rectified_waveforms)); + + view_menu->addSeparator(); + + QActionGroup* frame_view_mode_group = new QActionGroup(this); + + frames_action = MenuHelper::create_menu_action(view_menu, "modeframes", &olive::MenuHelper, SLOT(set_timecode_view())); + frames_action->setData(olive::kTimecodeFrames); + frames_action->setCheckable(true); + frame_view_mode_group->addAction(frames_action); + + drop_frame_action = MenuHelper::create_menu_action(view_menu, "modedropframe", &olive::MenuHelper, SLOT(set_timecode_view())); + drop_frame_action->setData(olive::kTimecodeDrop); + drop_frame_action->setCheckable(true); + frame_view_mode_group->addAction(drop_frame_action); + + nondrop_frame_action = MenuHelper::create_menu_action(view_menu, "modenondropframe", &olive::MenuHelper, SLOT(set_timecode_view())); + nondrop_frame_action->setData(olive::kTimecodeNonDrop); + nondrop_frame_action->setCheckable(true); + frame_view_mode_group->addAction(nondrop_frame_action); + + milliseconds_action = MenuHelper::create_menu_action(view_menu, "milliseconds", &olive::MenuHelper, SLOT(set_timecode_view())); + milliseconds_action->setData(olive::kTimecodeMilliseconds); + milliseconds_action->setCheckable(true); + frame_view_mode_group->addAction(milliseconds_action); + + view_menu->addSeparator(); + + title_safe_area_menu = MenuHelper::create_submenu(view_menu); + + QActionGroup* title_safe_group = new QActionGroup(this); + + title_safe_off = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafeoff", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); + title_safe_off->setCheckable(true); + title_safe_off->setData(qSNaN()); + title_safe_group->addAction(title_safe_off); + + title_safe_default = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafedefault", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); + title_safe_default->setCheckable(true); + title_safe_default->setData(0.0); + title_safe_group->addAction(title_safe_default); + + title_safe_43 = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafe43", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); + title_safe_43->setCheckable(true); + title_safe_43->setData(4.0/3.0); + title_safe_group->addAction(title_safe_43); + + title_safe_169 = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafe169", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); + title_safe_169->setCheckable(true); + title_safe_169->setData(16.0/9.0); + title_safe_group->addAction(title_safe_169); + + title_safe_custom = MenuHelper::create_menu_action(title_safe_area_menu, "titlesafecustom", &olive::MenuHelper, SLOT(set_titlesafe_from_menu())); + title_safe_custom->setCheckable(true); + title_safe_custom->setData(-1.0); + title_safe_group->addAction(title_safe_custom); + + view_menu->addSeparator(); + + full_screen = MenuHelper::create_menu_action(view_menu, "fullscreen", this, SLOT(toggle_full_screen()), QKeySequence("F11")); + full_screen->setCheckable(true); + + full_screen_viewer_ = MenuHelper::create_menu_action(view_menu, "fullscreenviewer", &olive::FocusFilter, SLOT(set_viewer_fullscreen())); + + // INITIALIZE PLAYBACK MENU + + playback_menu = MenuHelper::create_submenu(menuBar, this, SLOT(playbackMenu_About_To_Be_Shown())); + + go_to_start_ = MenuHelper::create_menu_action(playback_menu, "gotostart", &olive::FocusFilter, SLOT(go_to_start()), QKeySequence("Home")); + previous_frame_ = MenuHelper::create_menu_action(playback_menu, "prevframe", &olive::FocusFilter, SLOT(prev_frame()), QKeySequence("Left")); + playpause_ = MenuHelper::create_menu_action(playback_menu, "playpause", &olive::FocusFilter, SLOT(playpause()), QKeySequence("Space")); + play_in_to_out_ = MenuHelper::create_menu_action(playback_menu, "playintoout", &olive::FocusFilter, SLOT(play_in_to_out()), QKeySequence("Shift+Space")); + next_frame_ = MenuHelper::create_menu_action(playback_menu, "nextframe", &olive::FocusFilter, SLOT(next_frame()), QKeySequence("Right")); + go_to_end_ = MenuHelper::create_menu_action(playback_menu, "gotoend", &olive::FocusFilter, SLOT(go_to_end()), QKeySequence("End")); + + playback_menu->addSeparator(); + + 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(); + + go_to_in_point_ = MenuHelper::create_menu_action(playback_menu, "gotoin", &olive::FocusFilter, SLOT(go_to_in()), QKeySequence("Shift+I")); + go_to_out_point_ = MenuHelper::create_menu_action(playback_menu, "gotoout", &olive::FocusFilter, SLOT(go_to_out()), QKeySequence("Shift+O")); + + playback_menu->addSeparator(); + + shuttle_left_ = MenuHelper::create_menu_action(playback_menu, "decspeed", &olive::FocusFilter, SLOT(decrease_speed()), QKeySequence("J")); + shuttle_stop_ = MenuHelper::create_menu_action(playback_menu, "pause", &olive::FocusFilter, SLOT(pause()), QKeySequence("K")); + shuttle_right_ = MenuHelper::create_menu_action(playback_menu, "incspeed", &olive::FocusFilter, SLOT(increase_speed()), QKeySequence("L")); + + playback_menu->addSeparator(); + + 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::config.loop)); + + // INITIALIZE WINDOW MENU + + window_menu = MenuHelper::create_submenu(menuBar, this, SLOT(windowMenu_About_To_Be_Shown())); + + 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.first())); + + window_effectcontrols_action = MenuHelper::create_menu_action(window_menu, "paneleffectcontrols", this, SLOT(toggle_panel_visibility())); + window_effectcontrols_action->setCheckable(true); + window_effectcontrols_action->setData(reinterpret_cast(panel_effect_controls)); + + 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.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)); + + window_sequenceviewer_action = MenuHelper::create_menu_action(window_menu, "panelsequenceviewer", this, SLOT(toggle_panel_visibility())); + window_sequenceviewer_action->setCheckable(true); + window_sequenceviewer_action->setData(reinterpret_cast(panel_sequence_viewer)); + + window_menu->addSeparator(); + + maximize_panel_ = MenuHelper::create_menu_action(window_menu, "maximizepanel", this, SLOT(maximize_panel()), QKeySequence("`")); + + lock_panels_ = MenuHelper::create_menu_action(window_menu, "lockpanels", this, SLOT(set_panels_locked(bool))); + lock_panels_->setCheckable(true); + + window_menu->addSeparator(); + + reset_default_layout_ = MenuHelper::create_menu_action(window_menu, "resetdefaultlayout", this, SLOT(reset_layout())); + + // INITIALIZE TOOLS MENU + + tools_menu = MenuHelper::create_submenu(menuBar, this, SLOT(toolMenu_About_To_Be_Shown())); + tools_menu->setToolTipsVisible(true); + + QActionGroup* tools_group = new QActionGroup(this); + + 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.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.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.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.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.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.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.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.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.first()->snappingButton)); + + tools_menu->addSeparator(); + + autocut_silence_ = MenuHelper::create_menu_action(tools_menu, "autocutsilence", olive::Global.get(), SLOT(open_autocut_silence_dialog())); + + tools_menu->addSeparator(); + + QActionGroup* autoscroll_group = new QActionGroup(this); + + no_autoscroll = MenuHelper::create_menu_action(tools_menu, "autoscrollno", &olive::MenuHelper, SLOT(set_autoscroll())); + no_autoscroll->setData(olive::AUTOSCROLL_NO_SCROLL); + no_autoscroll->setCheckable(true); + autoscroll_group->addAction(no_autoscroll); + + page_autoscroll = MenuHelper::create_menu_action(tools_menu, "autoscrollpage", &olive::MenuHelper, SLOT(set_autoscroll())); + page_autoscroll->setData(olive::AUTOSCROLL_PAGE_SCROLL); + page_autoscroll->setCheckable(true); + autoscroll_group->addAction(page_autoscroll); + + smooth_autoscroll = MenuHelper::create_menu_action(tools_menu, "autoscrollsmooth", &olive::MenuHelper, SLOT(set_autoscroll())); + smooth_autoscroll->setData(olive::AUTOSCROLL_SMOOTH_SCROLL); + smooth_autoscroll->setCheckable(true); + autoscroll_group->addAction(smooth_autoscroll); + + tools_menu->addSeparator(); + + preferences_action_ = MenuHelper::create_menu_action(tools_menu, "prefs", olive::Global.get(), SLOT(open_preferences()), QKeySequence("Ctrl+,")); + +#ifdef QT_DEBUG + clear_undo_action_ = MenuHelper::create_menu_action(tools_menu, "clearundo", olive::Global.get(), SLOT(clear_undo_stack())); +#endif + + // INITIALIZE HELP MENU + + help_menu = MenuHelper::create_submenu(menuBar); + + action_search_ = MenuHelper::create_menu_action(help_menu, "actionsearch", olive::Global.get(), SLOT(open_action_search()), QKeySequence("/")); + + help_menu->addSeparator(); + + debug_log_ = MenuHelper::create_menu_action(help_menu, "debuglog", olive::Global.get(), SLOT(open_debug_log())); + + help_menu->addSeparator(); + + about_action_ = MenuHelper::create_menu_action(help_menu, "about", olive::Global.get(), SLOT(open_about_dialog())); + + load_shortcuts(get_config_path() + "/shortcuts"); +} + +void MainWindow::Retranslate() +{ + file_menu->setTitle(tr("&File")); + new_menu->setTitle(tr("&New")); + open_project->setText(tr("&Open Project")); + clear_open_recent_action->setText(tr("Clear Recent List")); + open_recent->setTitle(tr("Open Recent")); + save_project->setText(tr("&Save Project")); + save_project_as->setText(tr("Save Project &As")); + import_action->setText(tr("&Import...")); + export_action->setText(tr("&Export...")); + exit_action->setText(tr("E&xit")); + + edit_menu->setTitle(tr("&Edit")); + undo_action->setText(tr("&Undo")); + redo_action->setText(tr("Redo")); + select_all_action->setText(tr("Select &All")); + deselect_all_action->setText(tr("Deselect All")); + ripple_to_in_point_->setText(tr("Ripple to In Point")); + ripple_to_out_point_->setText(tr("Ripple to Out Point")); + edit_to_in_point_->setText(tr("Edit to In Point")); + edit_to_out_point_->setText(tr("Edit to Out Point")); + delete_inout_point_->setText(tr("Delete In/Out Point")); + ripple_delete_inout_point_->setText(tr("Ripple Delete In/Out Point")); + setedit_marker_->setText(tr("Set/Edit Marker")); + + view_menu->setTitle(tr("&View")); + zoom_in_->setText(tr("Zoom In")); + zoom_out_->setText(tr("Zoom Out")); + increase_track_height_->setText(tr("Increase Track Height")); + decrease_track_height_->setText(tr("Decrease Track Height")); + show_all->setText(tr("Toggle Show All")); + rectified_waveforms->setText(tr("Rectified Waveforms")); + frames_action->setText(tr("Frames")); + drop_frame_action->setText(tr("Drop Frame")); + nondrop_frame_action->setText(tr("Non-Drop Frame")); + milliseconds_action->setText(tr("Milliseconds")); + + title_safe_area_menu->setTitle(tr("Title/Action Safe Area")); + title_safe_off->setText(tr("Off")); + title_safe_default->setText(tr("Default")); + title_safe_43->setText(tr("4:3")); + title_safe_169->setText(tr("16:9")); + title_safe_custom->setText(tr("Custom")); + + full_screen->setText(tr("Full Screen")); + full_screen_viewer_->setText(tr("Full Screen Viewer")); + + playback_menu->setTitle(tr("&Playback")); + go_to_start_->setText(tr("Go to Start")); + previous_frame_->setText(tr("Previous Frame")); + playpause_->setText(tr("Play/Pause")); + play_in_to_out_->setText(tr("Play In to Out")); + next_frame_->setText(tr("Next Frame")); + go_to_end_->setText(tr("Go to End")); + + go_to_prev_cut_->setText(tr("Go to Previous Cut")); + go_to_next_cut_->setText(tr("Go to Next Cut")); + go_to_in_point_->setText(tr("Go to In Point")); + go_to_out_point_->setText(tr("Go to Out Point")); + + shuttle_left_->setText(tr("Shuttle Left")); + shuttle_stop_->setText(tr("Shuttle Stop")); + shuttle_right_->setText(tr("Shuttle Right")); + + loop_action_->setText(tr("Loop")); + + window_menu->setTitle(tr("&Window")); + + window_project_action->setText(tr("Project")); + 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")); + + maximize_panel_->setText(tr("Maximize Panel")); + lock_panels_->setText(tr("Lock Panels")); + reset_default_layout_->setText(tr("Reset to Default Layout")); + + tools_menu->setTitle(tr("&Tools")); + + pointer_tool_action->setText(tr("Pointer Tool")); + edit_tool_action->setText(tr("Edit Tool")); + ripple_tool_action->setText(tr("Ripple Tool")); + razor_tool_action->setText(tr("Razor Tool")); + slip_tool_action->setText(tr("Slip Tool")); + slide_tool_action->setText(tr("Slide Tool")); + hand_tool_action->setText(tr("Hand Tool")); + transition_tool_action->setText(tr("Transition Tool")); + snap_toggle->setText(tr("Enable Snapping")); + autocut_silence_->setText(tr("Auto-Cut Silence")); + + no_autoscroll->setText(tr("No Auto-Scroll")); + page_autoscroll->setText(tr("Page Auto-Scroll")); + smooth_autoscroll->setText(tr("Smooth Auto-Scroll")); + + preferences_action_->setText(tr("Preferences")); +#ifdef QT_DEBUG + clear_undo_action_->setText(tr("Clear Undo")); +#endif + + help_menu->setTitle(tr("&Help")); + + action_search_->setText(tr("A&ction Search")); + debug_log_->setText(tr("Debug Log")); + about_action_->setText(tr("&About...")); + + panel_sequence_viewer->set_panel_name(QCoreApplication::translate("Viewer", "Sequence Viewer: %1")); + panel_footage_viewer->set_panel_name(QCoreApplication::translate("Viewer", "Media Viewer: %1")); + + // the recommended changeEvent() and event() methods of propagating language change messages provided mixed results + // (i.e. different panels failed to translate in different sessions), so we translate them manually here + for (int i=0;iRetranslate(); + } + olive::MenuHelper.Retranslate(); + + updateTitle(); +} + +void MainWindow::updateTitle() { + setWindowTitle(QString("%1 - %2[*]").arg(olive::AppName, + (olive::ActiveProjectFilename.isEmpty()) ? + tr("") : olive::ActiveProjectFilename) + ); +} + +void MainWindow::closeEvent(QCloseEvent *e) { + if (olive::Global->can_close_project()) { + // stop proxy generator thread + olive::proxy_generator.cancel(); + + panel_graph_editor->set_row(nullptr); + panel_effect_controls->Clear(true); + + panel_footage_viewer->viewer_widget()->close_window(); + panel_sequence_viewer->viewer_widget()->close_window(); + + 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")); + } + } + if (!config_path.isEmpty()) { + QDir config_dir = QDir(config_path); + + QString config_fn = config_dir.filePath("config.xml"); + + // save settings + olive::config.save(config_fn); + + // save panel layout + QFile panel_config(get_config_dir().filePath("layout")); + if (panel_config.open(QFile::WriteOnly)) { + QXmlStreamWriter stream(&panel_config); + stream.setAutoFormatting(true); + stream.writeStartDocument(); + + stream.writeStartElement("layout"); + + stream.writeTextElement("panels", saveState(0).toBase64()); + + // if the panels have any specific layout data to save, save it now + for (int i=0;iSaveLayoutState(); + + if (!layout_data.isEmpty()) { + + // layout data is matched with the panel's objectName(), which we can't do if the panel has no name + const QString& panel_name = olive::panels.at(i)->objectName(); + if (panel_name.isEmpty()) { + qWarning() << "Panel" << i << "had layout state data but no objectName(). Layout was not saved."; + } else { + stream.writeStartElement("panel"); + + stream.writeAttribute("name", panel_name); + + stream.writeCharacters(layout_data.toBase64()); + + stream.writeEndElement(); + } + } + } + + stream.writeEndElement(); // layout + + stream.writeEndDocument(); + panel_config.close(); + } else { + qCritical() << "Failed to save layout"; + } + + save_shortcuts(config_path + "/shortcuts"); + } + + stop_audio(); + + e->accept(); + } else { + e->ignore(); + } +} + +void MainWindow::paintEvent(QPaintEvent *event) { + QMainWindow::paintEvent(event); + + if (first_show) { + // set this to false immediately to prevent anything here being called again + first_show = false; + + /** + * @brief Set up the dock widget layout on the main window + * + * For some reason, Qt didn't like this in the constructor. It would lead to several geometry issues with HiDPI + * on Windows, and also seemed to break QMainWindow::restoreState() which is why it took so long to implement + * saving/restoring panel layouts. Putting it in showEvent() didn't help either, nor did putting it in + * changeEvent() (QEvent::type() == QEvent::Polish). This is the only place it's functioned as expected. + */ + setup_layout(false); + + /** + Signal that window has finished loading. + */ + emit finished_first_paint(); + } +} + +void MainWindow::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + + // if this was a LanguageEvent, run the retranslation function + Retranslate(); + + } else { + + // otherwise pass it to the base class + QMainWindow::changeEvent(e); + + } +} + +void MainWindow::reset_layout() { + setup_layout(true); +} + +void MainWindow::maximize_panel() { + // toggles between normal state and a state of one panel being maximized + if (temp_panel_state.isEmpty()) { + // get currently hovered panel + QDockWidget* focused_panel = get_focused_panel(true); + + // if the mouse is in fact hovering over a panel + if (focused_panel != nullptr) { + // store the current state of panels + temp_panel_state = saveState(); + + // remove all dock widgets that aren't the hovered panel + for (int i=0;isetVisible(false); + + // set it to floating + olive::panels.at(i)->setFloating(true); + } + } + } + } else { + // we must be maximized, restore previous state + restoreState(temp_panel_state); + + // clear temp panel state for next maximize call + temp_panel_state.clear(); + } +} + +void MainWindow::windowMenu_About_To_Be_Shown() { + QList window_actions = window_menu->actions(); + for (int i=0;idata().isNull()) { + a->setChecked(reinterpret_cast(a->data().value())->isVisible()); + } + } + + lock_panels_->setChecked(olive::config.locked_panels); +} + +void MainWindow::playbackMenu_About_To_Be_Shown() { + olive::MenuHelper.set_bool_action_checked(loop_action_); +} + +void MainWindow::viewMenu_About_To_Be_Shown() { + olive::MenuHelper.set_bool_action_checked(rectified_waveforms); + + 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::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.first()->showing_all); +} + +void MainWindow::toolMenu_About_To_Be_Shown() { + olive::MenuHelper.set_button_action_checked(pointer_tool_action); + olive::MenuHelper.set_button_action_checked(edit_tool_action); + olive::MenuHelper.set_button_action_checked(ripple_tool_action); + olive::MenuHelper.set_button_action_checked(razor_tool_action); + olive::MenuHelper.set_button_action_checked(slip_tool_action); + olive::MenuHelper.set_button_action_checked(slide_tool_action); + olive::MenuHelper.set_button_action_checked(hand_tool_action); + 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::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() { + QAction* action = static_cast(sender()); + QDockWidget* w = reinterpret_cast(action->data().value()); + w->setVisible(!w->isVisible()); + + // layout has changed, we're no longer in maximized panel mode, + // so we clear this byte array + temp_panel_state.clear(); +} + +void MainWindow::set_panels_locked(bool locked) +{ + for (int i=0;isetFeatures(panel->features() & ~QDockWidget::DockWidgetMovable); + + // hide the title bar (only real way to do this is to replace it with an empty QWidget) + panel->setTitleBarWidget(new QWidget(panel)); + } else { + // re-enable moving on QDockWidget + panel->setFeatures(panel->features() | QDockWidget::DockWidgetMovable); + + // set the "custom" titlebar to null so the default gets restored + panel->setTitleBarWidget(nullptr); + } + } + + olive::config.locked_panels = locked; +} + +void MainWindow::fileMenu_About_To_Be_Shown() { + if (olive::Global->recent_project_count() > 0) { + open_recent->clear(); + open_recent->setEnabled(true); + 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())); + } + open_recent->addSeparator(); + + open_recent->addAction(clear_open_recent_action); + } else { + open_recent->setEnabled(false); + } +} + +void MainWindow::toggle_full_screen() { + if (windowState() == Qt::WindowFullScreen) { + setWindowState(Qt::WindowNoState); // seems to be necessary for it to return to Maximized correctly on Linux + setWindowState(Qt::WindowMaximized); + } else { + setWindowState(Qt::WindowFullScreen); + } +} diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 722e575e5..9bb0c2c45 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -1,353 +1,353 @@ -/*** - - 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 MAINWINDOW_H -#define MAINWINDOW_H - -#include - -class Project; -class EffectControls; -class Viewer; -class Timeline; - -class MainWindow : public QMainWindow { - Q_OBJECT -public: - explicit MainWindow(QWidget *parent); - virtual ~MainWindow() override; - - /** - * @brief Update window title - * - * Updates the window title to reflect the current project filename. Call if the project filename changes. - * - * NOTE: It's recommended to use update_project_filename() from Olive::Global to update the filename completely - * instead of calling this function directly (update_project_filename() calls this function in the process). - */ - void updateTitle(); - - /** - * @brief Load shortcut file. - * - * Loads a shortcut configuration from file and sets Olive to use them. - * - * @param fn - * - * URL of the shortcut file to be loaded - * - */ - void load_shortcuts(const QString &fn); - - /** - * @brief Save shortcut file. - * - * Saves the current shortcut configuration to file. Only saves shortcuts that have been changed from default. - * - * @param fn - * - * URL to save the shortcut file to. - */ - void save_shortcuts(const QString &fn); - - /** - * @brief Load a CSS/QSS style from file to customize Olive's interface. - * - * @param fn - * - * URL to load the CSS file from. - * - * @return - * - * **TRUE** if CSS was successfully loaded. - */ - bool load_css_from_file(const QString& fn); - - /** - * @brief Set application's QStyle based on values from Config - */ - void Restyle(); - -public slots: - /** - * @brief Toggles full screen mode. - * - * Toggles the main window between full screen and windowed modes. - */ - void toggle_full_screen(); - -signals: - /** - * @brief Signal emitted once when the main window has finished initializing - * - * Emitted the first time paintEvent runs. Connect this to functions that must be completed post-initialization. - */ - void finished_first_paint(); - -protected: - /** - * @brief Close event - * - * Confirms whether the project can be closed, and if so performs various clean-up functions before the application - * exits. It's preferable to call clean-up functions here rather than in the destructor because this will get called - * first. - */ - virtual void closeEvent(QCloseEvent *) override; - - /** - * @brief Paint event - * - * Overridden to provide the finished_first_paint() signal. - */ - virtual void paintEvent(QPaintEvent *) override; - - /** - * @brief Change event - * - * Overridden to handle language changes (e->type() == QEvent::LanguageChange) by calling Retranslate(). - * - * See documentation for QWidget::changeEvent() for more information. - * - * @param e - * @return - */ - virtual void changeEvent(QEvent* e) override; - -private slots: - /** - * @brief Maximizes the currently hovered panel. - * - * Saves the current state of the panels/dock widgets and removes all except the currently hovered panel, - * effectively maximizing the panel to the entire window. - */ - void maximize_panel(); - - /** - * @brief Reset panel layout to default. - * - * Resets the current panel layout to default. Doesn't save the current layout. - */ - void reset_layout(); - - /** - * @brief Function to prepare File menu. - * - * Primarily used to populate the Open Recent Projects menu. - */ - void fileMenu_About_To_Be_Shown(); - - /** - * @brief Function to prepare Edit menu. - * - * Primarily used to set the enabled state on Undo and Redo depending if there are undos/redos available. - */ - void editMenu_About_To_Be_Shown(); - - /** - * @brief Function to prepare Window menu. - * - * Primarily used to set the checked state of menu items corresponding to the panels that are currently visible. - */ - void windowMenu_About_To_Be_Shown(); - - /** - * @brief Function to prepare Playback menu. - * - * Primarily used to set the checked state on the "Loop" item. - */ - void playbackMenu_About_To_Be_Shown(); - - /** - * @brief Function to prepare View menu. - * - * Primarily used to set the checked state of various options in the view menu (e.g. title safe area, timecode - * units, etc.) - */ - void viewMenu_About_To_Be_Shown(); - - /** - * @brief Function to prepare Tools menu. - * - * Primarily used to set the checked state on various settings available from the Tools menu. - */ - void toolMenu_About_To_Be_Shown(); - - /** - * @brief Toggle whether a panel is visible or not. - * - * Assumes the sender() QAction has a pointer to a QDockWidget in its data variable. Casts it and toggles its - * visibility. - */ - void toggle_panel_visibility(); - - /** - * @brief Set panel lock - * - * @param locked - * - * If **TRUE** prevents panels from being moved around. Defaults to **FALSE**. - */ - void set_panels_locked(bool locked); - -private: - /** - * @brief Internal function for setting the panel layout to a predetermined preset. - * - * Resets layout to default and optionally loads a layout from file. If loading from file, this function will - * always load from `get_config_path() + "/layout"`. - * - * @param reset - * - * **TRUE** if this function should just reset the current layout. **FALSE** if it should load from the - * aforementioned layout file. - */ - void setup_layout(bool reset); - - /** - * @brief Initialize menu bar menus and items. - * - * Internal initialization function for all menus and menu items in the main window. Called once from the - * MainWindow() constructor. - */ - void setup_menus(); - - void Retranslate(); - - // file menu actions - QMenu* file_menu; - QMenu* new_menu; - QAction* open_project; - QMenu* open_recent; - QAction* open_action; - QAction* clear_open_recent_action; - QAction* save_project; - QAction* save_project_as; - QAction* import_action; - QAction* export_action; - QAction* exit_action; - - // edit menu actions - QMenu* edit_menu; - QAction* undo_action; - QAction* redo_action; - QAction* select_all_action; - QAction* deselect_all_action; - QAction* ripple_to_in_point_; - QAction* ripple_to_out_point_; - QAction* edit_to_in_point_; - QAction* edit_to_out_point_; - QAction* delete_inout_point_; - QAction* ripple_delete_inout_point_; - QAction* setedit_marker_; - - // view menu actions - QMenu* view_menu; - QAction* zoom_in_; - QAction* zoom_out_; - QAction* increase_track_height_; - QAction* decrease_track_height_; - QAction* frames_action; - QAction* drop_frame_action; - QAction* nondrop_frame_action; - QAction* milliseconds_action; - QAction* no_autoscroll; - QAction* page_autoscroll; - QAction* smooth_autoscroll; - - QMenu* title_safe_area_menu; - QAction* title_safe_off; - QAction* title_safe_default; - QAction* title_safe_43; - QAction* title_safe_169; - QAction* title_safe_custom; - - QAction* full_screen; - QAction* full_screen_viewer_; - QAction* show_all; - - // playback menu - QMenu* playback_menu; - - QAction* go_to_start_; - QAction* previous_frame_; - QAction* playpause_; - QAction* play_in_to_out_; - QAction* next_frame_; - QAction* go_to_end_; - QAction* go_to_prev_cut_; - QAction* go_to_next_cut_; - QAction* go_to_in_point_; - QAction* go_to_out_point_; - QAction* shuttle_left_; - QAction* shuttle_stop_; - QAction* shuttle_right_; - QAction* loop_action_; - - // window menu - - QMenu* window_menu; - - QAction* window_project_action; - 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; - - QAction* maximize_panel_; - QAction* lock_panels_; - QAction* reset_default_layout_; - - // tools menu - QMenu* tools_menu; - - QAction* pointer_tool_action; - QAction* edit_tool_action; - QAction* ripple_tool_action; - QAction* razor_tool_action; - QAction* slip_tool_action; - QAction* slide_tool_action; - QAction* hand_tool_action; - QAction* transition_tool_action; - QAction* snap_toggle; - QAction* rectified_waveforms; - QAction* autocut_silence_; - QAction* preferences_action_; - QAction* clear_undo_action_; - - // help menu - QMenu* help_menu; - QAction* action_search_; - QAction* debug_log_; - QAction* about_action_; - - // used to store the panel state when one panel is maximized - QByteArray temp_panel_state; - - // used in paintEvent() to determine the first paintEvent() performed - bool first_show; -}; - -namespace olive { -extern MainWindow* MainWindow; -} - -#endif // MAINWINDOW_H +/*** + + 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include + +class Project; +class EffectControls; +class Viewer; +class Timeline; + +class MainWindow : public QMainWindow { + Q_OBJECT +public: + explicit MainWindow(QWidget *parent); + virtual ~MainWindow() override; + + /** + * @brief Update window title + * + * Updates the window title to reflect the current project filename. Call if the project filename changes. + * + * NOTE: It's recommended to use update_project_filename() from Olive::Global to update the filename completely + * instead of calling this function directly (update_project_filename() calls this function in the process). + */ + void updateTitle(); + + /** + * @brief Load shortcut file. + * + * Loads a shortcut configuration from file and sets Olive to use them. + * + * @param fn + * + * URL of the shortcut file to be loaded + * + */ + void load_shortcuts(const QString &fn); + + /** + * @brief Save shortcut file. + * + * Saves the current shortcut configuration to file. Only saves shortcuts that have been changed from default. + * + * @param fn + * + * URL to save the shortcut file to. + */ + void save_shortcuts(const QString &fn); + + /** + * @brief Load a CSS/QSS style from file to customize Olive's interface. + * + * @param fn + * + * URL to load the CSS file from. + * + * @return + * + * **TRUE** if CSS was successfully loaded. + */ + bool load_css_from_file(const QString& fn); + + /** + * @brief Set application's QStyle based on values from Config + */ + void Restyle(); + +public slots: + /** + * @brief Toggles full screen mode. + * + * Toggles the main window between full screen and windowed modes. + */ + void toggle_full_screen(); + +signals: + /** + * @brief Signal emitted once when the main window has finished initializing + * + * Emitted the first time paintEvent runs. Connect this to functions that must be completed post-initialization. + */ + void finished_first_paint(); + +protected: + /** + * @brief Close event + * + * Confirms whether the project can be closed, and if so performs various clean-up functions before the application + * exits. It's preferable to call clean-up functions here rather than in the destructor because this will get called + * first. + */ + virtual void closeEvent(QCloseEvent *) override; + + /** + * @brief Paint event + * + * Overridden to provide the finished_first_paint() signal. + */ + virtual void paintEvent(QPaintEvent *) override; + + /** + * @brief Change event + * + * Overridden to handle language changes (e->type() == QEvent::LanguageChange) by calling Retranslate(). + * + * See documentation for QWidget::changeEvent() for more information. + * + * @param e + * @return + */ + virtual void changeEvent(QEvent* e) override; + +private slots: + /** + * @brief Maximizes the currently hovered panel. + * + * Saves the current state of the panels/dock widgets and removes all except the currently hovered panel, + * effectively maximizing the panel to the entire window. + */ + void maximize_panel(); + + /** + * @brief Reset panel layout to default. + * + * Resets the current panel layout to default. Doesn't save the current layout. + */ + void reset_layout(); + + /** + * @brief Function to prepare File menu. + * + * Primarily used to populate the Open Recent Projects menu. + */ + void fileMenu_About_To_Be_Shown(); + + /** + * @brief Function to prepare Edit menu. + * + * Primarily used to set the enabled state on Undo and Redo depending if there are undos/redos available. + */ + void editMenu_About_To_Be_Shown(); + + /** + * @brief Function to prepare Window menu. + * + * Primarily used to set the checked state of menu items corresponding to the panels that are currently visible. + */ + void windowMenu_About_To_Be_Shown(); + + /** + * @brief Function to prepare Playback menu. + * + * Primarily used to set the checked state on the "Loop" item. + */ + void playbackMenu_About_To_Be_Shown(); + + /** + * @brief Function to prepare View menu. + * + * Primarily used to set the checked state of various options in the view menu (e.g. title safe area, timecode + * units, etc.) + */ + void viewMenu_About_To_Be_Shown(); + + /** + * @brief Function to prepare Tools menu. + * + * Primarily used to set the checked state on various settings available from the Tools menu. + */ + void toolMenu_About_To_Be_Shown(); + + /** + * @brief Toggle whether a panel is visible or not. + * + * Assumes the sender() QAction has a pointer to a QDockWidget in its data variable. Casts it and toggles its + * visibility. + */ + void toggle_panel_visibility(); + + /** + * @brief Set panel lock + * + * @param locked + * + * If **TRUE** prevents panels from being moved around. Defaults to **FALSE**. + */ + void set_panels_locked(bool locked); + +private: + /** + * @brief Internal function for setting the panel layout to a predetermined preset. + * + * Resets layout to default and optionally loads a layout from file. If loading from file, this function will + * always load from `get_config_path() + "/layout"`. + * + * @param reset + * + * **TRUE** if this function should just reset the current layout. **FALSE** if it should load from the + * aforementioned layout file. + */ + void setup_layout(bool reset); + + /** + * @brief Initialize menu bar menus and items. + * + * Internal initialization function for all menus and menu items in the main window. Called once from the + * MainWindow() constructor. + */ + void setup_menus(); + + void Retranslate(); + + // file menu actions + QMenu* file_menu; + QMenu* new_menu; + QAction* open_project; + QMenu* open_recent; + QAction* open_action; + QAction* clear_open_recent_action; + QAction* save_project; + QAction* save_project_as; + QAction* import_action; + QAction* export_action; + QAction* exit_action; + + // edit menu actions + QMenu* edit_menu; + QAction* undo_action; + QAction* redo_action; + QAction* select_all_action; + QAction* deselect_all_action; + QAction* ripple_to_in_point_; + QAction* ripple_to_out_point_; + QAction* edit_to_in_point_; + QAction* edit_to_out_point_; + QAction* delete_inout_point_; + QAction* ripple_delete_inout_point_; + QAction* setedit_marker_; + + // view menu actions + QMenu* view_menu; + QAction* zoom_in_; + QAction* zoom_out_; + QAction* increase_track_height_; + QAction* decrease_track_height_; + QAction* frames_action; + QAction* drop_frame_action; + QAction* nondrop_frame_action; + QAction* milliseconds_action; + QAction* no_autoscroll; + QAction* page_autoscroll; + QAction* smooth_autoscroll; + + QMenu* title_safe_area_menu; + QAction* title_safe_off; + QAction* title_safe_default; + QAction* title_safe_43; + QAction* title_safe_169; + QAction* title_safe_custom; + + QAction* full_screen; + QAction* full_screen_viewer_; + QAction* show_all; + + // playback menu + QMenu* playback_menu; + + QAction* go_to_start_; + QAction* previous_frame_; + QAction* playpause_; + QAction* play_in_to_out_; + QAction* next_frame_; + QAction* go_to_end_; + QAction* go_to_prev_cut_; + QAction* go_to_next_cut_; + QAction* go_to_in_point_; + QAction* go_to_out_point_; + QAction* shuttle_left_; + QAction* shuttle_stop_; + QAction* shuttle_right_; + QAction* loop_action_; + + // window menu + + QMenu* window_menu; + + QAction* window_project_action; + 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; + + QAction* maximize_panel_; + QAction* lock_panels_; + QAction* reset_default_layout_; + + // tools menu + QMenu* tools_menu; + + QAction* pointer_tool_action; + QAction* edit_tool_action; + QAction* ripple_tool_action; + QAction* razor_tool_action; + QAction* slip_tool_action; + QAction* slide_tool_action; + QAction* hand_tool_action; + QAction* transition_tool_action; + QAction* snap_toggle; + QAction* rectified_waveforms; + QAction* autocut_silence_; + QAction* preferences_action_; + QAction* clear_undo_action_; + + // help menu + QMenu* help_menu; + QAction* action_search_; + QAction* debug_log_; + QAction* about_action_; + + // used to store the panel state when one panel is maximized + QByteArray temp_panel_state; + + // used in paintEvent() to determine the first paintEvent() performed + bool first_show; +}; + +namespace olive { +extern MainWindow* MainWindow; +} + +#endif // MAINWINDOW_H diff --git a/ui/mediaiconservice.cpp b/ui/mediaiconservice.cpp index 955d31db0..ff8483891 100644 --- a/ui/mediaiconservice.cpp +++ b/ui/mediaiconservice.cpp @@ -1,105 +1,105 @@ -/*** - - 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 "mediaiconservice.h" - -const int kThrobberLimit = 20; -const int kThrobberSize = 50; - -#include "project/projectmodel.h" -#include "ui/icons.h" - -std::unique_ptr olive::media_icon_service; - -MediaIconService::MediaIconService() { - // set up animation timer - throbber_animator_.setInterval(20); - connect(&throbber_animator_, SIGNAL(timeout()), this, SLOT(AnimationUpdate())); - - // set up pixmap - throbber_pixmap_ = QPixmap(":/icons/throbber.png"); -} - -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(); - - // if we aren't animating anything, no need to run the timer for now - if (throbber_items_.empty()) { - // ensure timer function is called in its own thread - QMetaObject::invokeMethod(&throbber_animator_, "stop", Qt::QueuedConnection); - } - } - - switch (icon_type) { - case ICON_TYPE_VIDEO: - olive::project_model.set_icon(media, olive::icon::MediaVideo); - break; - case ICON_TYPE_AUDIO: - olive::project_model.set_icon(media, olive::icon::MediaAudio); - break; - case ICON_TYPE_IMAGE: - olive::project_model.set_icon(media, olive::icon::MediaImage); - break; - 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 - throbber_animation_frame_ = 0; - - // ensure timer function is called in its own thread - QMetaObject::invokeMethod(&throbber_animator_, "start", Qt::QueuedConnection); - } - break; - case ICON_TYPE_ERROR: - olive::project_model.set_icon(media, olive::icon::MediaError); - break; - } - - emit IconChanged(); -} - -void MediaIconService::AnimationUpdate() { - if (throbber_animation_frame_ == kThrobberLimit) { - throbber_animation_frame_ = 0; - } - - QIcon throbber_ico = QIcon(throbber_pixmap_.copy(kThrobberSize*throbber_animation_frame_, 0, kThrobberSize, kThrobberSize)); - - throbber_lock_.lock(); - - for (int i=0;i. + +***/ + +#include "mediaiconservice.h" + +const int kThrobberLimit = 20; +const int kThrobberSize = 50; + +#include "project/projectmodel.h" +#include "ui/icons.h" + +std::unique_ptr olive::media_icon_service; + +MediaIconService::MediaIconService() { + // set up animation timer + throbber_animator_.setInterval(20); + connect(&throbber_animator_, SIGNAL(timeout()), this, SLOT(AnimationUpdate())); + + // set up pixmap + throbber_pixmap_ = QPixmap(":/icons/throbber.png"); +} + +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(); + + // if we aren't animating anything, no need to run the timer for now + if (throbber_items_.empty()) { + // ensure timer function is called in its own thread + QMetaObject::invokeMethod(&throbber_animator_, "stop", Qt::QueuedConnection); + } + } + + switch (icon_type) { + case ICON_TYPE_VIDEO: + olive::project_model.set_icon(media, olive::icon::MediaVideo); + break; + case ICON_TYPE_AUDIO: + olive::project_model.set_icon(media, olive::icon::MediaAudio); + break; + case ICON_TYPE_IMAGE: + olive::project_model.set_icon(media, olive::icon::MediaImage); + break; + 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 + throbber_animation_frame_ = 0; + + // ensure timer function is called in its own thread + QMetaObject::invokeMethod(&throbber_animator_, "start", Qt::QueuedConnection); + } + break; + case ICON_TYPE_ERROR: + olive::project_model.set_icon(media, olive::icon::MediaError); + break; + } + + emit IconChanged(); +} + +void MediaIconService::AnimationUpdate() { + if (throbber_animation_frame_ == kThrobberLimit) { + throbber_animation_frame_ = 0; + } + + QIcon throbber_ico = QIcon(throbber_pixmap_.copy(kThrobberSize*throbber_animation_frame_, 0, kThrobberSize, kThrobberSize)); + + throbber_lock_.lock(); + + for (int i=0;i. - -***/ - -#ifndef MEDIAICONSERVICE_H -#define MEDIAICONSERVICE_H - -#include -#include -#include - -#include "project/media.h" - -enum IconType { - ICON_TYPE_VIDEO, - ICON_TYPE_AUDIO, - ICON_TYPE_IMAGE, - ICON_TYPE_LOADING, - ICON_TYPE_ERROR -}; - -class MediaIconService : public QObject { - Q_OBJECT -public: - MediaIconService(); -public slots: - void SetMediaIcon(Media* media, IconType icon_type); -signals: - void IconChanged(); -private slots: - void AnimationUpdate(); -private: - int throbber_animation_frame_; - QVector throbber_items_; - QTimer throbber_animator_; - QPixmap throbber_pixmap_; - QMutex throbber_lock_; -}; - -namespace olive { -extern std::unique_ptr media_icon_service; -} - -#endif // MEDIAICONSERVICE_H +/*** + + 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 MEDIAICONSERVICE_H +#define MEDIAICONSERVICE_H + +#include +#include +#include + +#include "project/media.h" + +enum IconType { + ICON_TYPE_VIDEO, + ICON_TYPE_AUDIO, + ICON_TYPE_IMAGE, + ICON_TYPE_LOADING, + ICON_TYPE_ERROR +}; + +class MediaIconService : public QObject { + Q_OBJECT +public: + MediaIconService(); +public slots: + void SetMediaIcon(Media* media, IconType icon_type); +signals: + void IconChanged(); +private slots: + void AnimationUpdate(); +private: + int throbber_animation_frame_; + QVector throbber_items_; + QTimer throbber_animator_; + QPixmap throbber_pixmap_; + QMutex throbber_lock_; +}; + +namespace olive { +extern std::unique_ptr media_icon_service; +} + +#endif // MEDIAICONSERVICE_H diff --git a/ui/menu.cpp b/ui/menu.cpp index a77652794..f81973be7 100644 --- a/ui/menu.cpp +++ b/ui/menu.cpp @@ -1,40 +1,40 @@ -/*** - - 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" -#include "global/config.h" - -Menu::Menu(QWidget *parent) : - QMenu(parent) -{ - if (olive::config.use_native_menu_styling) { - OliveGlobal::SetNativeStyling(this); - } -} - -Menu::Menu(const QString &title, QWidget *parent) : - QMenu(title, parent) -{ - if (olive::config.use_native_menu_styling) { - OliveGlobal::SetNativeStyling(this); - } -} +/*** + + 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" +#include "global/config.h" + +Menu::Menu(QWidget *parent) : + QMenu(parent) +{ + if (olive::config.use_native_menu_styling) { + OliveGlobal::SetNativeStyling(this); + } +} + +Menu::Menu(const QString &title, QWidget *parent) : + QMenu(title, parent) +{ + if (olive::config.use_native_menu_styling) { + OliveGlobal::SetNativeStyling(this); + } +} diff --git a/ui/menu.h b/ui/menu.h index a4f36bdc0..f7e542a09 100644 --- a/ui/menu.h +++ b/ui/menu.h @@ -1,33 +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 . - -***/ - -#ifndef MENU_H -#define MENU_H - -#include - -class Menu : public QMenu -{ -public: - Menu(QWidget* parent = nullptr); - Menu(const QString &title, QWidget *parent = nullptr); -}; - -#endif // MENU_H +/*** + + 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 + +#include + +class Menu : public QMenu +{ +public: + Menu(QWidget* parent = nullptr); + Menu(const QString &title, QWidget *parent = nullptr); +}; + +#endif // MENU_H diff --git a/ui/menuhelper.cpp b/ui/menuhelper.cpp index e4a38c280..4ad3359db 100644 --- a/ui/menuhelper.cpp +++ b/ui/menuhelper.cpp @@ -1,325 +1,325 @@ -/*** - - 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 "menuhelper.h" - -#include -#include -#include -#include - -#include "global/config.h" -#include "global/clipboard.h" -#include "ui/mainwindow.h" -#include "global/global.h" -#include "panels/panels.h" -#include "ui/focusfilter.h" - -MenuHelper olive::MenuHelper; - -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", 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.first(), SLOT(new_folder())); - new_folder_->setParent(this); - - set_in_point_ = create_menu_action(nullptr, "setinpoint", &olive::FocusFilter, SLOT(set_in_point()), QKeySequence("I")); - set_in_point_->setParent(this); - - set_out_point_ = create_menu_action(nullptr, "setoutpoint", &olive::FocusFilter, SLOT(set_out_point()), QKeySequence("O")); - set_out_point_->setParent(this); - - reset_in_point_ = create_menu_action(nullptr, "resetin", &olive::FocusFilter, SLOT(clear_in())); - reset_in_point_->setParent(this); - - reset_out_point_ = create_menu_action(nullptr, "resetout", &olive::FocusFilter, SLOT(clear_out())); - reset_out_point_->setParent(this); - - 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.first(), SLOT(add_transition()), QKeySequence("Ctrl+Shift+D")); - add_default_transition_->setParent(this); - - 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.first(), SLOT(toggle_enable_on_selected_clips()), QKeySequence("Shift+E")); - enable_disable_->setParent(this); - - 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")); - cut_->setParent(this); - - copy_ = create_menu_action(nullptr, "copy", &olive::FocusFilter, SLOT(copy()), QKeySequence("Ctrl+C")); - copy_->setParent(this); - - paste_ = create_menu_action(nullptr, "paste", olive::Global.get(), SLOT(paste()), QKeySequence("Ctrl+V")); - paste_->setParent(this); - - paste_insert_ = create_menu_action(nullptr, "pasteinsert", olive::Global.get(), SLOT(paste_insert()), QKeySequence("Ctrl+Shift+V")); - paste_insert_->setParent(this); - - duplicate_ = create_menu_action(nullptr, "duplicate", &olive::FocusFilter, SLOT(duplicate()), QKeySequence("Ctrl+D")); - duplicate_->setParent(this); - - 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.first(), SLOT(ripple_delete()), QKeySequence("Shift+Del")); - ripple_delete_->setParent(this); - - split_ = create_menu_action(nullptr, "split", panel_timeline.first(), SLOT(split_at_playhead()), QKeySequence("Ctrl+K")); - split_->setParent(this); - - Retranslate(); -} - -void MenuHelper::make_new_menu(QMenu *parent) { - parent->addAction(new_project_); - parent->addSeparator(); - parent->addAction(new_sequence_); - parent->addAction(new_folder_); -} - -void MenuHelper::make_inout_menu(QMenu *parent) { - parent->addAction(set_in_point_); - parent->addAction(set_out_point_); - parent->addSeparator(); - parent->addAction(reset_in_point_); - parent->addAction(reset_out_point_); - parent->addAction(clear_inout_point); -} - -void MenuHelper::make_clip_functions_menu(QMenu *parent) { - parent->addAction(add_default_transition_); - parent->addAction(link_unlink_); - parent->addAction(enable_disable_); - parent->addAction(nest_); -} - -void MenuHelper::make_edit_functions_menu(QMenu *parent, bool objects_are_selected) { - if (objects_are_selected) { - parent->addAction(cut_); - parent->addAction(copy_); - } - - parent->addAction(paste_); - parent->addAction(paste_insert_); - - if (objects_are_selected) { - parent->addAction(duplicate_); - parent->addAction(delete_); - parent->addAction(ripple_delete_); - parent->addAction(split_); - } -} - -void MenuHelper::set_bool_action_checked(QAction *a) { - if (!a->data().isNull()) { - bool* variable = reinterpret_cast(a->data().value()); - a->setChecked(*variable); - } -} - -void MenuHelper::set_int_action_checked(QAction *a, const int& i) { - if (!a->data().isNull()) { - a->setChecked(a->data() == i); - } -} - -void MenuHelper::set_button_action_checked(QAction *a) { - a->setChecked(reinterpret_cast(a->data().value())->isChecked()); -} - -void MenuHelper::Retranslate() -{ - new_project_->setText(tr("&Project")); - new_sequence_->setText(tr("&Sequence")); - new_folder_->setText(tr("&Folder")); - set_in_point_->setText(tr("Set In Point")); - set_out_point_->setText(tr("Set Out Point")); - reset_in_point_->setText(tr("Reset In Point")); - reset_out_point_->setText(tr("Reset Out Point")); - clear_inout_point->setText(tr("Clear In/Out Point")); - add_default_transition_->setText(tr("Add Default Transition")); - link_unlink_->setText(tr("Link/Unlink")); - enable_disable_->setText(tr("Enable/Disable")); - nest_->setText(tr("Nest")); - cut_->setText(tr("Cu&t")); - copy_->setText(tr("Cop&y")); - paste_->setText(tr("&Paste")); - paste_insert_->setText(tr("Paste Insert")); - duplicate_->setText(tr("Duplicate")); - delete_->setText(tr("Delete")); - ripple_delete_->setText(tr("Ripple Delete")); - split_->setText(tr("Split")); -} - -void MenuHelper::toggle_bool_action() { - QAction* action = static_cast(sender()); - bool* variable = reinterpret_cast(action->data().value()); - *variable = !(*variable); - update_ui(false); -} - -void MenuHelper::set_titlesafe_from_menu() { - double tsa = static_cast(sender())->data().toDouble(); - - if (qIsNaN(tsa)) { - - // disable title safe area - olive::config.show_title_safe_area = false; - - } else { - - // using title safe area - 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::config.use_custom_title_safe_ratio = false; - - } else { - - // using a specific aspect ratio - olive::config.use_custom_title_safe_ratio = true; - - if (tsa < 0.0) { - - // set a custom title safe area - QString input; - bool invalid = false; - QRegExp arTest("[0-9.]+:[0-9.]+"); - - do { - if (invalid) { - QMessageBox::critical(olive::MainWindow, tr("Invalid aspect ratio"), tr("The aspect ratio '%1' is invalid. Please try again.").arg(input)); - } - - input = QInputDialog::getText(olive::MainWindow, tr("Enter custom aspect ratio"), tr("Enter the aspect ratio to use for the title/action safe area (e.g. 16:9):")); - invalid = !arTest.exactMatch(input) && !input.isEmpty(); - } while (invalid); - - if (!input.isEmpty()) { - QStringList inputList = input.split(':'); - olive::config.custom_title_safe_ratio = inputList.at(0).toDouble()/inputList.at(1).toDouble(); - } - - } else { - - // specified tsa is a specific custom aspect ratio - olive::config.custom_title_safe_ratio = tsa; - } - - } - - } - - panel_sequence_viewer->viewer_widget()->update(); -} - -void MenuHelper::set_autoscroll() { - QAction* action = static_cast(sender()); - olive::config.autoscroll = action->data().toInt(); -} - -void MenuHelper::menu_click_button() { - reinterpret_cast(static_cast(sender())->data().value())->click(); -} - -void MenuHelper::set_timecode_view() { - QAction* action = static_cast(sender()); - olive::config.timecode_view = action->data().toInt(); - update_ui(false); -} - -void MenuHelper::open_recent_from_menu() { - int index = static_cast(sender())->data().toInt(); - olive::Global.get()->open_recent(index); -} - -void MenuHelper::create_effect_paste_action(QMenu *menu) -{ - 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, - const QObject *receiver, - const char *member) { - Menu* menu = new Menu(parent); - - /* - menu->setStyle(QStyleFactory::create("windowsvista")); - menu->setPalette(menu->style()->standardPalette()); - menu->setStyleSheet(""); - */ - - parent->addMenu(menu); - - if (receiver != nullptr) { - QObject::connect(menu, SIGNAL(aboutToShow()), receiver, member); - } - - return menu; -} - -Menu* MenuHelper::create_submenu(QMenu* parent) { - Menu* menu = new Menu(parent); - - /* - menu->setStyle(QStyleFactory::create("windowsvista")); - menu->setPalette(menu->style()->standardPalette()); - menu->setStyleSheet(""); - */ - - parent->addMenu(menu); - return menu; -} - -QAction* MenuHelper::create_menu_action(QWidget *parent, - const char* id, - const QObject *receiver, - const char *member, - const QKeySequence &shortcut) { - QAction* action = new QAction(parent); - action->setProperty("id", id); - action->setShortcut(shortcut); - - if (receiver != nullptr) { - QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); - } - - if (parent != nullptr) { - parent->addAction(action); - } - - return action; -} +/*** + + 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 "menuhelper.h" + +#include +#include +#include +#include + +#include "global/config.h" +#include "global/clipboard.h" +#include "ui/mainwindow.h" +#include "global/global.h" +#include "panels/panels.h" +#include "ui/focusfilter.h" + +MenuHelper olive::MenuHelper; + +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", 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.first(), SLOT(new_folder())); + new_folder_->setParent(this); + + set_in_point_ = create_menu_action(nullptr, "setinpoint", &olive::FocusFilter, SLOT(set_in_point()), QKeySequence("I")); + set_in_point_->setParent(this); + + set_out_point_ = create_menu_action(nullptr, "setoutpoint", &olive::FocusFilter, SLOT(set_out_point()), QKeySequence("O")); + set_out_point_->setParent(this); + + reset_in_point_ = create_menu_action(nullptr, "resetin", &olive::FocusFilter, SLOT(clear_in())); + reset_in_point_->setParent(this); + + reset_out_point_ = create_menu_action(nullptr, "resetout", &olive::FocusFilter, SLOT(clear_out())); + reset_out_point_->setParent(this); + + 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.first(), SLOT(add_transition()), QKeySequence("Ctrl+Shift+D")); + add_default_transition_->setParent(this); + + 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.first(), SLOT(toggle_enable_on_selected_clips()), QKeySequence("Shift+E")); + enable_disable_->setParent(this); + + 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")); + cut_->setParent(this); + + copy_ = create_menu_action(nullptr, "copy", &olive::FocusFilter, SLOT(copy()), QKeySequence("Ctrl+C")); + copy_->setParent(this); + + paste_ = create_menu_action(nullptr, "paste", olive::Global.get(), SLOT(paste()), QKeySequence("Ctrl+V")); + paste_->setParent(this); + + paste_insert_ = create_menu_action(nullptr, "pasteinsert", olive::Global.get(), SLOT(paste_insert()), QKeySequence("Ctrl+Shift+V")); + paste_insert_->setParent(this); + + duplicate_ = create_menu_action(nullptr, "duplicate", &olive::FocusFilter, SLOT(duplicate()), QKeySequence("Ctrl+D")); + duplicate_->setParent(this); + + 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.first(), SLOT(ripple_delete()), QKeySequence("Shift+Del")); + ripple_delete_->setParent(this); + + split_ = create_menu_action(nullptr, "split", panel_timeline.first(), SLOT(split_at_playhead()), QKeySequence("Ctrl+K")); + split_->setParent(this); + + Retranslate(); +} + +void MenuHelper::make_new_menu(QMenu *parent) { + parent->addAction(new_project_); + parent->addSeparator(); + parent->addAction(new_sequence_); + parent->addAction(new_folder_); +} + +void MenuHelper::make_inout_menu(QMenu *parent) { + parent->addAction(set_in_point_); + parent->addAction(set_out_point_); + parent->addSeparator(); + parent->addAction(reset_in_point_); + parent->addAction(reset_out_point_); + parent->addAction(clear_inout_point); +} + +void MenuHelper::make_clip_functions_menu(QMenu *parent) { + parent->addAction(add_default_transition_); + parent->addAction(link_unlink_); + parent->addAction(enable_disable_); + parent->addAction(nest_); +} + +void MenuHelper::make_edit_functions_menu(QMenu *parent, bool objects_are_selected) { + if (objects_are_selected) { + parent->addAction(cut_); + parent->addAction(copy_); + } + + parent->addAction(paste_); + parent->addAction(paste_insert_); + + if (objects_are_selected) { + parent->addAction(duplicate_); + parent->addAction(delete_); + parent->addAction(ripple_delete_); + parent->addAction(split_); + } +} + +void MenuHelper::set_bool_action_checked(QAction *a) { + if (!a->data().isNull()) { + bool* variable = reinterpret_cast(a->data().value()); + a->setChecked(*variable); + } +} + +void MenuHelper::set_int_action_checked(QAction *a, const int& i) { + if (!a->data().isNull()) { + a->setChecked(a->data() == i); + } +} + +void MenuHelper::set_button_action_checked(QAction *a) { + a->setChecked(reinterpret_cast(a->data().value())->isChecked()); +} + +void MenuHelper::Retranslate() +{ + new_project_->setText(tr("&Project")); + new_sequence_->setText(tr("&Sequence")); + new_folder_->setText(tr("&Folder")); + set_in_point_->setText(tr("Set In Point")); + set_out_point_->setText(tr("Set Out Point")); + reset_in_point_->setText(tr("Reset In Point")); + reset_out_point_->setText(tr("Reset Out Point")); + clear_inout_point->setText(tr("Clear In/Out Point")); + add_default_transition_->setText(tr("Add Default Transition")); + link_unlink_->setText(tr("Link/Unlink")); + enable_disable_->setText(tr("Enable/Disable")); + nest_->setText(tr("Nest")); + cut_->setText(tr("Cu&t")); + copy_->setText(tr("Cop&y")); + paste_->setText(tr("&Paste")); + paste_insert_->setText(tr("Paste Insert")); + duplicate_->setText(tr("Duplicate")); + delete_->setText(tr("Delete")); + ripple_delete_->setText(tr("Ripple Delete")); + split_->setText(tr("Split")); +} + +void MenuHelper::toggle_bool_action() { + QAction* action = static_cast(sender()); + bool* variable = reinterpret_cast(action->data().value()); + *variable = !(*variable); + update_ui(false); +} + +void MenuHelper::set_titlesafe_from_menu() { + double tsa = static_cast(sender())->data().toDouble(); + + if (qIsNaN(tsa)) { + + // disable title safe area + olive::config.show_title_safe_area = false; + + } else { + + // using title safe area + 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::config.use_custom_title_safe_ratio = false; + + } else { + + // using a specific aspect ratio + olive::config.use_custom_title_safe_ratio = true; + + if (tsa < 0.0) { + + // set a custom title safe area + QString input; + bool invalid = false; + QRegExp arTest("[0-9.]+:[0-9.]+"); + + do { + if (invalid) { + QMessageBox::critical(olive::MainWindow, tr("Invalid aspect ratio"), tr("The aspect ratio '%1' is invalid. Please try again.").arg(input)); + } + + input = QInputDialog::getText(olive::MainWindow, tr("Enter custom aspect ratio"), tr("Enter the aspect ratio to use for the title/action safe area (e.g. 16:9):")); + invalid = !arTest.exactMatch(input) && !input.isEmpty(); + } while (invalid); + + if (!input.isEmpty()) { + QStringList inputList = input.split(':'); + olive::config.custom_title_safe_ratio = inputList.at(0).toDouble()/inputList.at(1).toDouble(); + } + + } else { + + // specified tsa is a specific custom aspect ratio + olive::config.custom_title_safe_ratio = tsa; + } + + } + + } + + panel_sequence_viewer->viewer_widget()->update(); +} + +void MenuHelper::set_autoscroll() { + QAction* action = static_cast(sender()); + olive::config.autoscroll = action->data().toInt(); +} + +void MenuHelper::menu_click_button() { + reinterpret_cast(static_cast(sender())->data().value())->click(); +} + +void MenuHelper::set_timecode_view() { + QAction* action = static_cast(sender()); + olive::config.timecode_view = action->data().toInt(); + update_ui(false); +} + +void MenuHelper::open_recent_from_menu() { + int index = static_cast(sender())->data().toInt(); + olive::Global.get()->open_recent(index); +} + +void MenuHelper::create_effect_paste_action(QMenu *menu) +{ + 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, + const QObject *receiver, + const char *member) { + Menu* menu = new Menu(parent); + + /* + menu->setStyle(QStyleFactory::create("windowsvista")); + menu->setPalette(menu->style()->standardPalette()); + menu->setStyleSheet(""); + */ + + parent->addMenu(menu); + + if (receiver != nullptr) { + QObject::connect(menu, SIGNAL(aboutToShow()), receiver, member); + } + + return menu; +} + +Menu* MenuHelper::create_submenu(QMenu* parent) { + Menu* menu = new Menu(parent); + + /* + menu->setStyle(QStyleFactory::create("windowsvista")); + menu->setPalette(menu->style()->standardPalette()); + menu->setStyleSheet(""); + */ + + parent->addMenu(menu); + return menu; +} + +QAction* MenuHelper::create_menu_action(QWidget *parent, + const char* id, + const QObject *receiver, + const char *member, + const QKeySequence &shortcut) { + QAction* action = new QAction(parent); + action->setProperty("id", id); + action->setShortcut(shortcut); + + if (receiver != nullptr) { + QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); + } + + if (parent != nullptr) { + parent->addAction(action); + } + + return action; +} diff --git a/ui/menuhelper.h b/ui/menuhelper.h index d568c3862..73c71ad36 100644 --- a/ui/menuhelper.h +++ b/ui/menuhelper.h @@ -1,241 +1,241 @@ -/*** - - 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 MENUHELPER_H -#define MENUHELPER_H - -#include -#include - -#include "ui/menu.h" - -class MenuHelper : public QObject { - Q_OBJECT -public: - void InitializeSharedMenus(); - - /** - * @brief Creates a menu of new items that can be created - * - * Adds the full set of creatable items to a QMenu (e.g. new project, - * new sequence, new folder, etc.) - * - * @param parent - * - * The menu to add items to. - */ - void make_new_menu(QMenu* parent); - - /** - * @brief Creates a menu of options for working with in/out points - * - * Adds a set of options for working with sequence/footage in/out points, - * e.g. setting in/out points, clearing in/out points, etc. - * - * @param parent - * - * The menu to add items to. - */ - void make_inout_menu(QMenu* parent); - - /** - * @brief Creates a menu of clip functions - * - * Adds a set of clip functions including: - * * Add Default Transition - * * Link/Unlink - * * Enable/Disable - * * Nest - * - * @param parent - * - * The menu to add items to. - */ - void make_clip_functions_menu(QMenu* parent); - - /** - * @brief Creates standard edit menu (cut, copy, paste, etc.) - * - * @param parent - * - * The menu to add items to. - * - * @param objects_are_selected - * - * Some extra functions may be hidden in the event no clip is actually selected. Set this to **FALSE** to hide those - * functions. - */ - void make_edit_functions_menu(QMenu* parent, bool objects_are_selected = true); - - /** - * @brief Sets the checked state of a menu item based on a Boolean variable. - * - * Many menu items simply toggle a Boolean variable. This is a convenience function, assuming the QAction's data - * variable is a pointer to a Boolean variable, that sets the checked state of the QAction to the enabled state - * of the Boolean. Used heavily in functions like toolMenu_About_To_Be_Shown() - * - * @param a - * - * The QAction to set the checked state of. - */ - void set_bool_action_checked(QAction* a); - - /** - * @brief Sets the checked state of a menu item based on an integer variable. - * - * Many menu items simply set a variable to a particular integer. This is a convenience function, assuming the - * QAction's data variable is an integer to set a variable to, that sets the checked state of the QAction to - * whether the QAction's integer equals the integer variable. Used heavily in functions like - * viewMenu_About_To_Be_Shown() - * - * @param a - * - * The QAction to set the checked state of - * - * @param i - * - * The integer variable to compare the QAction's integer to - */ - void set_int_action_checked(QAction* a, const int& i); - - /** - * @brief Sets the checked state of a menu item based on a QPushButton. - * - * Some menu items function largely as a proxy to a QPushButton. Assuming the QAction's data variable is a - * pointer to a QPushButton, this sets a QAction's checked state to the checked state of the QPushButton. - * - * @param a - */ - void set_button_action_checked(QAction* a); - - void Retranslate(); - - static Menu *create_submenu(QMenuBar* parent, - const QObject *receiver = nullptr, - const char *member = nullptr); - static Menu* create_submenu(QMenu* parent); - static QAction* create_menu_action(QWidget *parent, - const char* id, - const QObject *receiver = nullptr, - const char *member = nullptr, - const QKeySequence &shortcut = 0); - -public slots: - - /** - * @brief Sets a QAction's Boolean reference to the opposite of its current value - * - * Many menu items simply toggle a Boolean variable. This is a convenience function, assuming the QAction's data - * variable is a pointer to a Boolean variable, that sets the Boolean variable to the opposite of its current value. - */ - void toggle_bool_action(); - - /** - * @brief Set Title/Action Safe Area from QAction - * - * A receiver for several Title/Action Safe Area setting items. Assumes the sender() is a QAction with a data - * variable as a `double`. The `double` can be the following values: - * * NaN (qSNaN()) - Disable Title/Action Safe Area - * * 0 - Enable Title/Action Safe Area, default aspect ratio (match current active Sequence's aspect ratio). - * * Negative Value - Enable Title/Action Safe Area, any negative number assumes a custom aspect ratio. Will ask - * the user to enter an aspect ratio and will use the result. - * * Positive Value - Enable Title/Action Safe Area, use value as the aspect ratio. - */ - void set_titlesafe_from_menu(); - - /** - * @brief Set Autoscroll setting from QAction - * - * Assumes the sender() is a QAction with an integer as its data variable. The data variable should be - * `AUTOSCROLL_NO_SCROLL`, `AUTOSCROLL_PAGE_SCROLL` (default) or `AUTOSCROLL_SMOOTH_SCROLL`. - */ - void set_autoscroll(); - - /** - * @brief Clicks a QPushButton referenced by a QAction when triggered. - * - * Some menu items function largely as a proxy to a QPushButton. Assuming the QAction's data variable is a - * pointer to a QPushButton, this triggers a click() event on that QPushButton. - */ - void menu_click_button(); - - /** - * @brief Sets the current timecode setting - * - * Assumes the sender() is a QAction with an integer as its data variable. The data variable should be - * `AUTOSCROLL_NO_AUTOSCROLL`, `AUTOSCROLL_PAGE_AUTOSCROLL` (default) or `AUTOSCROLL_SMOOTH_AUTOSCROLL`. - */ - void set_timecode_view(); - - /** - * @brief Calls open_recent() in Olive::Global using the index from a QAction - * - * Assumes the sender() is a QAction with an integer as its data variable. The data variable is an index of - * the internal auto-recovery project list. - */ - void open_recent_from_menu(); - - /** - * @brief Create a "Paste" action on the specified menu that's enabled only if the clipboard contains effects - * - * @param menu - * - * Menu to add action to - */ - void create_effect_paste_action(QMenu *menu); - -private: - QAction* new_project_; - QAction* new_sequence_; - QAction* new_folder_; - - QAction* set_in_point_; - QAction* set_out_point_; - QAction* reset_in_point_; - QAction* reset_out_point_; - QAction* clear_inout_point; - - QAction* add_default_transition_; - QAction* link_unlink_; - QAction* enable_disable_; - QAction* nest_; - - QAction* cut_; - QAction* copy_; - QAction* paste_; - QAction* paste_insert_; - QAction* duplicate_; - QAction* delete_; - QAction* ripple_delete_; - QAction* split_; - -private slots: - - -}; - -namespace olive { -/** - * @brief A global MenuHelper object to assist menu creation throughout Olive. - */ -extern MenuHelper MenuHelper; -}; - -#endif // MENUHELPER_H +/*** + + 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 MENUHELPER_H +#define MENUHELPER_H + +#include +#include + +#include "ui/menu.h" + +class MenuHelper : public QObject { + Q_OBJECT +public: + void InitializeSharedMenus(); + + /** + * @brief Creates a menu of new items that can be created + * + * Adds the full set of creatable items to a QMenu (e.g. new project, + * new sequence, new folder, etc.) + * + * @param parent + * + * The menu to add items to. + */ + void make_new_menu(QMenu* parent); + + /** + * @brief Creates a menu of options for working with in/out points + * + * Adds a set of options for working with sequence/footage in/out points, + * e.g. setting in/out points, clearing in/out points, etc. + * + * @param parent + * + * The menu to add items to. + */ + void make_inout_menu(QMenu* parent); + + /** + * @brief Creates a menu of clip functions + * + * Adds a set of clip functions including: + * * Add Default Transition + * * Link/Unlink + * * Enable/Disable + * * Nest + * + * @param parent + * + * The menu to add items to. + */ + void make_clip_functions_menu(QMenu* parent); + + /** + * @brief Creates standard edit menu (cut, copy, paste, etc.) + * + * @param parent + * + * The menu to add items to. + * + * @param objects_are_selected + * + * Some extra functions may be hidden in the event no clip is actually selected. Set this to **FALSE** to hide those + * functions. + */ + void make_edit_functions_menu(QMenu* parent, bool objects_are_selected = true); + + /** + * @brief Sets the checked state of a menu item based on a Boolean variable. + * + * Many menu items simply toggle a Boolean variable. This is a convenience function, assuming the QAction's data + * variable is a pointer to a Boolean variable, that sets the checked state of the QAction to the enabled state + * of the Boolean. Used heavily in functions like toolMenu_About_To_Be_Shown() + * + * @param a + * + * The QAction to set the checked state of. + */ + void set_bool_action_checked(QAction* a); + + /** + * @brief Sets the checked state of a menu item based on an integer variable. + * + * Many menu items simply set a variable to a particular integer. This is a convenience function, assuming the + * QAction's data variable is an integer to set a variable to, that sets the checked state of the QAction to + * whether the QAction's integer equals the integer variable. Used heavily in functions like + * viewMenu_About_To_Be_Shown() + * + * @param a + * + * The QAction to set the checked state of + * + * @param i + * + * The integer variable to compare the QAction's integer to + */ + void set_int_action_checked(QAction* a, const int& i); + + /** + * @brief Sets the checked state of a menu item based on a QPushButton. + * + * Some menu items function largely as a proxy to a QPushButton. Assuming the QAction's data variable is a + * pointer to a QPushButton, this sets a QAction's checked state to the checked state of the QPushButton. + * + * @param a + */ + void set_button_action_checked(QAction* a); + + void Retranslate(); + + static Menu *create_submenu(QMenuBar* parent, + const QObject *receiver = nullptr, + const char *member = nullptr); + static Menu* create_submenu(QMenu* parent); + static QAction* create_menu_action(QWidget *parent, + const char* id, + const QObject *receiver = nullptr, + const char *member = nullptr, + const QKeySequence &shortcut = 0); + +public slots: + + /** + * @brief Sets a QAction's Boolean reference to the opposite of its current value + * + * Many menu items simply toggle a Boolean variable. This is a convenience function, assuming the QAction's data + * variable is a pointer to a Boolean variable, that sets the Boolean variable to the opposite of its current value. + */ + void toggle_bool_action(); + + /** + * @brief Set Title/Action Safe Area from QAction + * + * A receiver for several Title/Action Safe Area setting items. Assumes the sender() is a QAction with a data + * variable as a `double`. The `double` can be the following values: + * * NaN (qSNaN()) - Disable Title/Action Safe Area + * * 0 - Enable Title/Action Safe Area, default aspect ratio (match current active Sequence's aspect ratio). + * * Negative Value - Enable Title/Action Safe Area, any negative number assumes a custom aspect ratio. Will ask + * the user to enter an aspect ratio and will use the result. + * * Positive Value - Enable Title/Action Safe Area, use value as the aspect ratio. + */ + void set_titlesafe_from_menu(); + + /** + * @brief Set Autoscroll setting from QAction + * + * Assumes the sender() is a QAction with an integer as its data variable. The data variable should be + * `AUTOSCROLL_NO_SCROLL`, `AUTOSCROLL_PAGE_SCROLL` (default) or `AUTOSCROLL_SMOOTH_SCROLL`. + */ + void set_autoscroll(); + + /** + * @brief Clicks a QPushButton referenced by a QAction when triggered. + * + * Some menu items function largely as a proxy to a QPushButton. Assuming the QAction's data variable is a + * pointer to a QPushButton, this triggers a click() event on that QPushButton. + */ + void menu_click_button(); + + /** + * @brief Sets the current timecode setting + * + * Assumes the sender() is a QAction with an integer as its data variable. The data variable should be + * `AUTOSCROLL_NO_AUTOSCROLL`, `AUTOSCROLL_PAGE_AUTOSCROLL` (default) or `AUTOSCROLL_SMOOTH_AUTOSCROLL`. + */ + void set_timecode_view(); + + /** + * @brief Calls open_recent() in Olive::Global using the index from a QAction + * + * Assumes the sender() is a QAction with an integer as its data variable. The data variable is an index of + * the internal auto-recovery project list. + */ + void open_recent_from_menu(); + + /** + * @brief Create a "Paste" action on the specified menu that's enabled only if the clipboard contains effects + * + * @param menu + * + * Menu to add action to + */ + void create_effect_paste_action(QMenu *menu); + +private: + QAction* new_project_; + QAction* new_sequence_; + QAction* new_folder_; + + QAction* set_in_point_; + QAction* set_out_point_; + QAction* reset_in_point_; + QAction* reset_out_point_; + QAction* clear_inout_point; + + QAction* add_default_transition_; + QAction* link_unlink_; + QAction* enable_disable_; + QAction* nest_; + + QAction* cut_; + QAction* copy_; + QAction* paste_; + QAction* paste_insert_; + QAction* duplicate_; + QAction* delete_; + QAction* ripple_delete_; + QAction* split_; + +private slots: + + +}; + +namespace olive { +/** + * @brief A global MenuHelper object to assist menu creation throughout Olive. + */ +extern MenuHelper MenuHelper; +}; + +#endif // MENUHELPER_H diff --git a/ui/panel.cpp b/ui/panel.cpp index 27d80afdf..b8cc85b21 100644 --- a/ui/panel.cpp +++ b/ui/panel.cpp @@ -1,65 +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 . - -***/ - -#include "panel.h" - -#include -#include - -QVector olive::panels; - -Panel::Panel(QWidget *parent) : QDockWidget (parent) { - olive::panels.append(this); -} - -Panel::~Panel() -{ - olive::panels.removeAll(this); -} - -bool Panel::focused() -{ - return hasFocus(); -} - -void Panel::LoadLayoutState(const QByteArray &) {} - -QByteArray Panel::SaveLayoutState() -{ - return QByteArray(); -} - -void Panel::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::LanguageChange) { - /** - - NOTE: While overriding changeEvent() is the official documented way of handling runtime language change events, - I found it buggy to do it this way (some panels would change and others wouldn't, and the panels that did/didn't - change would be different each time). The current workaround is calling Retranslate() on each panel manually - from MainWindow::Retranslate which is triggered by its own changeEvent() that seems fairly reliable. Currently - this function is mostly a no-op. - - */ -// Retranslate(); - } else { - QDockWidget::changeEvent(e); - } -} +/*** + + 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 "panel.h" + +#include +#include + +QVector olive::panels; + +Panel::Panel(QWidget *parent) : QDockWidget (parent) { + olive::panels.append(this); +} + +Panel::~Panel() +{ + olive::panels.removeAll(this); +} + +bool Panel::focused() +{ + return hasFocus(); +} + +void Panel::LoadLayoutState(const QByteArray &) {} + +QByteArray Panel::SaveLayoutState() +{ + return QByteArray(); +} + +void Panel::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + /** + + NOTE: While overriding changeEvent() is the official documented way of handling runtime language change events, + I found it buggy to do it this way (some panels would change and others wouldn't, and the panels that did/didn't + change would be different each time). The current workaround is calling Retranslate() on each panel manually + from MainWindow::Retranslate which is triggered by its own changeEvent() that seems fairly reliable. Currently + this function is mostly a no-op. + + */ +// Retranslate(); + } else { + QDockWidget::changeEvent(e); + } +} diff --git a/ui/panel.h b/ui/panel.h index 4c97ee178..28c2210c3 100644 --- a/ui/panel.h +++ b/ui/panel.h @@ -1,46 +1,46 @@ -/*** - - 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 PANEL_H -#define PANEL_H - -#include - -class Panel : public QDockWidget { - Q_OBJECT -public: - Panel(QWidget* parent = nullptr); - virtual ~Panel() override; - - virtual void Retranslate() = 0; - - virtual bool focused(); - - virtual void LoadLayoutState(const QByteArray& data); - virtual QByteArray SaveLayoutState(); -protected: - virtual void changeEvent(QEvent* e) override; -}; - -namespace olive { - extern QVector panels; -} - -#endif // PANEL_H +/*** + + 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 PANEL_H +#define PANEL_H + +#include + +class Panel : public QDockWidget { + Q_OBJECT +public: + Panel(QWidget* parent = nullptr); + virtual ~Panel() override; + + virtual void Retranslate() = 0; + + virtual bool focused(); + + virtual void LoadLayoutState(const QByteArray& data); + virtual QByteArray SaveLayoutState(); +protected: + virtual void changeEvent(QEvent* e) override; +}; + +namespace olive { + extern QVector panels; +} + +#endif // PANEL_H diff --git a/ui/playbutton.cpp b/ui/playbutton.cpp index 4a2eea08b..ffe2e925a 100644 --- a/ui/playbutton.cpp +++ b/ui/playbutton.cpp @@ -1,9 +1,9 @@ -#include "playbutton.h" - -PlayButton::PlayButton(QWidget* parent) : QPushButton(parent) -{ - play_text = ">"; - pause_text = "||"; - - setText(play_text); -} +#include "playbutton.h" + +PlayButton::PlayButton(QWidget* parent) : QPushButton(parent) +{ + play_text = ">"; + pause_text = "||"; + + setText(play_text); +} diff --git a/ui/playbutton.h b/ui/playbutton.h index 3dc8b5a88..b78d7deb7 100644 --- a/ui/playbutton.h +++ b/ui/playbutton.h @@ -1,15 +1,15 @@ -#ifndef PLAYBUTTON_H -#define PLAYBUTTON_H - -#include - -class PlayButton : public QPushButton -{ -public: - PlayButton(QWidget* parent = 0); -private: - QString play_text; - QString pause_text; -}; - -#endif // PLAYBUTTON_H +#ifndef PLAYBUTTON_H +#define PLAYBUTTON_H + +#include + +class PlayButton : public QPushButton +{ +public: + PlayButton(QWidget* parent = 0); +private: + QString play_text; + QString pause_text; +}; + +#endif // PLAYBUTTON_H diff --git a/ui/rectangleselect.cpp b/ui/rectangleselect.cpp index 8c865ebf2..4df214de9 100644 --- a/ui/rectangleselect.cpp +++ b/ui/rectangleselect.cpp @@ -1,27 +1,27 @@ -/*** - - 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 "rectangleselect.h" - -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); -} +/*** + + 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 "rectangleselect.h" + +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 10764ffa9..cc784dfe5 100644 --- a/ui/rectangleselect.h +++ b/ui/rectangleselect.h @@ -1,45 +1,45 @@ -/*** - - 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 RECTANGLESELECT_H -#define RECTANGLESELECT_H - -#include - -namespace olive { -namespace ui { - -/** - * @brief Routine for drawing a drag selection rectangle for any given QPainter - * - * @param painter - * - * QPainter object to use for drawing - * - * @param rect - * - * Rectangle to draw - */ -void DrawSelectionRectangle(QPainter& painter, const QRect& rect); - -} -} - -#endif // RECTANGLESELECT_H +/*** + + 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 RECTANGLESELECT_H +#define RECTANGLESELECT_H + +#include + +namespace olive { +namespace ui { + +/** + * @brief Routine for drawing a drag selection rectangle for any given QPainter + * + * @param painter + * + * QPainter object to use for drawing + * + * @param rect + * + * Rectangle to draw + */ +void DrawSelectionRectangle(QPainter& painter, const QRect& rect); + +} +} + +#endif // RECTANGLESELECT_H diff --git a/ui/resizablescrollbar.cpp b/ui/resizablescrollbar.cpp index a339a0c9f..1fab95d8a 100644 --- a/ui/resizablescrollbar.cpp +++ b/ui/resizablescrollbar.cpp @@ -1,126 +1,126 @@ -/*** - - 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 "resizablescrollbar.h" - -#include -#include -#include - -#include "global/debug.h" - -#define RESIZE_HANDLE_SIZE 10 - -ResizableScrollBar::ResizableScrollBar(QWidget *parent) : - QScrollBar(parent), - resize_init(false), - resize_proc(false) -{ - setSingleStep(20); - setMaximum(0); - setMouseTracking(true); -} - -bool ResizableScrollBar::is_resizing() { - return resize_proc; -} - -void ResizableScrollBar::resizeEvent(QResizeEvent *event) { - setPageStep(event->size().width()); -} - -void ResizableScrollBar::mousePressEvent(QMouseEvent *e) { - if (resize_init) { - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, - QStyle::SC_ScrollBarSlider, this); - - resize_proc = true; - resize_start = e->pos().x(); - - resize_start_max = maximum(); - resize_start_width = sr.width(); - } else { - QScrollBar::mousePressEvent(e); - } -} - -void ResizableScrollBar::mouseMoveEvent(QMouseEvent *e) { - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, - QStyle::SC_ScrollBarSlider, this); - QRect gr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, - QStyle::SC_ScrollBarGroove, this); - - if (resize_proc) { - int diff = (e->pos().x() - resize_start); - if (resize_top) diff = -diff; - double scale = double(sr.width())/double(sr.width()+diff); - if (!qIsInf(scale) && !qIsNull(scale)) { - emit resize_move(scale); - resize_start = e->pos().x(); - - if (resize_top) { - int slider_min = gr.x(); - int slider_max = gr.right() - (sr.width()+diff); - int val = QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x() - slider_min, slider_max - slider_min, opt.upsideDown); - - setValue(val); - } else { - setValue(qRound(value() * scale)); - } - } - } else { - bool new_resize_init = false; - - if ((orientation() == Qt::Horizontal && e->pos().x() > sr.left()-RESIZE_HANDLE_SIZE && e->pos().x() < sr.left()+RESIZE_HANDLE_SIZE) - || (orientation() == Qt::Vertical && e->pos().y() > sr.top()-RESIZE_HANDLE_SIZE && e->pos().y() < sr.top()+RESIZE_HANDLE_SIZE)) { - new_resize_init = true; - resize_top = true; - } else if ((orientation() == Qt::Horizontal && e->pos().x() > sr.right()-RESIZE_HANDLE_SIZE && e->pos().x() < sr.right()+RESIZE_HANDLE_SIZE) - || (orientation() == Qt::Vertical && e->pos().y() > sr.bottom()-RESIZE_HANDLE_SIZE && e->pos().y() < sr.bottom()+RESIZE_HANDLE_SIZE)) { - new_resize_init = true; - resize_top = false; - } - - if (resize_init != new_resize_init) { - if (new_resize_init) { - setCursor(Qt::SizeHorCursor); - } else { - unsetCursor(); - } - resize_init = new_resize_init; - } - - QScrollBar::mouseMoveEvent(e); - } -} - -void ResizableScrollBar::mouseReleaseEvent(QMouseEvent *e) { - if (resize_proc) { - resize_proc = false; - } else { - QScrollBar::mouseReleaseEvent(e); - } -} +/*** + + 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 "resizablescrollbar.h" + +#include +#include +#include + +#include "global/debug.h" + +#define RESIZE_HANDLE_SIZE 10 + +ResizableScrollBar::ResizableScrollBar(QWidget *parent) : + QScrollBar(parent), + resize_init(false), + resize_proc(false) +{ + setSingleStep(20); + setMaximum(0); + setMouseTracking(true); +} + +bool ResizableScrollBar::is_resizing() { + return resize_proc; +} + +void ResizableScrollBar::resizeEvent(QResizeEvent *event) { + setPageStep(event->size().width()); +} + +void ResizableScrollBar::mousePressEvent(QMouseEvent *e) { + if (resize_init) { + QStyleOptionSlider opt; + initStyleOption(&opt); + + QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarSlider, this); + + resize_proc = true; + resize_start = e->pos().x(); + + resize_start_max = maximum(); + resize_start_width = sr.width(); + } else { + QScrollBar::mousePressEvent(e); + } +} + +void ResizableScrollBar::mouseMoveEvent(QMouseEvent *e) { + QStyleOptionSlider opt; + initStyleOption(&opt); + + QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarSlider, this); + QRect gr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarGroove, this); + + if (resize_proc) { + int diff = (e->pos().x() - resize_start); + if (resize_top) diff = -diff; + double scale = double(sr.width())/double(sr.width()+diff); + if (!qIsInf(scale) && !qIsNull(scale)) { + emit resize_move(scale); + resize_start = e->pos().x(); + + if (resize_top) { + int slider_min = gr.x(); + int slider_max = gr.right() - (sr.width()+diff); + int val = QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x() - slider_min, slider_max - slider_min, opt.upsideDown); + + setValue(val); + } else { + setValue(qRound(value() * scale)); + } + } + } else { + bool new_resize_init = false; + + if ((orientation() == Qt::Horizontal && e->pos().x() > sr.left()-RESIZE_HANDLE_SIZE && e->pos().x() < sr.left()+RESIZE_HANDLE_SIZE) + || (orientation() == Qt::Vertical && e->pos().y() > sr.top()-RESIZE_HANDLE_SIZE && e->pos().y() < sr.top()+RESIZE_HANDLE_SIZE)) { + new_resize_init = true; + resize_top = true; + } else if ((orientation() == Qt::Horizontal && e->pos().x() > sr.right()-RESIZE_HANDLE_SIZE && e->pos().x() < sr.right()+RESIZE_HANDLE_SIZE) + || (orientation() == Qt::Vertical && e->pos().y() > sr.bottom()-RESIZE_HANDLE_SIZE && e->pos().y() < sr.bottom()+RESIZE_HANDLE_SIZE)) { + new_resize_init = true; + resize_top = false; + } + + if (resize_init != new_resize_init) { + if (new_resize_init) { + setCursor(Qt::SizeHorCursor); + } else { + unsetCursor(); + } + resize_init = new_resize_init; + } + + QScrollBar::mouseMoveEvent(e); + } +} + +void ResizableScrollBar::mouseReleaseEvent(QMouseEvent *e) { + if (resize_proc) { + resize_proc = false; + } else { + QScrollBar::mouseReleaseEvent(e); + } +} diff --git a/ui/resizablescrollbar.h b/ui/resizablescrollbar.h index 571ada493..f7abc04d1 100644 --- a/ui/resizablescrollbar.h +++ b/ui/resizablescrollbar.h @@ -1,49 +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 RESIZABLESCROLLBAR_H -#define RESIZABLESCROLLBAR_H - -#include - -class ResizableScrollBar : public QScrollBar -{ - Q_OBJECT -public: - ResizableScrollBar(QWidget * parent = nullptr); - bool is_resizing(); -signals: - void resize_move(double i); -protected: - 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; - - int resize_start_max; - int resize_start_width; -}; - -#endif // RESIZABLESCROLLBAR_H +/*** + + 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 RESIZABLESCROLLBAR_H +#define RESIZABLESCROLLBAR_H + +#include + +class ResizableScrollBar : public QScrollBar +{ + Q_OBJECT +public: + ResizableScrollBar(QWidget * parent = nullptr); + bool is_resizing(); +signals: + void resize_move(double i); +protected: + 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; + + int resize_start_max; + int resize_start_width; +}; + +#endif // RESIZABLESCROLLBAR_H diff --git a/ui/sourceiconview.cpp b/ui/sourceiconview.cpp index d4f1ecceb..39b689536 100644 --- a/ui/sourceiconview.cpp +++ b/ui/sourceiconview.cpp @@ -1,215 +1,215 @@ -/*** - - 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 "sourceiconview.h" - -#include -#include - -#include "panels/project.h" -#include "project/media.h" -#include "project/sourcescommon.h" -#include "global/debug.h" -#include "global/math.h" - -SourceIconView::SourceIconView(SourcesCommon &commons) : - commons_(commons) -{ - setMovement(QListView::Free); - setSelectionMode(QAbstractItemView::ExtendedSelection); - setResizeMode(QListView::Adjust); - setContextMenuPolicy(Qt::CustomContextMenu); - setItemDelegate(&delegate_); - connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); -} - -void SourceIconView::show_context_menu() { - commons_.show_context_menu(this, selectedIndexes()); -} - -void SourceIconView::item_click(const QModelIndex& index) { - if (selectedIndexes().size() == 1 && index.column() == 0) { - commons_.item_click(project_parent->item_to_media(index), index); - } -} - -void SourceIconView::mousePressEvent(QMouseEvent* event) { - commons_.mousePressEvent(event); - if (!indexAt(event->pos()).isValid()) selectionModel()->clear(); - QListView::mousePressEvent(event); -} - -void SourceIconView::dragEnterEvent(QDragEnterEvent *event) { - if (event->mimeData()->hasUrls()) { - event->acceptProposedAction(); - } else { - QListView::dragEnterEvent(event); - } -} - -void SourceIconView::dragMoveEvent(QDragMoveEvent *event) { - if (event->mimeData()->hasUrls()) { - event->acceptProposedAction(); - } else { - QListView::dragMoveEvent(event); - } -} - -void SourceIconView::dropEvent(QDropEvent* event) { - QModelIndex drop_item = indexAt(event->pos()); - if (!drop_item.isValid()) drop_item = rootIndex(); - commons_.dropEvent(this, event, drop_item, selectedIndexes()); -} - -void SourceIconView::mouseDoubleClickEvent(QMouseEvent *) { - if (selectedIndexes().size() == 1) { - Media* m = project_parent->item_to_media(selectedIndexes().at(0)); - if (m->get_type() == MEDIA_TYPE_FOLDER) { - setRootIndex(selectedIndexes().at(0)); - emit changed_root(); - return; - } - } - - // Double click was not a folder, so we perform the default behavior (sending the double click to SourcesCommon) - commons_.mouseDoubleClickEvent(selectedIndexes()); -} - -SourceIconDelegate::SourceIconDelegate(QObject *parent) : - QStyledItemDelegate (parent) -{ -} - -QSize SourceIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const -{ - if (option.decorationPosition == QStyleOptionViewItem::Top) { // Icon Mode - - return QSize(256, 256); - - } else { - - return QSize(option.decorationSize.height(), option.decorationSize.height()); - - } -} - -void SourceIconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QFontMetrics fm = painter->fontMetrics(); - QRect img_rect = option.rect; - - if (option.decorationPosition == QStyleOptionViewItem::Top) { // Icon Mode - - // Draw Text - if (fm.height() < option.rect.height() / 2) { - img_rect.setHeight(img_rect.height()-fm.height()); - - QRect text_rect = option.rect; - text_rect.setTop(text_rect.top() + option.rect.height() - fm.height()); - - QColor text_bgcolor; - QColor text_fgcolor; - - if (option.state & QStyle::State_Selected) { - text_bgcolor = option.palette.highlight().color(); - text_fgcolor = option.palette.highlightedText().color(); - } else { - text_bgcolor = Qt::white; - text_fgcolor = Qt::black; - } - - painter->fillRect(text_rect, text_bgcolor); - painter->setPen(text_fgcolor); - - QString duration_str = index.data(Qt::UserRole).toString(); - int timecode_width = fm.width(duration_str); - int max_name_width = option.rect.width(); - - if (timecode_width < option.rect.width() / 2) { - painter->drawText(text_rect, Qt::AlignBottom | Qt::AlignRight, index.data(Qt::UserRole).toString()); - max_name_width -= timecode_width; - } - - painter->drawText(text_rect, - Qt::AlignBottom | Qt::AlignLeft, - fm.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, max_name_width)); - - } - - // Draw image - QIcon ico = index.data(Qt::DecorationRole).value(); - QSize icon_size = ico.actualSize(img_rect.size()); - img_rect = QRect(img_rect.x() + (img_rect.width() / 2 - icon_size.width() / 2), - img_rect.y() + (img_rect.height() / 2 - icon_size.height() / 2), - icon_size.width(), - icon_size.height()); - painter->drawPixmap(img_rect, ico.pixmap(icon_size)); - - if (option.state & QStyle::State_Selected) { - QColor highlight_color = option.palette.highlight().color(); - highlight_color.setAlphaF(0.5); - - painter->setCompositionMode(QPainter::CompositionMode_SourceAtop); - painter->fillRect(img_rect, highlight_color); - } - } else if (option.decorationPosition == QStyleOptionViewItem::Left) { // List Mode - - if (option.state & QStyle::State_Selected) { - painter->fillRect(option.rect, option.palette.highlight()); - } - - img_rect.setWidth(qMin(img_rect.width(), img_rect.height())); - - QIcon ico = index.data(Qt::DecorationRole).value(); - QSize icon_size = ico.actualSize(img_rect.size()); - img_rect = QRect(img_rect.x() + (img_rect.width() / 2 - icon_size.width() / 2), - img_rect.y() + (img_rect.height() / 2 - icon_size.height() / 2), - icon_size.width(), - icon_size.height()); - painter->drawPixmap(img_rect, ico.pixmap(icon_size)); - - QRect text_rect = option.rect; - text_rect.setLeft(text_rect.left() + option.rect.height()); - - int maximum_line_count = qMax(1, option.rect.height() / fm.height() - 1); - QString text; - if (maximum_line_count == 1) { - text = index.data(Qt::DisplayRole).toString(); - } else { - text = index.data(Qt::ToolTipRole).toString(); - if (text.isEmpty()) { - text = index.data(Qt::DisplayRole).toString(); - } else { - QStringList strings = text.split("\n"); - while (strings.size() > maximum_line_count) { - strings.removeLast(); - } - text = strings.join("\n"); - } - } - - painter->setPen(option.state & QStyle::State_Selected ? - option.palette.highlightedText().color() : option.palette.text().color()); - - painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, text); - - } -} +/*** + + 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 "sourceiconview.h" + +#include +#include + +#include "panels/project.h" +#include "project/media.h" +#include "project/sourcescommon.h" +#include "global/debug.h" +#include "global/math.h" + +SourceIconView::SourceIconView(SourcesCommon &commons) : + commons_(commons) +{ + setMovement(QListView::Free); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setResizeMode(QListView::Adjust); + setContextMenuPolicy(Qt::CustomContextMenu); + setItemDelegate(&delegate_); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); +} + +void SourceIconView::show_context_menu() { + commons_.show_context_menu(this, selectedIndexes()); +} + +void SourceIconView::item_click(const QModelIndex& index) { + if (selectedIndexes().size() == 1 && index.column() == 0) { + commons_.item_click(project_parent->item_to_media(index), index); + } +} + +void SourceIconView::mousePressEvent(QMouseEvent* event) { + commons_.mousePressEvent(event); + if (!indexAt(event->pos()).isValid()) selectionModel()->clear(); + QListView::mousePressEvent(event); +} + +void SourceIconView::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } else { + QListView::dragEnterEvent(event); + } +} + +void SourceIconView::dragMoveEvent(QDragMoveEvent *event) { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } else { + QListView::dragMoveEvent(event); + } +} + +void SourceIconView::dropEvent(QDropEvent* event) { + QModelIndex drop_item = indexAt(event->pos()); + if (!drop_item.isValid()) drop_item = rootIndex(); + commons_.dropEvent(this, event, drop_item, selectedIndexes()); +} + +void SourceIconView::mouseDoubleClickEvent(QMouseEvent *) { + if (selectedIndexes().size() == 1) { + Media* m = project_parent->item_to_media(selectedIndexes().at(0)); + if (m->get_type() == MEDIA_TYPE_FOLDER) { + setRootIndex(selectedIndexes().at(0)); + emit changed_root(); + return; + } + } + + // Double click was not a folder, so we perform the default behavior (sending the double click to SourcesCommon) + commons_.mouseDoubleClickEvent(selectedIndexes()); +} + +SourceIconDelegate::SourceIconDelegate(QObject *parent) : + QStyledItemDelegate (parent) +{ +} + +QSize SourceIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const +{ + if (option.decorationPosition == QStyleOptionViewItem::Top) { // Icon Mode + + return QSize(256, 256); + + } else { + + return QSize(option.decorationSize.height(), option.decorationSize.height()); + + } +} + +void SourceIconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QFontMetrics fm = painter->fontMetrics(); + QRect img_rect = option.rect; + + if (option.decorationPosition == QStyleOptionViewItem::Top) { // Icon Mode + + // Draw Text + if (fm.height() < option.rect.height() / 2) { + img_rect.setHeight(img_rect.height()-fm.height()); + + QRect text_rect = option.rect; + text_rect.setTop(text_rect.top() + option.rect.height() - fm.height()); + + QColor text_bgcolor; + QColor text_fgcolor; + + if (option.state & QStyle::State_Selected) { + text_bgcolor = option.palette.highlight().color(); + text_fgcolor = option.palette.highlightedText().color(); + } else { + text_bgcolor = Qt::white; + text_fgcolor = Qt::black; + } + + painter->fillRect(text_rect, text_bgcolor); + painter->setPen(text_fgcolor); + + QString duration_str = index.data(Qt::UserRole).toString(); + int timecode_width = fm.width(duration_str); + int max_name_width = option.rect.width(); + + if (timecode_width < option.rect.width() / 2) { + painter->drawText(text_rect, Qt::AlignBottom | Qt::AlignRight, index.data(Qt::UserRole).toString()); + max_name_width -= timecode_width; + } + + painter->drawText(text_rect, + Qt::AlignBottom | Qt::AlignLeft, + fm.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, max_name_width)); + + } + + // Draw image + QIcon ico = index.data(Qt::DecorationRole).value(); + QSize icon_size = ico.actualSize(img_rect.size()); + img_rect = QRect(img_rect.x() + (img_rect.width() / 2 - icon_size.width() / 2), + img_rect.y() + (img_rect.height() / 2 - icon_size.height() / 2), + icon_size.width(), + icon_size.height()); + painter->drawPixmap(img_rect, ico.pixmap(icon_size)); + + if (option.state & QStyle::State_Selected) { + QColor highlight_color = option.palette.highlight().color(); + highlight_color.setAlphaF(0.5); + + painter->setCompositionMode(QPainter::CompositionMode_SourceAtop); + painter->fillRect(img_rect, highlight_color); + } + } else if (option.decorationPosition == QStyleOptionViewItem::Left) { // List Mode + + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + } + + img_rect.setWidth(qMin(img_rect.width(), img_rect.height())); + + QIcon ico = index.data(Qt::DecorationRole).value(); + QSize icon_size = ico.actualSize(img_rect.size()); + img_rect = QRect(img_rect.x() + (img_rect.width() / 2 - icon_size.width() / 2), + img_rect.y() + (img_rect.height() / 2 - icon_size.height() / 2), + icon_size.width(), + icon_size.height()); + painter->drawPixmap(img_rect, ico.pixmap(icon_size)); + + QRect text_rect = option.rect; + text_rect.setLeft(text_rect.left() + option.rect.height()); + + int maximum_line_count = qMax(1, option.rect.height() / fm.height() - 1); + QString text; + if (maximum_line_count == 1) { + text = index.data(Qt::DisplayRole).toString(); + } else { + text = index.data(Qt::ToolTipRole).toString(); + if (text.isEmpty()) { + text = index.data(Qt::DisplayRole).toString(); + } else { + QStringList strings = text.split("\n"); + while (strings.size() > maximum_line_count) { + strings.removeLast(); + } + text = strings.join("\n"); + } + } + + painter->setPen(option.state & QStyle::State_Selected ? + option.palette.highlightedText().color() : option.palette.text().color()); + + painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, text); + + } +} diff --git a/ui/sourceiconview.h b/ui/sourceiconview.h index bbd7e3236..01da47956 100644 --- a/ui/sourceiconview.h +++ b/ui/sourceiconview.h @@ -1,61 +1,61 @@ -/*** - - 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 SOURCEICONVIEW_H -#define SOURCEICONVIEW_H - -#include -#include -#include - -#include "project/sourcescommon.h" - -class Project; -class SourceIconDelegate; - -class SourceIconDelegate : public QStyledItemDelegate { -public: - SourceIconDelegate(QObject *parent = nullptr); - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; -}; - -class SourceIconView : public QListView { - Q_OBJECT -public: - SourceIconView(SourcesCommon& commons); - Project* project_parent; - - void mousePressEvent(QMouseEvent* event); - void mouseDoubleClickEvent(QMouseEvent *event); - void dragEnterEvent(QDragEnterEvent *event); - void dragMoveEvent(QDragMoveEvent *event); - void dropEvent(QDropEvent* event); -signals: - void changed_root(); -private slots: - void show_context_menu(); - void item_click(const QModelIndex& index); -private: - SourcesCommon& commons_; - SourceIconDelegate delegate_; -}; - -#endif // SOURCEICONVIEW_H +/*** + + 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 SOURCEICONVIEW_H +#define SOURCEICONVIEW_H + +#include +#include +#include + +#include "project/sourcescommon.h" + +class Project; +class SourceIconDelegate; + +class SourceIconDelegate : public QStyledItemDelegate { +public: + SourceIconDelegate(QObject *parent = nullptr); + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +class SourceIconView : public QListView { + Q_OBJECT +public: + SourceIconView(SourcesCommon& commons); + Project* project_parent; + + void mousePressEvent(QMouseEvent* event); + void mouseDoubleClickEvent(QMouseEvent *event); + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent* event); +signals: + void changed_root(); +private slots: + void show_context_menu(); + void item_click(const QModelIndex& index); +private: + SourcesCommon& commons_; + SourceIconDelegate delegate_; +}; + +#endif // SOURCEICONVIEW_H diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index d3f74f449..baac4f429 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -1,97 +1,97 @@ -/*** - - 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 "sourcetable.h" -#include "panels/project.h" - -#include "project/footage.h" -#include "panels/timeline.h" -#include "panels/viewer.h" -#include "panels/panels.h" -#include "rendering/renderfunctions.h" -#include "undo/undo.h" -#include "timeline/sequence.h" -#include "mainwindow.h" -#include "global/config.h" -#include "project/media.h" -#include "project/sourcescommon.h" -#include "global/debug.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -SourceTable::SourceTable(SourcesCommon& commons) : commons_(commons) { - setSortingEnabled(true); - setAcceptDrops(true); - sortByColumn(0, Qt::AscendingOrder); - setContextMenuPolicy(Qt::CustomContextMenu); - setEditTriggers(QAbstractItemView::NoEditTriggers); - setDragDropMode(QAbstractItemView::DragDrop); - setSelectionMode(QAbstractItemView::ExtendedSelection); - connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); -} - -void SourceTable::show_context_menu() { - commons_.show_context_menu(this, selectionModel()->selectedRows()); -} - -void SourceTable::item_click(const QModelIndex& index) { - if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { - commons_.item_click(project_parent->item_to_media(index), index); - } -} - -void SourceTable::mousePressEvent(QMouseEvent* event) { - commons_.mousePressEvent(event); - QTreeView::mousePressEvent(event); -} - -void SourceTable::mouseDoubleClickEvent(QMouseEvent* ) { - commons_.mouseDoubleClickEvent(selectionModel()->selectedRows()); -} - -void SourceTable::dragEnterEvent(QDragEnterEvent *event) { - if (event->mimeData()->hasUrls()) { - event->acceptProposedAction(); - } else { - QTreeView::dragEnterEvent(event); - } -} - -void SourceTable::dragMoveEvent(QDragMoveEvent *event) { - if (event->mimeData()->hasUrls()) { - event->acceptProposedAction(); - } else { - QTreeView::dragMoveEvent(event); - } -} - -void SourceTable::dropEvent(QDropEvent* event) { - commons_.dropEvent(this, event, indexAt(event->pos()), selectionModel()->selectedRows()); -} +/*** + + 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 "sourcetable.h" +#include "panels/project.h" + +#include "project/footage.h" +#include "panels/timeline.h" +#include "panels/viewer.h" +#include "panels/panels.h" +#include "rendering/renderfunctions.h" +#include "undo/undo.h" +#include "timeline/sequence.h" +#include "mainwindow.h" +#include "global/config.h" +#include "project/media.h" +#include "project/sourcescommon.h" +#include "global/debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SourceTable::SourceTable(SourcesCommon& commons) : commons_(commons) { + setSortingEnabled(true); + setAcceptDrops(true); + sortByColumn(0, Qt::AscendingOrder); + setContextMenuPolicy(Qt::CustomContextMenu); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setDragDropMode(QAbstractItemView::DragDrop); + setSelectionMode(QAbstractItemView::ExtendedSelection); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); +} + +void SourceTable::show_context_menu() { + commons_.show_context_menu(this, selectionModel()->selectedRows()); +} + +void SourceTable::item_click(const QModelIndex& index) { + if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { + commons_.item_click(project_parent->item_to_media(index), index); + } +} + +void SourceTable::mousePressEvent(QMouseEvent* event) { + commons_.mousePressEvent(event); + QTreeView::mousePressEvent(event); +} + +void SourceTable::mouseDoubleClickEvent(QMouseEvent* ) { + commons_.mouseDoubleClickEvent(selectionModel()->selectedRows()); +} + +void SourceTable::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } else { + QTreeView::dragEnterEvent(event); + } +} + +void SourceTable::dragMoveEvent(QDragMoveEvent *event) { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } else { + QTreeView::dragMoveEvent(event); + } +} + +void SourceTable::dropEvent(QDropEvent* event) { + commons_.dropEvent(this, event, indexAt(event->pos()), selectionModel()->selectedRows()); +} diff --git a/ui/sourcetable.h b/ui/sourcetable.h index aeea9dda3..ffd02787d 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -1,52 +1,52 @@ -/*** - - 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 SOURCETABLE_H -#define SOURCETABLE_H - -#include -#include -#include - -#include "project/sourcescommon.h" - -class Project; -class Media; - -class SourceTable : public QTreeView -{ - Q_OBJECT -public: - SourceTable(SourcesCommon& commons); - Project* project_parent; -protected: - void mousePressEvent(QMouseEvent*); - void mouseDoubleClickEvent(QMouseEvent *); - void dragEnterEvent(QDragEnterEvent *event); - void dragMoveEvent(QDragMoveEvent *event); - void dropEvent(QDropEvent *event); -private slots: - void item_click(const QModelIndex& index); - void show_context_menu(); -private: - SourcesCommon& commons_; -}; - -#endif // SOURCETABLE_H +/*** + + 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 SOURCETABLE_H +#define SOURCETABLE_H + +#include +#include +#include + +#include "project/sourcescommon.h" + +class Project; +class Media; + +class SourceTable : public QTreeView +{ + Q_OBJECT +public: + SourceTable(SourcesCommon& commons); + Project* project_parent; +protected: + void mousePressEvent(QMouseEvent*); + void mouseDoubleClickEvent(QMouseEvent *); + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); +private slots: + void item_click(const QModelIndex& index); + void show_context_menu(); +private: + SourcesCommon& commons_; +}; + +#endif // SOURCETABLE_H diff --git a/ui/styling.cpp b/ui/styling.cpp index 5d8dfd08f..b80d4d845 100644 --- a/ui/styling.cpp +++ b/ui/styling.cpp @@ -1,44 +1,44 @@ -/*** - - 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::config.style == kOliveDefaultLight || olive::config.style == kNativeDarkIcons; -} - -QColor olive::styling::GetIconColor() -{ - if (UseDarkIcons()) { - return Qt::black; - } else { - return Qt::white; - } -} - - - -bool olive::styling::UseNativeUI() -{ - return olive::config.style == kNativeLightIcons || olive::config.style == kNativeDarkIcons; -} +/*** + + 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::config.style == kOliveDefaultLight || olive::config.style == kNativeDarkIcons; +} + +QColor olive::styling::GetIconColor() +{ + if (UseDarkIcons()) { + return Qt::black; + } else { + return Qt::white; + } +} + + + +bool olive::styling::UseNativeUI() +{ + return olive::config.style == kNativeLightIcons || olive::config.style == kNativeDarkIcons; +} diff --git a/ui/styling.h b/ui/styling.h index e41a9df9b..f50c1ccab 100644 --- a/ui/styling.h +++ b/ui/styling.h @@ -1,87 +1,87 @@ -/*** - - 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 - -#include - -namespace olive { - namespace styling { - - /** - * @brief Officially supported styles to use in Olive - */ - enum Style { - /** - Qt Fusion-based cross-platform UI. The default styling of Olive. Can also be heavily customized with a CSS - file. - */ - kOliveDefaultDark, - - /** - Qt Fusion-based cross-platform UI. The default styling of Olive. Can also be heavily customized with a CSS - file. This will use the - */ - kOliveDefaultLight, - - /** - Use current OS's native styling (or at least Qt's default). Most UIs use a light theming, so this will - automatically implement dark icons/UI elements. - */ - kNativeDarkIcons, - - /** - Use current OS's native styling (or at least Qt's default). Most UIs use a light theming, but in case one - doesn't, this option will provide light icons for use with a dark theme. - */ - kNativeLightIcons - }; - - /** - * @brief Return whether to use dark icons or light icons - * @return - * - * **TRUE** if icons should be dark. - */ - bool UseDarkIcons(); - - /** - * @brief Return whether to use native UI or Fusion - * @return - * - * **TRUE** if UI should use native styling - */ - bool UseNativeUI(); - - /** - * @brief Return the current icon color based on Config::use_dark_icons. - * - * Also used by some other UI elements like the lines and text on the TimelineHeader - * - * @return - * - * Either white or black depending on Config::use_dark_icons - */ - QColor GetIconColor(); - } -} - -#endif // STYLING_H +/*** + + 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 + +#include + +namespace olive { + namespace styling { + + /** + * @brief Officially supported styles to use in Olive + */ + enum Style { + /** + Qt Fusion-based cross-platform UI. The default styling of Olive. Can also be heavily customized with a CSS + file. + */ + kOliveDefaultDark, + + /** + Qt Fusion-based cross-platform UI. The default styling of Olive. Can also be heavily customized with a CSS + file. This will use the + */ + kOliveDefaultLight, + + /** + Use current OS's native styling (or at least Qt's default). Most UIs use a light theming, so this will + automatically implement dark icons/UI elements. + */ + kNativeDarkIcons, + + /** + Use current OS's native styling (or at least Qt's default). Most UIs use a light theming, but in case one + doesn't, this option will provide light icons for use with a dark theme. + */ + kNativeLightIcons + }; + + /** + * @brief Return whether to use dark icons or light icons + * @return + * + * **TRUE** if icons should be dark. + */ + bool UseDarkIcons(); + + /** + * @brief Return whether to use native UI or Fusion + * @return + * + * **TRUE** if UI should use native styling + */ + bool UseNativeUI(); + + /** + * @brief Return the current icon color based on Config::use_dark_icons. + * + * Also used by some other UI elements like the lines and text on the TimelineHeader + * + * @return + * + * Either white or black depending on Config::use_dark_icons + */ + QColor GetIconColor(); + } +} + +#endif // STYLING_H diff --git a/ui/texteditex.cpp b/ui/texteditex.cpp index 28f32a8b0..2b9f44f41 100644 --- a/ui/texteditex.cpp +++ b/ui/texteditex.cpp @@ -1,117 +1,117 @@ -/*** - - 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 "texteditex.h" - -#include -#include - -#include "dialogs/texteditdialog.h" -#include "ui/menu.h" -#include "mainwindow.h" - -TextEditEx::TextEditEx(QWidget *parent, bool enable_rich_text) : - QWidget(parent), - enable_rich_text_(enable_rich_text) -{ - QVBoxLayout* layout = new QVBoxLayout(this); - 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())); - layout->addWidget(text_editor_); - - QPushButton* edit_button = new QPushButton(tr("Edit Text")); - layout->addWidget(edit_button); - connect(edit_button, SIGNAL(clicked(bool)), this, SLOT(open_text_edit())); - - /* - text_editor_->setContextMenuPolicy(Qt::CustomContextMenu); - connect(text_editor_, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(text_edit_menu())); - */ -} - -void TextEditEx::setUndoRedoEnabled(bool e) -{ - text_editor_->setUndoRedoEnabled(e); -} - -QTextDocument *TextEditEx::document() -{ - return text_editor_->document(); -} - -QTextCursor TextEditEx::textCursor() -{ - return text_editor_->textCursor(); -} - -void TextEditEx::setTextCursor(const QTextCursor &cursor) -{ - text_editor_->setTextCursor(cursor); -} - -void TextEditEx::setTextHeight(int h) -{ - text_editor_->setFixedHeight(h); -} - -void TextEditEx::setHtml(const QString &text) -{ - text_editor_->setHtml(text); -} - -void TextEditEx::setPlainText(const QString &text) -{ - text_editor_->setPlainText(text); -} - -void TextEditEx::text_edit_menu() { - Menu menu; - - menu.addAction(tr("&Edit Text"), this, SLOT(open_text_edit())); - - menu.exec(QCursor::pos()); -} - -void TextEditEx::open_text_edit() { - const QString& current_text = (enable_rich_text_) ? text_editor_->toHtml() : text_editor_->toPlainText(); - - TextEditDialog ted(olive::MainWindow, current_text, enable_rich_text_); - ted.exec(); - QString result = ted.get_string(); - if (!result.isEmpty()) { - if (enable_rich_text_) { - text_editor_->setHtml(result); - } else { - text_editor_->setPlainText(result); - } - } -} - -void TextEditEx::queue_text_modified() -{ - emit textModified(enable_rich_text_ ? text_editor_->toHtml() : text_editor_->toPlainText()); -} +/*** + + 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 "texteditex.h" + +#include +#include + +#include "dialogs/texteditdialog.h" +#include "ui/menu.h" +#include "mainwindow.h" + +TextEditEx::TextEditEx(QWidget *parent, bool enable_rich_text) : + QWidget(parent), + enable_rich_text_(enable_rich_text) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + 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())); + layout->addWidget(text_editor_); + + QPushButton* edit_button = new QPushButton(tr("Edit Text")); + layout->addWidget(edit_button); + connect(edit_button, SIGNAL(clicked(bool)), this, SLOT(open_text_edit())); + + /* + text_editor_->setContextMenuPolicy(Qt::CustomContextMenu); + connect(text_editor_, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(text_edit_menu())); + */ +} + +void TextEditEx::setUndoRedoEnabled(bool e) +{ + text_editor_->setUndoRedoEnabled(e); +} + +QTextDocument *TextEditEx::document() +{ + return text_editor_->document(); +} + +QTextCursor TextEditEx::textCursor() +{ + return text_editor_->textCursor(); +} + +void TextEditEx::setTextCursor(const QTextCursor &cursor) +{ + text_editor_->setTextCursor(cursor); +} + +void TextEditEx::setTextHeight(int h) +{ + text_editor_->setFixedHeight(h); +} + +void TextEditEx::setHtml(const QString &text) +{ + text_editor_->setHtml(text); +} + +void TextEditEx::setPlainText(const QString &text) +{ + text_editor_->setPlainText(text); +} + +void TextEditEx::text_edit_menu() { + Menu menu; + + menu.addAction(tr("&Edit Text"), this, SLOT(open_text_edit())); + + menu.exec(QCursor::pos()); +} + +void TextEditEx::open_text_edit() { + const QString& current_text = (enable_rich_text_) ? text_editor_->toHtml() : text_editor_->toPlainText(); + + TextEditDialog ted(olive::MainWindow, current_text, enable_rich_text_); + ted.exec(); + QString result = ted.get_string(); + if (!result.isEmpty()) { + if (enable_rich_text_) { + text_editor_->setHtml(result); + } else { + text_editor_->setPlainText(result); + } + } +} + +void TextEditEx::queue_text_modified() +{ + emit textModified(enable_rich_text_ ? text_editor_->toHtml() : text_editor_->toPlainText()); +} diff --git a/ui/texteditex.h b/ui/texteditex.h index 46e12bb66..047bac38a 100644 --- a/ui/texteditex.h +++ b/ui/texteditex.h @@ -1,52 +1,52 @@ -/*** - - 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 TEXTEDITEX_H -#define TEXTEDITEX_H - -#include -#include - -class TextEditEx : public QWidget { - Q_OBJECT -public: - TextEditEx(QWidget* parent = nullptr, bool enable_rich_text = true); - - void setUndoRedoEnabled(bool e); - QTextDocument* document(); - QTextCursor textCursor(); - void setTextCursor(const QTextCursor &cursor); - void setTextHeight(int h); -public slots: - void setHtml(const QString &text); - void setPlainText(const QString &text); -signals: - void textModified(const QString& s); -private slots: - void text_edit_menu(); - void open_text_edit(); - void queue_text_modified(); -private: - QTextEdit* text_editor_; - - bool enable_rich_text_; -}; - -#endif // TEXTEDITEX_H +/*** + + 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 TEXTEDITEX_H +#define TEXTEDITEX_H + +#include +#include + +class TextEditEx : public QWidget { + Q_OBJECT +public: + TextEditEx(QWidget* parent = nullptr, bool enable_rich_text = true); + + void setUndoRedoEnabled(bool e); + QTextDocument* document(); + QTextCursor textCursor(); + void setTextCursor(const QTextCursor &cursor); + void setTextHeight(int h); +public slots: + void setHtml(const QString &text); + void setPlainText(const QString &text); +signals: + void textModified(const QString& s); +private slots: + void text_edit_menu(); + void open_text_edit(); + void queue_text_modified(); +private: + QTextEdit* text_editor_; + + bool enable_rich_text_; +}; + +#endif // TEXTEDITEX_H diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 43e19518a..d6cd92f3c 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -1,496 +1,496 @@ -/*** - - 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 "timelineheader.h" - -#include -#include -#include -#include -#include - -#include "mainwindow.h" -#include "panels/panels.h" -#include "global/math.h" -#include "timeline/sequence.h" -#include "undo/undo.h" -#include "project/media.h" -#include "global/timing.h" -#include "global/config.h" -#include "global/global.h" -#include "ui/menu.h" -#include "ui/menuhelper.h" -#include "global/debug.h" -#include "undo/undostack.h" - -#define CLICK_RANGE 5 -#define PLAYHEAD_SIZE 6 -#define LINE_MIN_PADDING 50 -#define SUBLINE_MIN_PADDING 50 // TODO play with this - -// used only if center_timeline_timecodes is FALSE -#define TEXT_PADDING_FROM_LINE 4 - -bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead) { - // returns true is the scroll was changed, false if not - int target_scroll = qMin(bar->maximum(), qMax(0, getScreenPointFromFrame(zoom, playhead)-(bar->width()>>1))); - if (target_scroll == bar->value()) { - return false; - } - bar->setValue(target_scroll); - return true; -} - -TimelineHeader::TimelineHeader(QWidget *parent) : - QWidget(parent), - snapping(true), - dragging(false), - resizing_workarea(false), - zoom(1), - in_visible(0), - fm(font()), - dragging_markers(false), - scroll(0), - height_actual(fm.height()) -{ - setCursor(Qt::ArrowCursor); - setMouseTracking(true); - setFocusPolicy(Qt::ClickFocus); - show_text(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(show_context_menu(const QPoint &))); -} - -void TimelineHeader::set_scroll(int s) { - scroll = s; - update(); -} - -long TimelineHeader::getHeaderFrameFromScreenPoint(int x) { - return getFrameFromScreenPoint(zoom, x + scroll) + in_visible; -} - -int TimelineHeader::getHeaderScreenPointFromFrame(long frame) { - return getScreenPointFromFrame(zoom, frame - in_visible) - scroll; -} - -void TimelineHeader::set_playhead(int mouse_x) { - long frame = getHeaderFrameFromScreenPoint(mouse_x); - if (snapping) viewer->seq->SnapPoint(&frame, zoom, false, true, true); - if (frame != viewer->seq->playhead) { - viewer->seek(frame); - } -} - -int TimelineHeader::get_marker_offset() { - return (text_enabled) ? height()/2 : 0; -} - -void TimelineHeader::set_visible_in(long i) { - in_visible = i; - update(); -} - -void TimelineHeader::set_in_point(long new_in) { - long new_out = viewer->seq->workarea_out; - if (new_out == new_in) { - new_in--; - } else if (new_out < new_in) { - new_out = viewer->seq->GetEndFrame(); - } - - olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); - update_parents(); -} - -void TimelineHeader::set_out_point(long new_out) { - long new_in = viewer->seq->workarea_in; - if (new_out == new_in) { - new_out++; - } else if (new_in > new_out || new_in < 0) { - new_in = 0; - } - - olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); - update_parents(); -} - -void TimelineHeader::set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset) { - bar->setMaximum(qMax(0, getScreenPointFromFrame(zoom, sequence_end_frame) - offset)); -} - -void TimelineHeader::show_text(bool enable) { - text_enabled = enable; - if (enable) { - setFixedHeight(height_actual*2); - } else { - setFixedHeight(height_actual); - } - update(); -} - -void TimelineHeader::mousePressEvent(QMouseEvent* event) { - if (viewer->seq != nullptr && event->buttons() & Qt::LeftButton) { - if (resizing_workarea) { - sequence_end = viewer->seq->GetEndFrame(); - } else { - /*int - QPoint start(in_x, height()+2); - QPainterPath path; - path.moveTo(start + QPoint(1,0)); - path.lineTo(in_x-PLAYHEAD_SIZE, yoff); - path.lineTo(in_x+PLAYHEAD_SIZE+1, yoff);*/ - - bool shift = (event->modifiers() & Qt::ShiftModifier); - bool clicked_on_marker = false; - int playhead_x = getHeaderScreenPointFromFrame(viewer->seq->playhead); - - if (event->pos().y() > get_marker_offset() - && (event->pos().x() < playhead_x-PLAYHEAD_SIZE - || event->pos().x() > playhead_x+PLAYHEAD_SIZE)) { - for (int i=0;imarker_ref->size();i++) { - int marker_pos = getHeaderScreenPointFromFrame(viewer->marker_ref->at(i).frame); - if (event->pos().x() > marker_pos - MARKER_SIZE && event->pos().x() < marker_pos + MARKER_SIZE) { - bool found = false; - for (int j=0;jmarker_ref->at(selected_markers.at(i)).frame; - } - drag_start = event->pos().x(); - dragging_markers = true; - } else { - if (selected_markers.size() > 0 && !shift) { - selected_markers.clear(); - update(); - } - set_playhead(event->pos().x()); - } - } - dragging = true; - } -} - -void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { - if (viewer->seq != nullptr) { - if (dragging) { - if (resizing_workarea) { - long frame = getHeaderFrameFromScreenPoint(event->pos().x()); - 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); - } else { - temp_workarea_out = qMin(qMax(temp_workarea_in+1, frame), sequence_end); - } - - update_parents(); - } else if (dragging_markers) { - long frame_movement = getHeaderFrameFromScreenPoint(event->pos().x()) - getHeaderFrameFromScreenPoint(drag_start); - - // snap markers - for (int i=0;iseq->SnapPoint(&fm, zoom, true, false, true)) { - frame_movement = fm - selected_marker_original_times.at(i); - break; - } - } - - // validate markers (ensure none go below 0) - long validator; - for (int i=0;imarker_ref[0][selected_markers.at(i)].frame = selected_marker_original_times.at(i) + frame_movement; - } - - update_parents(); - } else { - set_playhead(event->pos().x()); - } - } else { - resizing_workarea = false; - unsetCursor(); - if (viewer->seq != nullptr && viewer->seq->using_workarea) { - long min_frame = getHeaderFrameFromScreenPoint(event->pos().x() - CLICK_RANGE) - 1; - long max_frame = getHeaderFrameFromScreenPoint(event->pos().x() + CLICK_RANGE) + 1; - if (viewer->seq->workarea_in > min_frame && viewer->seq->workarea_in < max_frame) { - resizing_workarea = true; - resizing_workarea_in = true; - } else if (viewer->seq->workarea_out > min_frame && viewer->seq->workarea_out < max_frame) { - resizing_workarea = true; - resizing_workarea_in = false; - } - if (resizing_workarea) { - temp_workarea_in = viewer->seq->workarea_in; - temp_workarea_out = viewer->seq->workarea_out; - setCursor(Qt::SizeHorCursor); - } - } - } - } -} - -void TimelineHeader::mouseReleaseEvent(QMouseEvent*) { - if (viewer->seq != nullptr) { - dragging = false; - if (resizing_workarea) { - 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(); - for (int i=0;imarker_ref[0][selected_markers.at(i)]; - if (selected_marker_original_times.at(i) != m->frame) { - ca->append(new MoveMarkerAction(m, selected_marker_original_times.at(i), m->frame)); - moved = true; - } - } - if (moved) { - olive::undo_stack.push(ca); - } else { - delete ca; - } - } - - resizing_workarea = false; - dragging = false; - dragging_markers = false; - olive::timeline::snapped = false; - update_parents(); - } -} - -void TimelineHeader::focusOutEvent(QFocusEvent*) { - selected_markers.clear(); - update(); -} - -void TimelineHeader::update_parents() { - viewer->update_parents(); -} - -void TimelineHeader::update_zoom(double z) { - zoom = z; - update(); -} - -double TimelineHeader::get_zoom() { - return zoom; -} - -void TimelineHeader::delete_markers() { - if (selected_markers.size() > 0) { - - // Send command to delete selected markers - DeleteMarkerAction* dma = new DeleteMarkerAction(viewer->marker_ref); - dma->markers.append(selected_markers); - olive::undo_stack.push(dma); - - // remove any indices for the selected markers that no longer exist - for (int i=0;i= viewer->marker_ref->size()) { - selected_markers.removeAt(i); - i--; - } - } - - // if we removed all the indices, re-select the last marker in the array so something is always selected - // (allows users to hold delete when deleting markers) - if (selected_markers.isEmpty() && !viewer->marker_ref->isEmpty()) { - selected_markers.append(viewer->marker_ref->size() - 1); - } - - update_parents(); - } -} - -void TimelineHeader::paintEvent(QPaintEvent*) { - if (viewer->seq != nullptr && zoom > 0) { - QPainter p(this); - int yoff = get_marker_offset(); - - double interval = viewer->seq->frame_rate(); - int textWidth = 0; - int lastTextBoundary = INT_MIN; - - int lastLineX = INT_MIN; - - int sublineCount = 1; - int sublineTest = qRound(interval*zoom); - int sublineInterval = 1; - while (sublineTest > SUBLINE_MIN_PADDING - && sublineInterval >= 1) { - sublineCount *= 2; - sublineInterval = (interval/sublineCount); - sublineTest = qRound(sublineInterval*zoom); - } - sublineCount = qMin(sublineCount, qRound(interval)); - - int text_x, fullTextWidth; - QString timecode; - - // find where to start drawing lines (lineX algorithm reversed if lineX = 0) - int i = qFloor(double(scroll)/zoom/interval); - - while (true) { - long frame = qRound(interval*i); - int lineX = qRound(frame*zoom) - scroll; - - if (lineX > width()) break; - - // draw text - bool draw_text = false; - if (text_enabled && lineX-textWidth > lastTextBoundary) { - 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::config.center_timeline_timecodes) { - text_x -= textWidth; - } else { - text_x += TEXT_PADDING_FROM_LINE; - } - - lastTextBoundary = lineX+textWidth; - if (lastTextBoundary >= 0) { - draw_text = true; - } - } - - if (lineX > lastLineX+LINE_MIN_PADDING) { - if (draw_text) { - p.setPen(olive::styling::GetIconColor()); - p.drawText(QRect(text_x, 0, fullTextWidth, yoff), timecode); - } - - // draw line markers - p.setPen(Qt::gray); - p.drawLine(lineX, (!olive::config.center_timeline_timecodes && draw_text) ? 0 : yoff, lineX, height()); - - // draw sub-line markers - for (int j=1;jseq->using_workarea) { - in_x = getHeaderScreenPointFromFrame((resizing_workarea ? temp_workarea_in : viewer->seq->workarea_in)); - int out_x = getHeaderScreenPointFromFrame((resizing_workarea ? temp_workarea_out : viewer->seq->workarea_out)); - p.fillRect(QRect(in_x, 0, out_x-in_x, height()), QColor(0, 192, 255, 128)); - p.setPen(olive::styling::GetIconColor()); - p.drawLine(in_x, 0, in_x, height()); - p.drawLine(out_x, 0, out_x, height()); - } - - // draw markers - for (int i=0;imarker_ref->size();i++) { - const Marker& m = viewer->marker_ref->at(i); - - int marker_x = getHeaderScreenPointFromFrame(m.frame); - - bool selected = false; - for (int j=0;jseq->playhead); - QPoint start(in_x, height()+2); - QPainterPath path; - path.moveTo(start + QPoint(1,0)); - path.lineTo(in_x-PLAYHEAD_SIZE, yoff); - path.lineTo(in_x+PLAYHEAD_SIZE+1, yoff); - path.lineTo(start); - p.fillPath(path, Qt::red); - - // Draw white line at the top for clarity - p.setPen(Qt::gray); - p.drawLine(0, 0, width(), 0); - } -} - -void TimelineHeader::show_context_menu(const QPoint &pos) { - Menu menu(this); - - // Add items for setting the in/out points of a QMenu - olive::MenuHelper.make_inout_menu(&menu); - - menu.addSeparator(); - - QAction* center_timecodes = menu.addAction(tr("Center Timecodes"), &olive::MenuHelper, SLOT(toggle_bool_action())); - center_timecodes->setCheckable(true); - center_timecodes->setChecked(olive::config.center_timeline_timecodes); - center_timecodes->setData(reinterpret_cast(&olive::config.center_timeline_timecodes)); - - menu.exec(mapToGlobal(pos)); -} - -void TimelineHeader::resized_scroll_listener(double d) { - update_zoom(zoom * d); -} +/*** + + 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 "timelineheader.h" + +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "panels/panels.h" +#include "global/math.h" +#include "timeline/sequence.h" +#include "undo/undo.h" +#include "project/media.h" +#include "global/timing.h" +#include "global/config.h" +#include "global/global.h" +#include "ui/menu.h" +#include "ui/menuhelper.h" +#include "global/debug.h" +#include "undo/undostack.h" + +#define CLICK_RANGE 5 +#define PLAYHEAD_SIZE 6 +#define LINE_MIN_PADDING 50 +#define SUBLINE_MIN_PADDING 50 // TODO play with this + +// used only if center_timeline_timecodes is FALSE +#define TEXT_PADDING_FROM_LINE 4 + +bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead) { + // returns true is the scroll was changed, false if not + int target_scroll = qMin(bar->maximum(), qMax(0, getScreenPointFromFrame(zoom, playhead)-(bar->width()>>1))); + if (target_scroll == bar->value()) { + return false; + } + bar->setValue(target_scroll); + return true; +} + +TimelineHeader::TimelineHeader(QWidget *parent) : + QWidget(parent), + snapping(true), + dragging(false), + resizing_workarea(false), + zoom(1), + in_visible(0), + fm(font()), + dragging_markers(false), + scroll(0), + height_actual(fm.height()) +{ + setCursor(Qt::ArrowCursor); + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + show_text(true); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(show_context_menu(const QPoint &))); +} + +void TimelineHeader::set_scroll(int s) { + scroll = s; + update(); +} + +long TimelineHeader::getHeaderFrameFromScreenPoint(int x) { + return getFrameFromScreenPoint(zoom, x + scroll) + in_visible; +} + +int TimelineHeader::getHeaderScreenPointFromFrame(long frame) { + return getScreenPointFromFrame(zoom, frame - in_visible) - scroll; +} + +void TimelineHeader::set_playhead(int mouse_x) { + long frame = getHeaderFrameFromScreenPoint(mouse_x); + if (snapping) viewer->seq->SnapPoint(&frame, zoom, false, true, true); + if (frame != viewer->seq->playhead) { + viewer->seek(frame); + } +} + +int TimelineHeader::get_marker_offset() { + return (text_enabled) ? height()/2 : 0; +} + +void TimelineHeader::set_visible_in(long i) { + in_visible = i; + update(); +} + +void TimelineHeader::set_in_point(long new_in) { + long new_out = viewer->seq->workarea_out; + if (new_out == new_in) { + new_in--; + } else if (new_out < new_in) { + new_out = viewer->seq->GetEndFrame(); + } + + olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); + update_parents(); +} + +void TimelineHeader::set_out_point(long new_out) { + long new_in = viewer->seq->workarea_in; + if (new_out == new_in) { + new_out++; + } else if (new_in > new_out || new_in < 0) { + new_in = 0; + } + + olive::undo_stack.push(new SetTimelineInOutCommand(viewer->seq.get(), true, new_in, new_out)); + update_parents(); +} + +void TimelineHeader::set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset) { + bar->setMaximum(qMax(0, getScreenPointFromFrame(zoom, sequence_end_frame) - offset)); +} + +void TimelineHeader::show_text(bool enable) { + text_enabled = enable; + if (enable) { + setFixedHeight(height_actual*2); + } else { + setFixedHeight(height_actual); + } + update(); +} + +void TimelineHeader::mousePressEvent(QMouseEvent* event) { + if (viewer->seq != nullptr && event->buttons() & Qt::LeftButton) { + if (resizing_workarea) { + sequence_end = viewer->seq->GetEndFrame(); + } else { + /*int + QPoint start(in_x, height()+2); + QPainterPath path; + path.moveTo(start + QPoint(1,0)); + path.lineTo(in_x-PLAYHEAD_SIZE, yoff); + path.lineTo(in_x+PLAYHEAD_SIZE+1, yoff);*/ + + bool shift = (event->modifiers() & Qt::ShiftModifier); + bool clicked_on_marker = false; + int playhead_x = getHeaderScreenPointFromFrame(viewer->seq->playhead); + + if (event->pos().y() > get_marker_offset() + && (event->pos().x() < playhead_x-PLAYHEAD_SIZE + || event->pos().x() > playhead_x+PLAYHEAD_SIZE)) { + for (int i=0;imarker_ref->size();i++) { + int marker_pos = getHeaderScreenPointFromFrame(viewer->marker_ref->at(i).frame); + if (event->pos().x() > marker_pos - MARKER_SIZE && event->pos().x() < marker_pos + MARKER_SIZE) { + bool found = false; + for (int j=0;jmarker_ref->at(selected_markers.at(i)).frame; + } + drag_start = event->pos().x(); + dragging_markers = true; + } else { + if (selected_markers.size() > 0 && !shift) { + selected_markers.clear(); + update(); + } + set_playhead(event->pos().x()); + } + } + dragging = true; + } +} + +void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { + if (viewer->seq != nullptr) { + if (dragging) { + if (resizing_workarea) { + long frame = getHeaderFrameFromScreenPoint(event->pos().x()); + 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); + } else { + temp_workarea_out = qMin(qMax(temp_workarea_in+1, frame), sequence_end); + } + + update_parents(); + } else if (dragging_markers) { + long frame_movement = getHeaderFrameFromScreenPoint(event->pos().x()) - getHeaderFrameFromScreenPoint(drag_start); + + // snap markers + for (int i=0;iseq->SnapPoint(&fm, zoom, true, false, true)) { + frame_movement = fm - selected_marker_original_times.at(i); + break; + } + } + + // validate markers (ensure none go below 0) + long validator; + for (int i=0;imarker_ref[0][selected_markers.at(i)].frame = selected_marker_original_times.at(i) + frame_movement; + } + + update_parents(); + } else { + set_playhead(event->pos().x()); + } + } else { + resizing_workarea = false; + unsetCursor(); + if (viewer->seq != nullptr && viewer->seq->using_workarea) { + long min_frame = getHeaderFrameFromScreenPoint(event->pos().x() - CLICK_RANGE) - 1; + long max_frame = getHeaderFrameFromScreenPoint(event->pos().x() + CLICK_RANGE) + 1; + if (viewer->seq->workarea_in > min_frame && viewer->seq->workarea_in < max_frame) { + resizing_workarea = true; + resizing_workarea_in = true; + } else if (viewer->seq->workarea_out > min_frame && viewer->seq->workarea_out < max_frame) { + resizing_workarea = true; + resizing_workarea_in = false; + } + if (resizing_workarea) { + temp_workarea_in = viewer->seq->workarea_in; + temp_workarea_out = viewer->seq->workarea_out; + setCursor(Qt::SizeHorCursor); + } + } + } + } +} + +void TimelineHeader::mouseReleaseEvent(QMouseEvent*) { + if (viewer->seq != nullptr) { + dragging = false; + if (resizing_workarea) { + 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(); + for (int i=0;imarker_ref[0][selected_markers.at(i)]; + if (selected_marker_original_times.at(i) != m->frame) { + ca->append(new MoveMarkerAction(m, selected_marker_original_times.at(i), m->frame)); + moved = true; + } + } + if (moved) { + olive::undo_stack.push(ca); + } else { + delete ca; + } + } + + resizing_workarea = false; + dragging = false; + dragging_markers = false; + olive::timeline::snapped = false; + update_parents(); + } +} + +void TimelineHeader::focusOutEvent(QFocusEvent*) { + selected_markers.clear(); + update(); +} + +void TimelineHeader::update_parents() { + viewer->update_parents(); +} + +void TimelineHeader::update_zoom(double z) { + zoom = z; + update(); +} + +double TimelineHeader::get_zoom() { + return zoom; +} + +void TimelineHeader::delete_markers() { + if (selected_markers.size() > 0) { + + // Send command to delete selected markers + DeleteMarkerAction* dma = new DeleteMarkerAction(viewer->marker_ref); + dma->markers.append(selected_markers); + olive::undo_stack.push(dma); + + // remove any indices for the selected markers that no longer exist + for (int i=0;i= viewer->marker_ref->size()) { + selected_markers.removeAt(i); + i--; + } + } + + // if we removed all the indices, re-select the last marker in the array so something is always selected + // (allows users to hold delete when deleting markers) + if (selected_markers.isEmpty() && !viewer->marker_ref->isEmpty()) { + selected_markers.append(viewer->marker_ref->size() - 1); + } + + update_parents(); + } +} + +void TimelineHeader::paintEvent(QPaintEvent*) { + if (viewer->seq != nullptr && zoom > 0) { + QPainter p(this); + int yoff = get_marker_offset(); + + double interval = viewer->seq->frame_rate(); + int textWidth = 0; + int lastTextBoundary = INT_MIN; + + int lastLineX = INT_MIN; + + int sublineCount = 1; + int sublineTest = qRound(interval*zoom); + int sublineInterval = 1; + while (sublineTest > SUBLINE_MIN_PADDING + && sublineInterval >= 1) { + sublineCount *= 2; + sublineInterval = (interval/sublineCount); + sublineTest = qRound(sublineInterval*zoom); + } + sublineCount = qMin(sublineCount, qRound(interval)); + + int text_x, fullTextWidth; + QString timecode; + + // find where to start drawing lines (lineX algorithm reversed if lineX = 0) + int i = qFloor(double(scroll)/zoom/interval); + + while (true) { + long frame = qRound(interval*i); + int lineX = qRound(frame*zoom) - scroll; + + if (lineX > width()) break; + + // draw text + bool draw_text = false; + if (text_enabled && lineX-textWidth > lastTextBoundary) { + 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::config.center_timeline_timecodes) { + text_x -= textWidth; + } else { + text_x += TEXT_PADDING_FROM_LINE; + } + + lastTextBoundary = lineX+textWidth; + if (lastTextBoundary >= 0) { + draw_text = true; + } + } + + if (lineX > lastLineX+LINE_MIN_PADDING) { + if (draw_text) { + p.setPen(olive::styling::GetIconColor()); + p.drawText(QRect(text_x, 0, fullTextWidth, yoff), timecode); + } + + // draw line markers + p.setPen(Qt::gray); + p.drawLine(lineX, (!olive::config.center_timeline_timecodes && draw_text) ? 0 : yoff, lineX, height()); + + // draw sub-line markers + for (int j=1;jseq->using_workarea) { + in_x = getHeaderScreenPointFromFrame((resizing_workarea ? temp_workarea_in : viewer->seq->workarea_in)); + int out_x = getHeaderScreenPointFromFrame((resizing_workarea ? temp_workarea_out : viewer->seq->workarea_out)); + p.fillRect(QRect(in_x, 0, out_x-in_x, height()), QColor(0, 192, 255, 128)); + p.setPen(olive::styling::GetIconColor()); + p.drawLine(in_x, 0, in_x, height()); + p.drawLine(out_x, 0, out_x, height()); + } + + // draw markers + for (int i=0;imarker_ref->size();i++) { + const Marker& m = viewer->marker_ref->at(i); + + int marker_x = getHeaderScreenPointFromFrame(m.frame); + + bool selected = false; + for (int j=0;jseq->playhead); + QPoint start(in_x, height()+2); + QPainterPath path; + path.moveTo(start + QPoint(1,0)); + path.lineTo(in_x-PLAYHEAD_SIZE, yoff); + path.lineTo(in_x+PLAYHEAD_SIZE+1, yoff); + path.lineTo(start); + p.fillPath(path, Qt::red); + + // Draw white line at the top for clarity + p.setPen(Qt::gray); + p.drawLine(0, 0, width(), 0); + } +} + +void TimelineHeader::show_context_menu(const QPoint &pos) { + Menu menu(this); + + // Add items for setting the in/out points of a QMenu + olive::MenuHelper.make_inout_menu(&menu); + + menu.addSeparator(); + + QAction* center_timecodes = menu.addAction(tr("Center Timecodes"), &olive::MenuHelper, SLOT(toggle_bool_action())); + center_timecodes->setCheckable(true); + center_timecodes->setChecked(olive::config.center_timeline_timecodes); + center_timecodes->setData(reinterpret_cast(&olive::config.center_timeline_timecodes)); + + menu.exec(mapToGlobal(pos)); +} + +void TimelineHeader::resized_scroll_listener(double d) { + update_zoom(zoom * d); +} diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 596365d02..e2a954e46 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -1,99 +1,99 @@ -/*** - - 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 TIMELINEHEADER_H -#define TIMELINEHEADER_H - -#include -#include -class Viewer; -class QScrollBar; - -bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead); - -class TimelineHeader : public QWidget -{ - Q_OBJECT -public: - explicit TimelineHeader(QWidget *parent = 0); - void set_in_point(long p); - void set_out_point(long p); - - Viewer* viewer; - - 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); - -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); - -protected: - void paintEvent(QPaintEvent*); - void mousePressEvent(QMouseEvent*); - void mouseMoveEvent(QMouseEvent*); - void mouseReleaseEvent(QMouseEvent*); - void focusOutEvent(QFocusEvent*); - -private: - void update_parents(); - - bool dragging; - - bool resizing_workarea; - bool resizing_workarea_in; - long temp_workarea_in; - long temp_workarea_out; - long sequence_end; - - double zoom; - - long in_visible; - - void set_playhead(int mouse_x); - - int get_marker_offset(); - - QFontMetrics fm; - - int drag_start; - bool dragging_markers; - QVector selected_markers; - QVector selected_marker_original_times; - - long getHeaderFrameFromScreenPoint(int x); - int getHeaderScreenPointFromFrame(long frame); - - int scroll; - - int height_actual; - bool text_enabled; - -signals: -}; - -#endif // TIMELINEHEADER_H +/*** + + 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 TIMELINEHEADER_H +#define TIMELINEHEADER_H + +#include +#include +class Viewer; +class QScrollBar; + +bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead); + +class TimelineHeader : public QWidget +{ + Q_OBJECT +public: + explicit TimelineHeader(QWidget *parent = 0); + void set_in_point(long p); + void set_out_point(long p); + + Viewer* viewer; + + 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); + +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); + +protected: + void paintEvent(QPaintEvent*); + void mousePressEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void focusOutEvent(QFocusEvent*); + +private: + void update_parents(); + + bool dragging; + + bool resizing_workarea; + bool resizing_workarea_in; + long temp_workarea_in; + long temp_workarea_out; + long sequence_end; + + double zoom; + + long in_visible; + + void set_playhead(int mouse_x); + + int get_marker_offset(); + + QFontMetrics fm; + + int drag_start; + bool dragging_markers; + QVector selected_markers; + QVector selected_marker_original_times; + + long getHeaderFrameFromScreenPoint(int x); + int getHeaderScreenPointFromFrame(long frame); + + int scroll; + + int height_actual; + bool text_enabled; + +signals: +}; + +#endif // TIMELINEHEADER_H diff --git a/ui/timelineview.cpp b/ui/timelineview.cpp index f5c470ee3..5d3643db4 100644 --- a/ui/timelineview.cpp +++ b/ui/timelineview.cpp @@ -1,3398 +1,3398 @@ -/*** - - 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/oldeffectnode.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), - sequence_(nullptr), - type_(olive::kTypeCount), - 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::SetTrackType(Sequence* s, olive::TrackType type) -{ - sequence_ = s; - type_ = type; - - 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 != nullptr - && 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 = sequence_->FirstTrack(type_); - } - - 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 (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) { - QVector track_list = sequence_->GetTrackList(type_); - 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; - - QVector track_list = sequence_->GetTrackList(type_); - for (int i=0;iheight(); - } - - 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")); - OldEffectNodePtr 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() == 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() == 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 { - QVector track_list = sequence_->GetTrackList(type_); - - // 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;i 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 (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 (sequence_ != nullptr) { - QPainter p(this); - - // get widget width and height - emit setScrollMaximum(GetTotalAreaHeight()); - - QVector track_list = sequence_->GetTrackList(type_); - for (int i=0;iheight(); - - 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() == 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); - - QVector track_list = sequence_->GetTrackList(type_); - - if (index < track_list.size()) { - return track_list.at(index); - } - - return nullptr; - -} - -int TimelineView::getScreenPointFromTrack(Track *track) { - return getScreenPointFromTrackIndex(track->Index()); -} - -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; - - QVector track_list = sequence_->GetTrackList(type_); - - while (true) { - - int new_heights = heights; - - if (i < track_list.size()) { - new_heights += track_list.at(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; - - int loop_start = 0; - int loop_end = track; - if (alignment_ == olive::timeline::kAlignmentBottom) { - loop_start++; - loop_end++; - } - for (int i=loop_start;iFirstTrack(type_)->height() - 1; - } - - return point - scroll; -} - -int TimelineView::getTrackHeightFromTrackIndex(int track) -{ - if (track < sequence_->TrackCount(type_)) { - return sequence_->TrackAt(type_, track)->height(); - } else { - return olive::timeline::kTrackDefaultHeight; - } -} - -Timeline *TimelineView::ParentTimeline() -{ - return timeline_; -} - -Sequence *TimelineView::sequence() -{ - return sequence_; -} - -void TimelineView::setScroll(int s) { - scroll = s; - update(); -} - -void TimelineView::reveal_media() { - panel_project.first()->reveal_media(rc_reveal_media); -} +/*** + + 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/oldeffectnode.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), + sequence_(nullptr), + type_(olive::kTypeCount), + 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::SetTrackType(Sequence* s, olive::TrackType type) +{ + sequence_ = s; + type_ = type; + + 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 != nullptr + && 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 = sequence_->FirstTrack(type_); + } + + 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 (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) { + QVector track_list = sequence_->GetTrackList(type_); + 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; + + QVector track_list = sequence_->GetTrackList(type_); + for (int i=0;iheight(); + } + + 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")); + OldEffectNodePtr 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() == 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() == 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 { + QVector track_list = sequence_->GetTrackList(type_); + + // 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;i 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 (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 (sequence_ != nullptr) { + QPainter p(this); + + // get widget width and height + emit setScrollMaximum(GetTotalAreaHeight()); + + QVector track_list = sequence_->GetTrackList(type_); + for (int i=0;iheight(); + + 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() == 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); + + QVector track_list = sequence_->GetTrackList(type_); + + if (index < track_list.size()) { + return track_list.at(index); + } + + return nullptr; + +} + +int TimelineView::getScreenPointFromTrack(Track *track) { + return getScreenPointFromTrackIndex(track->Index()); +} + +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; + + QVector track_list = sequence_->GetTrackList(type_); + + while (true) { + + int new_heights = heights; + + if (i < track_list.size()) { + new_heights += track_list.at(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; + + int loop_start = 0; + int loop_end = track; + if (alignment_ == olive::timeline::kAlignmentBottom) { + loop_start++; + loop_end++; + } + for (int i=loop_start;iFirstTrack(type_)->height() - 1; + } + + return point - scroll; +} + +int TimelineView::getTrackHeightFromTrackIndex(int track) +{ + if (track < sequence_->TrackCount(type_)) { + return sequence_->TrackAt(type_, track)->height(); + } else { + return olive::timeline::kTrackDefaultHeight; + } +} + +Timeline *TimelineView::ParentTimeline() +{ + return timeline_; +} + +Sequence *TimelineView::sequence() +{ + return sequence_; +} + +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 index dd5f100ff..91a2bdb03 100644 --- a/ui/timelineview.h +++ b/ui/timelineview.h @@ -1,128 +1,128 @@ -/*** - - 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 SetTrackType(Sequence* s, olive::TrackType type); - - 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_; - Sequence* sequence_; - olive::TrackType type_; - - 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 +/*** + + 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 SetTrackType(Sequence* s, olive::TrackType type); + + 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_; + Sequence* sequence_; + olive::TrackType type_; + + 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.cpp b/ui/timelinewidget.cpp index eb078189e..588cee783 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -1,3451 +1,3451 @@ -/*** - - 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 "timelinewidget.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 "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 "effects/effect.h" -#include "effects/internal/solideffect.h" - -#define MAX_TEXT_WIDTH 20 -#define TRANSITION_BETWEEN_RANGE 40 - -TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent) { - selection_command = nullptr; - self_created_sequence = nullptr; - scroll = 0; - - bottom_align = false; - track_resizing = false; - setMouseTracking(true); - - 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 TimelineWidget::show_context_menu(const QPoint& pos) { - if (olive::ActiveSequence != nullptr) { - // hack because sometimes right clicking doesn't trigger mouse release event - panel_timeline->rect_select_init = false; - panel_timeline->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::UndoStack.canUndo()); - redoAction->setEnabled(olive::UndoStack.canRedo()); - menu.addSeparator(); - - // collect all the selected clips - QVector selected_clips = olive::ActiveSequence->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 - panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(pos.x()); - panel_timeline->cursor_track = getTrackFromScreenPoint(pos.y()); - - if (panel_timeline->can_ripple_empty_space(panel_timeline->cursor_frame, panel_timeline->cursor_track)) { - QAction* ripple_delete_action = menu.addAction(tr("R&ipple Delete Empty Space")); - connect(ripple_delete_action, SIGNAL(triggered(bool)), panel_timeline, 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;itrack() < 0) { - 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); - - // stabilizer option - /*int video_clip_count = 0; - bool all_video_is_footage = true; - for (int i=0;itrack() < 0) { - video_clip_count++; - if (selected_clips.at(i)->media() == nullptr - || selected_clips.at(i)->media()->get_type() != MEDIA_TYPE_FOOTAGE) { - all_video_is_footage = false; - } - } - } - if (video_clip_count == 1 && all_video_is_footage) { - QAction* stabilizerAction = menu.addAction("S&tabilizer"); - connect(stabilizerAction, SIGNAL(triggered(bool)), this, SLOT(show_stabilizer_diag())); - }*/ - - // 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 TimelineWidget::toggle_autoscale() { - QVector selected_clips = olive::ActiveSequence->SelectedClips(); - - if (!selected_clips.isEmpty()) { - SetClipProperty* action = new SetClipProperty(kSetClipPropertyAutoscale); - - for (int i=0;iAddSetting(c, !c->autoscaled()); - } - - olive::UndoStack.push(action); - } -} - -void TimelineWidget::tooltip_timer_timeout() { - if (olive::ActiveSequence != nullptr) { - if (tooltip_clip < olive::ActiveSequence->clips.size()) { - ClipPtr c = olive::ActiveSequence->clips.at(tooltip_clip); - if (c != nullptr) { - QToolTip::showText(QCursor::pos(), - tr("%1\nStart: %2\nEnd: %3\nDuration: %4").arg( - c->name(), - frame_to_timecode(c->timeline_in(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate), - frame_to_timecode(c->timeline_out(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate), - frame_to_timecode(c->length(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate) - )); - } - } - } - tooltip_timer.stop(); -} - -void TimelineWidget::open_sequence_properties() { - QList sequence_items; - QList all_top_level_items; - for (int i=0;iget_all_media_from_table(all_top_level_items, sequence_items, MEDIA_TYPE_SEQUENCE); // find all sequences in project - for (int i=0;ito_sequence() == olive::ActiveSequence) { - NewSequenceDialog nsd(this, sequence_items.at(i)); - nsd.exec(); - return; - } - } - QMessageBox::critical(this, tr("Error"), tr("Couldn't locate media wrapper for sequence.")); -} - -void TimelineWidget::show_clip_properties() -{ - // get list of selected clips - QVector selected_clips = olive::ActiveSequence->SelectedClips(); - - // if clips are selected, open the clip properties dialog - if (!selected_clips.isEmpty()) { - ClipPropertiesDialog cpd(this, selected_clips); - cpd.exec(); - } -} - -bool same_sign(int a, int b) { - return (a < 0) == (b < 0); -} - -void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { - bool import_init = false; - - QVector media_list; - panel_timeline->importing_files = false; - - if (panel_project->IsProjectWidget(event->source())) { - QModelIndexList items = panel_project->get_current_selected(); - media_list.resize(items.size()); - for (int i=0;iitem_to_media(items.at(i)); - } - import_init = true; - } - - if (event->source() == panel_footage_viewer) { - if (panel_footage_viewer->seq != olive::ActiveSequence) { // 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::CurrentConfig.enable_drag_files_to_timeline && event->mimeData()->hasUrls()) { - QList urls = event->mimeData()->urls(); - if (!urls.isEmpty()) { - QStringList file_list; - - for (int i=0;iprocess_file_list(file_list); - - for (int i=0;ilast_imported_media.size();i++) { - Footage* f = panel_project->last_imported_media.at(i)->to_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(panel_project->last_imported_media.at(i)); - } - } - - if (media_list.isEmpty()) { - olive::UndoStack.undo(); - } else { - import_init = true; - panel_timeline->importing_files = true; - } - } - } - - if (import_init) { - event->acceptProposedAction(); - - long entry_point; - Sequence* seq = olive::ActiveSequence.get(); - - 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 = create_sequence_from_media(media_list); - seq = self_created_sequence.get(); - } else { - entry_point = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); - panel_timeline->drag_frame_start = entry_point + getFrameFromScreenPoint(panel_timeline->zoom, 50); - panel_timeline->drag_track_start = (bottom_align) ? -1 : 0; - } - - panel_timeline->create_ghosts_from_media(seq, entry_point, media_list); - - panel_timeline->importing = true; - } -} - -void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) { - if (panel_timeline->importing) { - event->acceptProposedAction(); - - if (olive::ActiveSequence != nullptr) { - QPoint pos = event->pos(); - panel_timeline->scroll_to_frame(panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x())); - update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier); - panel_timeline->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing)); - update_ui(false); - } - } -} - -void TimelineWidget::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::CurrentConfig.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::CurrentConfig.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; - } - - panel_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 = panel_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 TimelineWidget::dragLeaveEvent(QDragLeaveEvent* event) { - event->accept(); - if (panel_timeline->importing) { - if (panel_timeline->importing_files) { - olive::UndoStack.undo(); - } - panel_timeline->importing_files = false; - panel_timeline->ghosts.clear(); - panel_timeline->importing = false; - update_ui(false); - } - if (self_created_sequence != nullptr) { - self_created_sequence.reset(); - self_created_sequence = nullptr; - } -} - -void delete_area_under_ghosts(ComboAction* ca) { - // delete areas before adding - QVector delete_areas; - for (int i=0;ighosts.size();i++) { - const Ghost& g = panel_timeline->ghosts.at(i); - Selection sel; - sel.in = g.in; - sel.out = g.out; - sel.track = g.track; - delete_areas.append(sel); - } - panel_timeline->delete_areas_and_relink(ca, delete_areas, false); -} - -void insert_clips(ComboAction* ca) { - 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 = panel_timeline->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 >= 0) { - ignore_clips.append(g.clip); - } else { - // don't try to close old gap if importing - ripple_old_point = false; - } - } - - panel_timeline->split_cache.clear(); - - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - // don't split any clips that are moving - bool found = false; - for (int j=0;jghosts.size();j++) { - if (panel_timeline->ghosts.at(j).clip == i) { - found = true; - break; - } - } - if (!found) { - if (c->timeline_in() < earliest_new_point && c->timeline_out() > earliest_new_point) { - panel_timeline->split_clip_and_relink(ca, i, 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(i)) { - ripple_old_point = false; - } - } - } - } - - long ripple_length = (latest_new_point - earliest_new_point); - - ripple_clips(ca, olive::ActiveSequence.get(), 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); - - ripple_clips(ca, olive::ActiveSequence.get(), latest_old_point, second_ripple_length, ignore_clips); - - if (earliest_old_point < earliest_new_point) { - for (int i=0;ighosts.size();i++) { - Ghost& g = panel_timeline->ghosts[i]; - g.in += second_ripple_length; - g.out += second_ripple_length; - } - for (int i=0;iselections.size();i++) { - Selection& s = olive::ActiveSequence->selections[i]; - s.in += second_ripple_length; - s.out += second_ripple_length; - } - } - } -} - -void TimelineWidget::dropEvent(QDropEvent* event) { - if (panel_timeline->importing && panel_timeline->ghosts.size() > 0) { - event->acceptProposedAction(); - - ComboAction* ca = new ComboAction(); - - Sequence* s = olive::ActiveSequence.get(); - - // if we're dropping into nothing, create a new sequences based on the clip being dragged - if (s == nullptr) { - s = self_created_sequence.get(); - panel_project->create_sequence_internal(ca, self_created_sequence, true, nullptr); - self_created_sequence = nullptr; - } else if (event->keyboardModifiers() & Qt::ControlModifier) { - insert_clips(ca); - } else { - delete_area_under_ghosts(ca); - } - - panel_timeline->add_clips_from_ghosts(ca, s); - - olive::UndoStack.push(ca); - - setFocus(); - - update_ui(true); - } -} - -void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *event) { - if (olive::ActiveSequence != nullptr) { - if (panel_timeline->tool == TIMELINE_TOOL_EDIT) { - int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); - if (clip_index >= 0) { - ClipPtr clip = olive::ActiveSequence->clips.at(clip_index); - if (!(event->modifiers() & Qt::ShiftModifier)) olive::ActiveSequence->selections.clear(); - Selection s; - s.in = clip->timeline_in(); - s.out = clip->timeline_out(); - s.track = clip->track(); - olive::ActiveSequence->selections.append(s); - update_ui(false); - } - } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER) { - int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); - if (clip_index >= 0) { - ClipPtr c = olive::ActiveSequence->clips.at(clip_index); - if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { - olive::Global->set_sequence(c->media()->to_sequence()); - } - } - } - } -} - -bool current_tool_shows_cursor() { - return (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR || panel_timeline->creating); -} - -void TimelineWidget::mousePressEvent(QMouseEvent *event) { - if (olive::ActiveSequence != nullptr) { - - int effective_tool = panel_timeline->tool; - - // some user actions will override which tool we'll be using - if (event->button() == Qt::MiddleButton) { - effective_tool = TIMELINE_TOOL_HAND; - panel_timeline->creating = false; - } else if (event->button() == Qt::RightButton) { - effective_tool = TIMELINE_TOOL_MENU; - panel_timeline->creating = false; - } - - // ensure cursor_frame and cursor_track are up to date - mouseMoveEvent(event); - - // store current cursor positions - panel_timeline->drag_x_start = event->pos().x(); - panel_timeline->drag_y_start = event->pos().y(); - - // store current frame/tracks as the values to start dragging from - panel_timeline->drag_frame_start = panel_timeline->cursor_frame; - panel_timeline->drag_track_start = panel_timeline->cursor_track; - - // get the clip the user is currently hovering over, priority to trim_target set from mouseMoveEvent - int hovered_clip = panel_timeline->trim_target == -1 ? - getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track) - : panel_timeline->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) { - panel_timeline->selection_offset = olive::ActiveSequence->selections.size(); - } else { - panel_timeline->selection_offset = 0; - } - - // if the user is creating an object - if (panel_timeline->creating) { - int comp = 0; - switch (panel_timeline->creating_object) { - case ADD_OBJ_TITLE: - case ADD_OBJ_SOLID: - case ADD_OBJ_BARS: - comp = -1; - break; - case ADD_OBJ_TONE: - case ADD_OBJ_NOISE: - case ADD_OBJ_AUDIO: - comp = 1; - break; - } - - // if the track the user clicked is correct for the type of object we're adding - - if ((panel_timeline->drag_track_start < 0) == (comp < 0)) { - Ghost g; - g.in = g.old_in = g.out = g.old_out = panel_timeline->drag_frame_start; - g.track = g.old_track = panel_timeline->drag_track_start; - g.transition = nullptr; - g.clip = -1; - g.trim_type = TRIM_OUT; - panel_timeline->ghosts.append(g); - - panel_timeline->moving_init = true; - panel_timeline->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 TIMELINE_TOOL_POINTER: - case TIMELINE_TOOL_RIPPLE: - case TIMELINE_TOOL_SLIP: - case TIMELINE_TOOL_ROLLING: - case TIMELINE_TOOL_SLIDE: - case TIMELINE_TOOL_MENU: - { - if (track_resizing && effective_tool != TIMELINE_TOOL_MENU) { - - // if the cursor is currently hovering over a track, init track resizing - panel_timeline->moving_init = true; - - } else { - - // check if we're currently hovering over a clip or not - if (hovered_clip >= 0) { - Clip* clip = olive::ActiveSequence->clips.at(hovered_clip).get(); - - if (clip->IsSelected()) { - - if (shift) { - - // if the user clicks a selected clip while holding shift, deselect the clip - panel_timeline->deselect_area(clip->timeline_in(), clip->timeline_out(), clip->track()); - - // if the user isn't holding alt, also deselect all of its links as well - if (!alt) { - for (int i=0;ilinked.size();i++) { - ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(i)); - panel_timeline->deselect_area(link->timeline_in(), link->timeline_out(), link->track()); - } - } - - } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER - && panel_timeline->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 - - panel_timeline->deselect_area(clip->timeline_in(), clip->timeline_out(), clip->track()); - - for (int i=0;ilinked.size();i++) { - ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(i)); - panel_timeline->deselect_area(link->timeline_in(), link->timeline_out(), link->track()); - } - - Selection s; - s.track = clip->track(); - - // select the transition only - if (panel_timeline->transition_select == kTransitionOpening && clip->opening_transition != nullptr) { - s.in = clip->timeline_in(); - - if (clip->opening_transition->secondary_clip != nullptr) { - s.in -= clip->opening_transition->get_true_length(); - } - - s.out = clip->timeline_in() + clip->opening_transition->get_true_length(); - } else if (panel_timeline->transition_select == kTransitionClosing && clip->closing_transition != nullptr) { - s.in = clip->timeline_out() - clip->closing_transition->get_true_length(); - s.out = clip->timeline_out(); - - if (clip->closing_transition->secondary_clip != nullptr) { - s.out += clip->closing_transition->get_true_length(); - } - } - olive::ActiveSequence->selections.append(s); - } - } else { - - // if the clip is not already selected - - // if shift is NOT down, we change clear all current selections - if (!shift) { - olive::ActiveSequence->selections.clear(); - } - - Selection s; - - s.in = clip->timeline_in(); - s.out = clip->timeline_out(); - s.track = clip->track(); - - // 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 (panel_timeline->tool == TIMELINE_TOOL_POINTER) { - if (panel_timeline->transition_select == kTransitionOpening) { - // move the selection to only select the transitoin - s.out = clip->timeline_in() + clip->opening_transition->get_true_length(); - - // if the transition is a "shared" transition, adjust the selection to select both sides - if (clip->opening_transition->secondary_clip != nullptr) { - s.in -= clip->opening_transition->get_true_length(); - } - } else if (panel_timeline->transition_select == kTransitionClosing) { - // move the selection to only select the transitoin - s.in = clip->timeline_out() - clip->closing_transition->get_true_length(); - - // if the transition is a "shared" transition, adjust the selection to select both sides - if (clip->closing_transition->secondary_clip != nullptr) { - s.out += clip->closing_transition->get_true_length(); - } - } - } - - // add the selection to the array - olive::ActiveSequence->selections.append(s); - - // if the config is set to also seek with selections, do so now - if (olive::CurrentConfig.select_also_seeks) { - panel_sequence_viewer->seek(clip->timeline_in()); - } - - // if alt is not down, select links (provided we're not selecting transitions) - if (!alt && panel_timeline->transition_select == kTransitionNone) { - - for (int i=0;ilinked.size();i++) { - - Clip* link = olive::ActiveSequence->clips.at(clip->linked.at(i)).get(); - - // check if the clip is already selected - if (!link->IsSelected()) { - Selection ss; - ss.in = link->timeline_in(); - ss.out = link->timeline_out(); - ss.track = link->track(); - olive::ActiveSequence->selections.append(ss); - } - - } - - } - } - - // authorize the starting of a move action if the mouse moves after this - if (effective_tool != TIMELINE_TOOL_MENU) { - panel_timeline->moving_init = true; - } - - } else { - - // if the user did not click a clip at all, we start a rectangle selection - - if (!shift) { - olive::ActiveSequence->selections.clear(); - } - - panel_timeline->rect_select_init = true; - } - - // update everything - update_ui(false); - } - } - break; - case TIMELINE_TOOL_HAND: - - // initiate moving with the hand tool - panel_timeline->hand_moving = true; - - break; - case TIMELINE_TOOL_EDIT: - - // if the config is set to seek with the edit tool, do so now - if (olive::CurrentConfig.edit_tool_also_seeks) { - panel_sequence_viewer->seek(panel_timeline->drag_frame_start); - } - - // initiate selecting - panel_timeline->selecting = true; - - break; - case TIMELINE_TOOL_RAZOR: - { - - // initiate razor tool - panel_timeline->splitting = true; - - // add this track as a track being split by the razor - panel_timeline->split_tracks.append(panel_timeline->drag_track_start); - - update_ui(false); - } - break; - case TIMELINE_TOOL_TRANSITION: - { - - // if there is a clip to run the transition tool on, initiate the transition tool - if (panel_timeline->transition_tool_open_clip > -1 - || panel_timeline->transition_tool_close_clip > -1) { - panel_timeline->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 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); - - int track = 0; - - // 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; - Selection s; - s.in = transition_start; - s.out = transition_end; - s.track = track; - areas.append(s); - panel_timeline->delete_areas_and_relink(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()); - } - } - } -} - -void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { - QToolTip::hideText(); - if (olive::ActiveSequence != 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 (panel_timeline->creating) { - if (panel_timeline->ghosts.size() > 0) { - const Ghost& g = panel_timeline->ghosts.at(0); - - if (panel_timeline->creating_object == ADD_OBJ_AUDIO) { - olive::MainWindow->statusBar()->clearMessage(); - panel_sequence_viewer->cue_recording(qMin(g.in, g.out), qMax(g.in, g.out), g.track); - panel_timeline->creating = false; - } else if (g.in != g.out) { - ClipPtr c = std::make_shared(olive::ActiveSequence.get()); - 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); - c->set_track(g.track); - - if (ctrl) { - insert_clips(ca); - } else { - Selection s; - s.in = c->timeline_in(); - s.out = c->timeline_out(); - s.track = c->track(); - QVector areas; - areas.append(s); - panel_timeline->delete_areas_and_relink(ca, areas, false); - } - - QVector add; - add.append(c); - ca->append(new AddClipCommand(olive::ActiveSequence.get(), add)); - - if (c->track() < 0 && olive::CurrentConfig.add_default_effects_to_clips) { - // default video effects (before custom effects) - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT))); - } - - switch (panel_timeline->creating_object) { - case ADD_OBJ_TITLE: - c->set_name(tr("Title")); - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_RICHTEXT, EFFECT_TYPE_EFFECT))); - break; - case ADD_OBJ_SOLID: - c->set_name(tr("Solid Color")); - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_SOLID, EFFECT_TYPE_EFFECT))); - break; - case ADD_OBJ_BARS: - { - c->set_name(tr("Bars")); - EffectPtr e = Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_SOLID, EFFECT_TYPE_EFFECT)); - - // Auto-select bars - SolidEffect* solid_effect = static_cast(e.get()); - solid_effect->SetType(SolidEffect::SOLID_TYPE_BARS); - - c->effects.append(e); - } - break; - case ADD_OBJ_TONE: - c->set_name(tr("Tone")); - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TONE, EFFECT_TYPE_EFFECT))); - break; - case ADD_OBJ_NOISE: - c->set_name(tr("Noise")); - c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_NOISE, EFFECT_TYPE_EFFECT))); - break; - } - - if (c->track() >= 0 && olive::CurrentConfig.add_default_effects_to_clips) { - // default audio effects (after custom 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))); - } - - push_undo = true; - - if (!shift) { - panel_timeline->creating = false; - } - } - } - } else if (panel_timeline->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 = panel_timeline->ghosts.at(i); - if (g.in != g.old_in - || g.out != g.old_out - || g.clip_in != g.old_clip_in - || g.track != g.old_track) { - process_moving = true; - break; - } - } - - if (process_moving) { - const Ghost& first_ghost = panel_timeline->ghosts.at(0); - - // start a ripple movement - if (panel_timeline->tool == 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 (panel_timeline->trim_type == 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++) { - olive::ActiveSequence->selections[i].in += ripple_length; - olive::ActiveSequence->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 - panel_timeline->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 = panel_timeline->ghosts.at(i); - - // for the same reason that we pushed selections forward above, for in trimming, - // we push the ghosts forward here - if (panel_timeline->trim_type == TRIM_IN) { - ignore_clips.append(g.clip); - panel_timeline->ghosts[i].in += ripple_length; - panel_timeline->ghosts[i].out += ripple_length; - } - - // find the earliest ripple point - long comp_point = (panel_timeline->trim_type == 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 (panel_timeline->trim_type == TRIM_OUT) ripple_length = -ripple_length; - - // finally, ripple everything - ripple_clips(ca, olive::ActiveSequence.get(), ripple_point, ripple_length, ignore_clips); - } - - if (panel_timeline->tool == TIMELINE_TOOL_POINTER - && (event->modifiers() & Qt::AltModifier) - && panel_timeline->trim_target == -1) { - - // 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 = panel_timeline->ghosts.at(i); - if (g.old_in != g.in || g.old_out != g.out || g.track != g.old_track || g.clip_in != g.old_clip_in) { - - // create copy of clip - ClipPtr c = olive::ActiveSequence->clips.at(g.clip)->copy(olive::ActiveSequence.get()); - - c->set_timeline_in(g.in); - c->set_timeline_out(g.out); - c->set_track(g.track); - - Selection s; - s.in = g.in; - s.out = g.out; - s.track = g.track; - delete_areas.append(s); - - old_clips.append(g.clip); - new_clips.append(c); - - } - } - - if (new_clips.size() > 0) { - - // delete anything under the new clips - panel_timeline->delete_areas_and_relink(ca, delete_areas, false); - - // relink duplicated clips - panel_timeline->relink_clips_using_ids(old_clips, new_clips); - - // add them - ca->append(new AddClipCommand(olive::ActiveSequence.get(), 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 (panel_timeline->tool == TIMELINE_TOOL_POINTER && ctrl) { - - insert_clips(ca); - - } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == 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 = panel_timeline->ghosts.at(i); - - // set clip to undeletable so it's unaffected by delete_areas_and_relink() below - olive::ActiveSequence->clips.at(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 - Selection s; - s.in = g.in; - s.out = g.out; - s.track = g.track; - delete_areas.append(s); - } - - panel_timeline->delete_areas_and_relink(ca, delete_areas, false); - - // clean up, i.e. make everything not undeletable again - for (int i=0;ighosts.size();i++) { - const Ghost& g = panel_timeline->ghosts.at(i); - olive::ActiveSequence->clips.at(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 = panel_timeline->ghosts[i]; - - Clip* c = olive::ActiveSequence->clips.at(g.clip).get(); - - 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 - g.old_track), 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 == 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, 0, false, true); - g.transition->secondary_clip->move(ca, timeline_in_movement, movement, timeline_in_movement, 0, 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), 0, 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, 0, 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 = panel_timeline->ghosts.at(i); - - // only applies to moving clips, transitions are verified above instead - if (g.transition == nullptr) { - ClipPtr c = olive::ActiveSequence->clips.at(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)) { - - // 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 = panel_timeline->ghosts.at(j); - - if (olive::ActiveSequence->clips.at(other_clip_ghost.clip).get() == 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; - 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, - nullptr, - 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, - nullptr, - 0)); - - } - } - - } - } - } - } - } - } - } - push_undo = true; - } - } else if (panel_timeline->selecting || panel_timeline->rect_select_proc) { - } else if (panel_timeline->transition_tool_proc) { - const Ghost& g = panel_timeline->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 = (panel_timeline->transition_tool_open_clip > -1) - ? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip).get() - : nullptr; - - Clip* close = (panel_timeline->transition_tool_close_clip > -1) - ? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip).get() - : nullptr; - - - - // 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, - panel_timeline->transition_tool_meta, - transition_length)); - - push_undo = true; - } - } else if (panel_timeline->splitting) { - bool split = false; - for (int i=0;isplit_tracks.size();i++) { - int split_index = getClipIndexFromCoords(panel_timeline->drag_frame_start, panel_timeline->split_tracks.at(i)); - if (split_index > -1 && panel_timeline->split_clip_and_relink(ca, split_index, panel_timeline->drag_frame_start, !alt)) { - split = true; - } - } - if (split) { - push_undo = true; - } - panel_timeline->split_cache.clear(); - } - - // remove duplicate selections - panel_timeline->clean_up_selections(olive::ActiveSequence->selections); - - if (selection_command != nullptr) { - selection_command->new_data = olive::ActiveSequence->selections; - ca->append(selection_command); - selection_command = nullptr; - push_undo = true; - } - - if (push_undo) { - olive::UndoStack.push(ca); - } else { - delete ca; - } - - // destroy all ghosts - panel_timeline->ghosts.clear(); - - // clear split tracks - panel_timeline->split_tracks.clear(); - - panel_timeline->selecting = false; - panel_timeline->moving_proc = false; - panel_timeline->moving_init = false; - panel_timeline->splitting = false; - panel_timeline->snapped = false; - panel_timeline->rect_select_init = false; - panel_timeline->rect_select_proc = false; - panel_timeline->transition_tool_init = false; - panel_timeline->transition_tool_proc = false; - pre_clips.clear(); - post_clips.clear(); - - update_ui(true); - } - panel_timeline->hand_moving = false; - } -} - -void TimelineWidget::init_ghosts() { - for (int i=0;ighosts.size();i++) { - Ghost& g = panel_timeline->ghosts[i]; - ClipPtr c = olive::ActiveSequence->clips.at(g.clip); - - g.track = g.old_track = c->track(); - g.clip_in = g.old_clip_in = c->clip_in(); - - if (panel_timeline->tool == 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 = olive::ActiveSequence->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 TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { - int effective_tool = panel_timeline->tool; - if (panel_timeline->importing || panel_timeline->creating) effective_tool = TIMELINE_TOOL_POINTER; - - int mouse_track = getTrackFromScreenPoint(mouse_pos.y()); - long frame_diff = (lock_frame) ? 0 : panel_timeline->getTimelineFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start; - int track_diff = ((effective_tool == TIMELINE_TOOL_SLIDE || panel_timeline->transition_select != kTransitionNone) && !panel_timeline->importing) ? 0 : mouse_track - panel_timeline->drag_track_start; - long validator; - long earliest_in_point = LONG_MAX; - - // first try to snap - long fm; - - if (effective_tool != 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 = panel_timeline->ghosts.at(i); - - // snap ghost's in point - if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1) - || g.trim_type == TRIM_IN - || panel_timeline->transition_tool_open_clip > -1) { - fm = g.old_in + frame_diff; - if (panel_timeline->snap_to_timeline(&fm, true, true, true)) { - frame_diff = fm - g.old_in; - break; - } - } - - // snap ghost's out point - if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1) - || g.trim_type == TRIM_OUT - || panel_timeline->transition_tool_close_clip > -1) { - fm = g.old_out + frame_diff; - if (panel_timeline->snap_to_timeline(&fm, true, true, true)) { - frame_diff = fm - g.old_out; - break; - } - } - - // if the ghost is attached to a clip, snap its markers too - if (panel_timeline->trim_target == -1 && g.clip >= 0 && panel_timeline->tool != TIMELINE_TOOL_TRANSITION) { - ClipPtr c = olive::ActiveSequence->clips.at(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 (panel_timeline->snap_to_timeline(&fm, true, true, true)) { - frame_diff = fm - marker_real_time; - break; - } - } - } - } - } - - bool clips_are_movable = (effective_tool == TIMELINE_TOOL_POINTER || effective_tool == 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 = panel_timeline->ghosts.at(i); - Clip* c = nullptr; - if (g.clip != -1) { - c = olive::ActiveSequence->clips.at(g.clip).get(); - } - - const FootageStream* ms = nullptr; - if (g.clip != -1 && c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - ms = c->media_stream(); - } - - // validate ghosts for trimming - if (panel_timeline->creating) { - // i feel like we might need something here but we haven't so far? - } else if (effective_tool == 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 != TRIM_NONE) { - if (g.trim_type == 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 != 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 == 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 == TRIM_IN) { - frame_diff += g.transition->get_true_length(); - } else { - frame_diff -= g.transition->get_true_length(); - } - } - - // ripple ops - if (effective_tool == TIMELINE_TOOL_RIPPLE) { - for (int j=0;jtrim_type == 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 (panel_timeline->trim_type == 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 clips from crossing tracks - if (same_sign(g.old_track, panel_timeline->drag_track_start)) { - while (!same_sign(g.old_track, g.old_track + track_diff)) { - if (g.old_track < 0) { - track_diff--; - } else { - track_diff++; - } - } - } - } else if (effective_tool == TIMELINE_TOOL_TRANSITION) { - if (panel_timeline->transition_tool_open_clip == -1 - || panel_timeline->transition_tool_close_clip == -1) { - validate_transitions(c, g.media_stream, frame_diff); - } else { - // open transition clip - Clip* otc = olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip).get(); - - // close transition clip - Clip* ctc = olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip).get(); - - 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) { - panel_timeline->snapped = false; - } - - // apply changes to ghosts - for (int i=0;ighosts.size();i++) { - Ghost& g = panel_timeline->ghosts[i]; - - if (effective_tool == TIMELINE_TOOL_SLIP) { - g.clip_in = g.old_clip_in - frame_diff; - } else if (g.trim_type != TRIM_NONE) { - long ghost_diff = frame_diff; - - // prevent trimming clips from overlapping each other - for (int j=0;jghosts.size();j++) { - const Ghost& comp = panel_timeline->ghosts.at(j); - if (i != j && g.track == comp.track) { - long validator; - if (g.trim_type == 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 == 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 == 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.track = g.old_track; - g.in = g.old_in + frame_diff; - g.out = g.old_out + frame_diff; - - if (g.transition != nullptr - && g.transition == olive::ActiveSequence->clips.at(g.clip)->opening_transition) { - g.clip_in = g.old_clip_in + frame_diff; - } - - if (panel_timeline->importing) { - if ((panel_timeline->video_ghosts && mouse_track < 0) - || (panel_timeline->audio_ghosts && mouse_track >= 0)) { - int abs_track_diff = abs(track_diff); - if (g.old_track < 0) { // clip is video - g.track -= abs_track_diff; - } else { // clip is audio - g.track += abs_track_diff; - } - } - } else if (same_sign(g.old_track, panel_timeline->drag_track_start)) { - g.track += track_diff; - } - } else if (effective_tool == TIMELINE_TOOL_TRANSITION) { - if (panel_timeline->transition_tool_open_clip > -1 - && panel_timeline->transition_tool_close_clip > -1) { - g.in = g.old_in - frame_diff; - g.out = g.old_out + frame_diff; - } else if (panel_timeline->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 != TIMELINE_TOOL_SLIP && !panel_timeline->importing && !panel_timeline->creating) { - for (int i=0;iselections.size();i++) { - Selection& s = olive::ActiveSequence->selections[i]; - if (panel_timeline->trim_target > -1) { - if (panel_timeline->trim_type == 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 = olive::ActiveSequence->selections[i]; - s.in = s.old_in + frame_diff; - s.out = s.old_out + frame_diff; - s.track = s.old_track; - - if (panel_timeline->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, panel_timeline->drag_track_start)) s.track += track_diff; - } - } - } - } - } - - if (panel_timeline->importing) { - QToolTip::showText(mapToGlobal(mouse_pos), frame_to_timecode(earliest_in_point, olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate)); - } else { - QString tip = ((frame_diff < 0) ? "-" : "+") + frame_to_timecode(qAbs(frame_diff), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate); - if (panel_timeline->trim_target > -1) { - // find which clip is being moved - const Ghost* g = nullptr; - for (int i=0;ighosts.size();i++) { - if (panel_timeline->ghosts.at(i).clip == panel_timeline->trim_target) { - g = &panel_timeline->ghosts.at(i); - break; - } - } - - if (g != nullptr) { - tip += " " + tr("Duration:") + " "; - long len = (g->old_out-g->old_in); - if (panel_timeline->trim_type == TRIM_IN) { - len -= frame_diff; - } else { - len += frame_diff; - } - tip += frame_to_timecode(len, olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate); - } - } - QToolTip::showText(mapToGlobal(mouse_pos), tip); - } -} - -void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { - // interrupt any potential tooltip about to show - tooltip_timer.stop(); - - if (olive::ActiveSequence != nullptr) { - bool alt = (event->modifiers() & Qt::AltModifier); - - // store current frame/track corresponding to the cursor - panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); - panel_timeline->cursor_track = getTrackFromScreenPoint(event->pos().y()); - - // if holding the mouse button down, let's scroll to that location - if (event->buttons() != 0 && panel_timeline->tool != TIMELINE_TOOL_HAND) { - panel_timeline->scroll_to_frame(panel_timeline->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 - panel_timeline->move_insert = ((event->modifiers() & Qt::ControlModifier) - && (panel_timeline->tool == TIMELINE_TOOL_POINTER - || panel_timeline->importing - || panel_timeline->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 (!panel_timeline->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()) { - panel_timeline->snap_to_timeline(&panel_timeline->cursor_frame, - - // 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::CurrentConfig.edit_tool_also_seeks || !panel_timeline->selecting, - - true, - true); - } - - if (panel_timeline->selecting) { - - // get number of selections based on tracks in selection area - int selection_tool_count = 1 + qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start) - qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); - - // 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 + panel_timeline->selection_offset; - - // resize selection object array to new count - if (olive::ActiveSequence->selections.size() != selection_count) { - olive::ActiveSequence->selections.resize(selection_count); - } - - // loop through tracks in selection area and adjust them accordingly - int minimum_selection_track = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); - int maximum_selection_track = qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start); - long selection_in = qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); - long selection_out = qMax(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); - for (int i=panel_timeline->selection_offset;iselections[i]; - s.track = minimum_selection_track + i - panel_timeline->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::CurrentConfig.edit_tool_selects_links) { - - // find which clips are selected - for (int j=0;jclips.size();j++) { - - Clip* c = olive::ActiveSequence->clips.at(j).get(); - - if (c != nullptr && c->IsSelected(false)) { - - // loop through linked clips - for (int k=0;klinked.size();k++) { - - ClipPtr link = olive::ActiveSequence->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 selection area, time to select it - Selection link_sel; - link_sel.in = selection_in; - link_sel.out = selection_out; - link_sel.track = link->track(); - olive::ActiveSequence->selections.append(link_sel); - - } - - } - - } - } - } - - // if the config is set to seek with the edit too, do so now - if (olive::CurrentConfig.edit_tool_also_seeks) { - panel_sequence_viewer->seek(qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame)); - } else { - // if not, repaint (seeking will trigger a repaint) - panel_timeline->repaint_timeline(); - } - - } else if (panel_timeline->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 - panel_timeline->block_repaints = true; - panel_timeline->horizontalScrollBar->setValue(panel_timeline->horizontalScrollBar->value() + panel_timeline->drag_x_start - event->pos().x()); - scrollBar->setValue(scrollBar->value() + panel_timeline->drag_y_start - event->pos().y()); - panel_timeline->block_repaints = false; - - // finally repaint - panel_timeline->repaint_timeline(); - - // store current cursor position for next hand move event - panel_timeline->drag_x_start = event->pos().x(); - panel_timeline->drag_y_start = event->pos().y(); - - } else if (panel_timeline->moving_init) { - - if (track_resizing) { - - // get cursor movement - int diff = (event->pos().y() - panel_timeline->drag_y_start); - - // add it to the current track height - int new_height = panel_timeline->GetTrackHeight(track_target); - if (bottom_align) { - new_height -= diff; - } else { - new_height += diff; - } - - // limit track height to track minimum height constant - new_height = qMax(new_height, olive::timeline::kTrackMinHeight); - - // set the track height - panel_timeline->SetTrackHeight(track_target, new_height); - - // store current cursor position for next track resize event - panel_timeline->drag_y_start = event->pos().y(); - - update(); - } else if (panel_timeline->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 - for (int i=0;iclips.size();i++) { - - Clip* c = olive::ActiveSequence->clips.at(i).get(); - - if (c != nullptr) { - Ghost g; - g.transition = nullptr; - - // check if whole clip is added - bool add = false; - - // check if a transition is selected (prioritize transition selection) - // (only the pointer tool supports moving transitions) - if (panel_timeline->tool == TIMELINE_TOOL_POINTER - && (c->opening_transition != nullptr || c->closing_transition != nullptr)) { - - // check if any selections contain a whole transition - for (int j=0;jselections.size();j++) { - - const Selection& s = olive::ActiveSequence->selections.at(j); - - if (s.track == c->track()) { - if (selection_contains_transition(s, c, kTransitionOpening)) { - - g.transition = c->opening_transition; - add = true; - break; - - } else if (selection_contains_transition(s, c, kTransitionClosing)) { - - g.transition = c->closing_transition; - add = true; - break; - - } - } - - } - - } - - // if a transition isn't selected, check if the whole clip is - if (!add) { - add = c->IsSelected(); - } - - if (add) { - - if (g.transition != nullptr) { - - // transition may be a dual transition, check if it's already been added elsewhere - for (int j=0;jghosts.size();j++) { - if (panel_timeline->ghosts.at(j).transition == g.transition) { - add = false; - break; - } - } - - } - - if (add) { - g.clip = i; - g.trim_type = panel_timeline->trim_type; - panel_timeline->ghosts.append(g); - } - - } - } - } - - if (panel_timeline->tool == 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 = panel_timeline->ghosts.size(); - - // loop through clips for any that are "touching" the selected clips - for (int j=0;jclips.size();j++) { - - ClipPtr c = olive::ActiveSequence->clips.at(j); - if (c != nullptr) { - - for (int i=0;ighosts[i]; - g.trim_type = TRIM_NONE; // the selected clips will be moving, not trimming - - ClipPtr ghost_clip = olive::ActiveSequence->clips.at(g.clip); - - if (c->track() == ghost_clip->track()) { - - // see if this clip is currently selected, if so we won't add it as a "touching" clip - bool found = false; - for (int k=0;kghosts.at(k).clip == j) { - found = true; - break; - } - } - - if (!found) { // the clip is not currently selected - - // check if this clip is indeed touching - bool is_in = (c->timeline_in() == ghost_clip->timeline_out()); - if (is_in || c->timeline_out() == ghost_clip->timeline_in()) { - Ghost gh; - gh.transition = nullptr; - gh.clip = j; - gh.trim_type = is_in ? TRIM_IN : TRIM_OUT; - panel_timeline->ghosts.append(gh); - } - } - } - } - } - } - } - - // set up ghost defaults - init_ghosts(); - - // if the ripple tool is selected, prepare to ripple - if (panel_timeline->tool == 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(panel_timeline->ghosts.size()); - - for (int i=0;ighosts.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(panel_timeline->ghosts.at(i).clip); - if (panel_timeline->trim_type == 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 - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr && !ghost_clips.contains(c)) { - bool clip_is_post = (c->timeline_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(olive::ActiveSequence.get()); - selection_command->old_data = olive::ActiveSequence->selections; - - // ready to start moving clips - panel_timeline->moving_proc = true; - } - - update_ui(false); - - } else if (panel_timeline->splitting) { - - // get the range of tracks currently dragged - int track_start = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); - int track_end = qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start); - int track_size = 1 + track_end - track_start; - - // set tracks to be split - panel_timeline->split_tracks.resize(track_size); - for (int i=0;isplit_tracks[i] = track_start + i; - } - - // if alt isn't being held, also add the tracks of the clip's links - if (!alt) { - for (int i=0;idrag_frame_start, panel_timeline->split_tracks[i]); - - if (clip_index > -1) { - ClipPtr clip = olive::ActiveSequence->clips.at(clip_index); - for (int j=0;jlinked.size();j++) { - - ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(j)); - - // if this clip isn't already in the list of tracks to split - if (link->track() < track_start || link->track() > track_end) { - panel_timeline->split_tracks.append(link->track()); - } - - } - } - } - } - - update_ui(false); - - } else if (panel_timeline->rect_select_init) { - - // set if the user started dragging at point where there was no clip - - if (panel_timeline->rect_select_proc) { - - // we're currently rectangle selecting - - // set the right/bottom coords to the current mouse position - // (left/top were set to the starting drag position earlier) - panel_timeline->rect_select_rect.setRight(event->pos().x()); - - if (bottom_align) { - panel_timeline->rect_select_rect.setBottom(event->pos().y() - height()); - } else { - panel_timeline->rect_select_rect.setBottom(event->pos().y()); - } - - long frame_min = qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); - long frame_max = qMax(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); - - int track_min = qMin(panel_timeline->drag_track_start, panel_timeline->cursor_track); - int track_max = qMax(panel_timeline->drag_track_start, panel_timeline->cursor_track); - - // determine which clips are in this rectangular selection - QVector selected_clips; - for (int i=0;iclips.size();i++) { - ClipPtr clip = olive::ActiveSequence->clips.at(i); - if (clip != nullptr && - clip->track() >= track_min && - clip->track() <= track_max && - !(clip->timeline_in() < frame_min && clip->timeline_out() < frame_min) && - !(clip->timeline_in() > frame_max && clip->timeline_out() > 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) { - for (int j=0;jlinked.size();j++) { - session_clips.append(olive::ActiveSequence->clips.at(clip->linked.at(j))); - } - } - - // 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;jselections.resize(selected_clips.size() + panel_timeline->selection_offset); - for (int i=0;iselections[i+panel_timeline->selection_offset]; - ClipPtr clip = selected_clips.at(i); - s.old_in = s.in = clip->timeline_in(); - s.old_out = s.out = clip->timeline_out(); - s.old_track = s.track = clip->track(); - } - - panel_timeline->repaint_timeline(); - } else { - - // set up rectangle selecting - panel_timeline->rect_select_rect.setX(event->pos().x()); - - if (bottom_align) { - // bottom aligned widgets start with 0 at the bottom and go down to a negative number - panel_timeline->rect_select_rect.setY(event->pos().y() - height()); - } else { - panel_timeline->rect_select_rect.setY(event->pos().y()); - } - - panel_timeline->rect_select_rect.setWidth(0); - panel_timeline->rect_select_rect.setHeight(0); - - panel_timeline->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 - panel_timeline->repaint_timeline(); - - } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || - panel_timeline->tool == TIMELINE_TOOL_RIPPLE || - panel_timeline->tool == 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 = 5; - 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 whether the cursor is within the rect of a clip - bool cursor_contains_clip = false; - - // used to determine how close the cursor is to a trim point - // (and more specifically, whether another point is closer or not) - int closeness = INT_MAX; - - // while we loop through the clips, we cache the maximum/minimum tracks in this sequence - int min_track = INT_MAX; - int max_track = INT_MIN; - - // we default to selecting no transition, but set this accordingly if the cursor is on a transition - panel_timeline->transition_select = kTransitionNone; - - // we also default to no trimming which may be changed later in this function - panel_timeline->trim_type = TRIM_NONE; - - // set currently trimming clip to -1 (aka null) - panel_timeline->trim_target = -1; - - // loop through current clips in the sequence - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr) { - - // cache track range - min_track = qMin(min_track, c->track()); - max_track = qMax(max_track, c->track()); - - // if this clip is on the same track the mouse is - if (c->track() == panel_timeline->cursor_track) { - - // if this cursor is inside the boundaries of this clip (hovering over the clip) - if (panel_timeline->cursor_frame >= c->timeline_in() && - panel_timeline->cursor_frame <= c->timeline_out()) { - - // acknowledge that we are hovering over a clip - cursor_contains_clip = true; - - // start a timer to show a tooltip about this clip - tooltip_timer.start(); - tooltip_clip = i; - - // check if the cursor is specifically hovering over one of the clip's transitions - if (c->opening_transition != nullptr - && panel_timeline->cursor_frame <= c->timeline_in() + c->opening_transition->get_true_length()) { - - panel_timeline->transition_select = kTransitionOpening; - - } else if (c->closing_transition != nullptr - && panel_timeline->cursor_frame >= c->timeline_out() - c->closing_transition->get_true_length()) { - - panel_timeline->transition_select = kTransitionClosing; - - } - } - - int visual_in_point = panel_timeline->getTimelineScreenPointFromFrame(c->timeline_in()); - int visual_out_point = panel_timeline->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) - panel_timeline->trim_target = i; - panel_timeline->trim_type = 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) - panel_timeline->trim_target = i; - panel_timeline->trim_type = 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 (panel_timeline->tool == 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 = panel_timeline->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) { - panel_timeline->trim_target = i; - panel_timeline->trim_type = TRIM_OUT; - panel_timeline->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 = panel_timeline->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) { - panel_timeline->trim_target = i; - panel_timeline->trim_type = TRIM_IN; - panel_timeline->transition_select = kTransitionClosing; - closeness = nc; - found = true; - } - } - } - } - } - } - } - - // if the cursor is indeed on a clip edge, we set the cursor accordingly - if (found) { - - if (panel_timeline->trim_type == TRIM_IN) { // if we're trimming an IN point - setCursor(panel_timeline->tool == TIMELINE_TOOL_RIPPLE ? olive::cursor::LeftRipple : olive::cursor::LeftTrim); - } else { // if we're trimming an OUT point - setCursor(panel_timeline->tool == 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 test_range = 5; - int mouse_pos = event->pos().y(); - int hover_track = getTrackFromScreenPoint(mouse_pos); - int track_y_edge = getScreenPointFromTrack(hover_track); - - if (!bottom_align) { - track_y_edge += panel_timeline->GetTrackHeight(hover_track); - } - - if (mouse_pos > track_y_edge - test_range - && mouse_pos < track_y_edge + test_range) { - if (cursor_contains_clip - || (olive::CurrentConfig.show_track_lines - && panel_timeline->cursor_track >= min_track - && panel_timeline->cursor_track <= max_track)) { - track_resizing = true; - track_target = hover_track; - setCursor(Qt::SizeVerCursor); - } - } - } - } else if (panel_timeline->tool == 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 (getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track) > -1) { - setCursor(olive::cursor::Slip); - } else { - unsetCursor(); - } - - } else if (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) { - - if (panel_timeline->transition_tool_init) { - - // the transition tool has started - - if (panel_timeline->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 - int primary_type = kTransitionOpening; - int primary = panel_timeline->transition_tool_open_clip; - if (primary == -1) { - primary_type = kTransitionClosing; - primary = panel_timeline->transition_tool_close_clip; - } - - ClipPtr c = olive::ActiveSequence->clips.at(primary); - - Ghost g; - - g.in = g.old_in = g.out = g.old_out = (primary_type == kTransitionOpening) ? - c->timeline_in() - : c->timeline_out(); - - g.track = c->track(); - g.clip = primary; - g.media_stream = primary_type; - g.trim_type = TRIM_NONE; - - panel_timeline->ghosts.append(g); - - panel_timeline->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 - - int mouse_clip = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); - - // set default transition tool references to no clip - panel_timeline->transition_tool_open_clip = -1; - panel_timeline->transition_tool_close_clip = -1; - - if (mouse_clip > -1) { - - // cursor is hovering over a clip - - ClipPtr c = olive::ActiveSequence->clips.at(mouse_clip); - - // check if the clip and transition are both the same sign (meaning video/audio are the same) - if (same_sign(c->track(), panel_timeline->transition_tool_side)) { - - // 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(panel_timeline->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 (panel_timeline->cursor_frame > (c->timeline_in() + (c->length()/2))) { - panel_timeline->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 (panel_timeline->cursor_frame > c->timeline_out() - between_range) { - panel_timeline->transition_tool_open_clip = getClipIndexFromCoords(c->timeline_out()+1, c->track()); - } - } else { - panel_timeline->transition_tool_open_clip = mouse_clip; - - if (panel_timeline->cursor_frame < c->timeline_in() + between_range) { - panel_timeline->transition_tool_close_clip = getClipIndexFromCoords(c->timeline_in()-1, c->track()); - } - } - - } - } - } - - panel_timeline->repaint_timeline(); - } - } -} - -void TimelineWidget::leaveEvent(QEvent*) { - tooltip_timer.stop(); -} - -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) { - // audio channels multiplied by the number of bytes in a 16-bit audio sample - int divider = ms->audio_channels*2; - - 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 mid = (olive::CurrentConfig.rectified_waveforms) ? clip_rect.top()+channel_height*(j+1) : clip_rect.top()+channel_height*j+(channel_height/2); - - 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); - - qint8 min = qint8(qRound(double(ms->audio_preview.at(offset_range_min)) / 128.0 * (channel_height/2))); - qint8 max = qint8(qRound(double(ms->audio_preview.at(offset_range_min+1)) / 128.0 * (channel_height/2))); - - 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 = qMin(min, qint8(qRound(double(ms->audio_preview.at(k)) / 128.0 * (channel_height/2)))); - max = qMax(max, qint8(qRound(double(ms->audio_preview.at(k+1)) / 128.0 * (channel_height/2)))); - } - - // draw waveforms - if (olive::CurrentConfig.rectified_waveforms) { - - // rectified waveforms start from the bottom and draw upwards - p->drawLine(clip_rect.left()+i, mid, clip_rect.left()+i, mid - (max - min)); - } else { - - // non-rectified waveforms start from the center and draw outwards - p->drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); - - } - } - } - last_waveform_index = waveform_index; - } -} - -void draw_transition(QPainter& p, ClipPtr 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(panel_timeline->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->meta->name, &transition_text_rect); - } - } - p.setPen(Qt::black); - p.drawRect(transition_rect); - } - -} - -void TimelineWidget::paintEvent(QPaintEvent*) { - // Draw clips - if (olive::ActiveSequence != nullptr) { - QPainter p(this); - - // get widget width and height - int video_track_limit = 0; - int audio_track_limit = 0; - for (int i=0;iclips.size();i++) { - ClipPtr clip = olive::ActiveSequence->clips.at(i); - if (clip != nullptr) { - video_track_limit = qMin(video_track_limit, clip->track()); - audio_track_limit = qMax(audio_track_limit, clip->track()); - } - } - - // start by adding a track height worth of padding - int panel_height = olive::timeline::kTrackDefaultHeight; - - // loop through tracks for maximum panel height - if (bottom_align) { - for (int i=-1;i>=video_track_limit;i--) { - panel_height += panel_timeline->GetTrackHeight(i); - } - } else { - for (int i=0;i<=audio_track_limit;i++) { - panel_height += panel_timeline->GetTrackHeight(i); - } - } - if (bottom_align) { - scrollBar->setMinimum(qMin(0, - panel_height + height())); - } else { - scrollBar->setMaximum(qMax(0, panel_height - height())); - } - - for (int i=0;iclips.size();i++) { - ClipPtr clip = olive::ActiveSequence->clips.at(i); - if (clip != nullptr && is_track_visible(clip->track())) { - QRect clip_rect(panel_timeline->getTimelineScreenPointFromFrame(clip->timeline_in()), getScreenPointFromTrack(clip->track()), getScreenPointFromFrame(panel_timeline->zoom, clip->length()), panel_timeline->GetTrackHeight(clip->track())); - 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->track() < 0) { - // 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(panel_timeline->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(panel_timeline->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(panel_timeline->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(panel_timeline->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); - } - - draw_waveform(clip, ms, media_length, &p, clip_rect, waveform_start, waveform_limit, panel_timeline->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 = panel_timeline->getTimelineScreenPointFromFrame(marker_time); - if (marker_x > clip_rect.x() && marker_x < clip_rect.right()) { - draw_marker(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 (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) { - - bool shared_transition = (panel_timeline->transition_tool_open_clip > -1 - && panel_timeline->transition_tool_close_clip > -1); - - QRect transition_tool_rect = clip_rect; - bool draw_transition_tool_rect = false; - - if (panel_timeline->transition_tool_open_clip == i) { - 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 (panel_timeline->transition_tool_close_clip == i) { - 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() && is_track_visible(panel_sequence_viewer->recording_track)) { - int rec_track_x = panel_timeline->getTimelineScreenPointFromFrame(panel_sequence_viewer->recording_start); - int rec_track_y = getScreenPointFromTrack(panel_sequence_viewer->recording_track); - int rec_track_height = panel_timeline->GetTrackHeight(panel_sequence_viewer->recording_track); - if (panel_sequence_viewer->recording_start != panel_sequence_viewer->recording_end) { - QRect rec_rect( - rec_track_x, - rec_track_y, - getScreenPointFromFrame(panel_timeline->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(panel_timeline->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 track lines - if (olive::CurrentConfig.show_track_lines) { - p.setPen(QColor(0, 0, 0, 96)); - audio_track_limit++; - if (video_track_limit == 0) video_track_limit--; - - if (bottom_align) { - // only draw lines for video tracks - for (int i=video_track_limit;i<0;i++) { - int line_y = getScreenPointFromTrack(i) - 1; - p.drawLine(0, line_y, rect().width(), line_y); - } - } else { - // only draw lines for audio tracks - for (int i=0;iGetTrackHeight(i); - p.drawLine(0, line_y, rect().width(), line_y); - } - } - } - - // Draw selections - for (int i=0;iselections.size();i++) { - const Selection& s = olive::ActiveSequence->selections.at(i); - if (is_track_visible(s.track)) { - int selection_y = getScreenPointFromTrack(s.track); - int selection_x = panel_timeline->getTimelineScreenPointFromFrame(s.in); - p.setPen(Qt::NoPen); - p.setBrush(Qt::NoBrush); - p.fillRect(selection_x, selection_y, panel_timeline->getTimelineScreenPointFromFrame(s.out) - selection_x, panel_timeline->GetTrackHeight(s.track), QColor(0, 0, 0, 64)); - } - } - - // draw rectangle select - if (panel_timeline->rect_select_proc) { - QRect rect_select = panel_timeline->rect_select_rect; - - if (bottom_align) { - rect_select.translate(0, height()); - } - - draw_selection_rectangle(p, rect_select); - } - - // Draw ghosts - if (!panel_timeline->ghosts.isEmpty()) { - QVector insert_points; - long first_ghost = LONG_MAX; - for (int i=0;ighosts.size();i++) { - const Ghost& g = panel_timeline->ghosts.at(i); - first_ghost = qMin(first_ghost, g.in); - if (is_track_visible(g.track)) { - int ghost_x = panel_timeline->getTimelineScreenPointFromFrame(g.in); - int ghost_y = getScreenPointFromTrack(g.track); - int ghost_width = panel_timeline->getTimelineScreenPointFromFrame(g.out) - ghost_x - 1; - int ghost_height = panel_timeline->GetTrackHeight(g.track) - 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 = panel_timeline->getTimelineScreenPointFromFrame(first_ghost); - int tri_size = olive::timeline::kTrackMinHeight>>2; - - for (int i=0;isplitting) { - for (int i=0;isplit_tracks.size();i++) { - if (is_track_visible(panel_timeline->split_tracks.at(i))) { - int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->drag_frame_start); - int cursor_y = getScreenPointFromTrack(panel_timeline->split_tracks.at(i)); - - p.setPen(QColor(64, 64, 64)); - p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->GetTrackHeight(panel_timeline->split_tracks.at(i))); - } - } - } - - // Draw playhead - p.setPen(Qt::red); - int playhead_x = panel_timeline->getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead); - p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); - - // Draw single frame highlight - int playhead_frame_width = panel_timeline->getTimelineScreenPointFromFrame(olive::ActiveSequence->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 = (bottom_align) ? rect().height()-1 : 0; - p.drawLine(0, edge_y, rect().width(), edge_y); - - // draw snap point - if (panel_timeline->snapped) { - p.setPen(Qt::white); - int snap_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->snap_point); - p.drawLine(snap_x, 0, snap_x, height()); - } - - // Draw edit cursor - if (current_tool_shows_cursor() && is_track_visible(panel_timeline->cursor_track)) { - int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->cursor_frame); - int cursor_y = getScreenPointFromTrack(panel_timeline->cursor_track); - - p.setPen(Qt::gray); - p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->GetTrackHeight(panel_timeline->cursor_track)); - } - } -} - -void TimelineWidget::resizeEvent(QResizeEvent *) { - scrollBar->setPageStep(height()); -} - -bool TimelineWidget::is_track_visible(int track) { - return (bottom_align == (track < 0)); -} - -// ************************************** -// screen point <-> frame/track functions -// ************************************** - -int TimelineWidget::getTrackFromScreenPoint(int y) { - int track_candidate = 0; - - y += scroll; - - if (bottom_align) { - y -= height(); - } - - if (y < 0) { - track_candidate--; - } - - int compounded_heights = 0; - - while (true) { - int track_height = panel_timeline->GetTrackHeight(track_candidate); - if (olive::CurrentConfig.show_track_lines) track_height++; - if (y < 0) { - track_height = -track_height; - } - - int next_compounded_height = compounded_heights + track_height; - - - if (y >= qMin(next_compounded_height, compounded_heights) && y < qMax(next_compounded_height, compounded_heights)) { - return track_candidate; - } - - compounded_heights = next_compounded_height; - - if (y < 0) { - track_candidate--; - } else { - track_candidate++; - } - } -} - -int TimelineWidget::getScreenPointFromTrack(int track) { - int point = 0; - - int start = (track < 0) ? -1 : 0; - int interval = (track < 0) ? -1 : 1; - - if (track < 0) track--; - - for (int i=start;i!=track;i+=interval) { - point += panel_timeline->GetTrackHeight(i); - if (olive::CurrentConfig.show_track_lines) point++; - } - - if (bottom_align) { - return height() - point - scroll; - } else { - return point - scroll; - } -} - -int TimelineWidget::getClipIndexFromCoords(long frame, int track) { - for (int i=0;iclips.size();i++) { - ClipPtr c = olive::ActiveSequence->clips.at(i); - if (c != nullptr && c->track() == track && frame >= c->timeline_in() && frame < c->timeline_out()) { - return i; - } - } - return -1; -} - -void TimelineWidget::setScroll(int s) { - scroll = s; - update(); -} - -void TimelineWidget::reveal_media() { - panel_project->reveal_media(rc_reveal_media); -} +/*** + + 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 "timelinewidget.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 "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 "effects/effect.h" +#include "effects/internal/solideffect.h" + +#define MAX_TEXT_WIDTH 20 +#define TRANSITION_BETWEEN_RANGE 40 + +TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent) { + selection_command = nullptr; + self_created_sequence = nullptr; + scroll = 0; + + bottom_align = false; + track_resizing = false; + setMouseTracking(true); + + 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 TimelineWidget::show_context_menu(const QPoint& pos) { + if (olive::ActiveSequence != nullptr) { + // hack because sometimes right clicking doesn't trigger mouse release event + panel_timeline->rect_select_init = false; + panel_timeline->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::UndoStack.canUndo()); + redoAction->setEnabled(olive::UndoStack.canRedo()); + menu.addSeparator(); + + // collect all the selected clips + QVector selected_clips = olive::ActiveSequence->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 + panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(pos.x()); + panel_timeline->cursor_track = getTrackFromScreenPoint(pos.y()); + + if (panel_timeline->can_ripple_empty_space(panel_timeline->cursor_frame, panel_timeline->cursor_track)) { + QAction* ripple_delete_action = menu.addAction(tr("R&ipple Delete Empty Space")); + connect(ripple_delete_action, SIGNAL(triggered(bool)), panel_timeline, 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;itrack() < 0) { + 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); + + // stabilizer option + /*int video_clip_count = 0; + bool all_video_is_footage = true; + for (int i=0;itrack() < 0) { + video_clip_count++; + if (selected_clips.at(i)->media() == nullptr + || selected_clips.at(i)->media()->get_type() != MEDIA_TYPE_FOOTAGE) { + all_video_is_footage = false; + } + } + } + if (video_clip_count == 1 && all_video_is_footage) { + QAction* stabilizerAction = menu.addAction("S&tabilizer"); + connect(stabilizerAction, SIGNAL(triggered(bool)), this, SLOT(show_stabilizer_diag())); + }*/ + + // 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 TimelineWidget::toggle_autoscale() { + QVector selected_clips = olive::ActiveSequence->SelectedClips(); + + if (!selected_clips.isEmpty()) { + SetClipProperty* action = new SetClipProperty(kSetClipPropertyAutoscale); + + for (int i=0;iAddSetting(c, !c->autoscaled()); + } + + olive::UndoStack.push(action); + } +} + +void TimelineWidget::tooltip_timer_timeout() { + if (olive::ActiveSequence != nullptr) { + if (tooltip_clip < olive::ActiveSequence->clips.size()) { + ClipPtr c = olive::ActiveSequence->clips.at(tooltip_clip); + if (c != nullptr) { + QToolTip::showText(QCursor::pos(), + tr("%1\nStart: %2\nEnd: %3\nDuration: %4").arg( + c->name(), + frame_to_timecode(c->timeline_in(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate), + frame_to_timecode(c->timeline_out(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate), + frame_to_timecode(c->length(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate) + )); + } + } + } + tooltip_timer.stop(); +} + +void TimelineWidget::open_sequence_properties() { + QList sequence_items; + QList all_top_level_items; + for (int i=0;iget_all_media_from_table(all_top_level_items, sequence_items, MEDIA_TYPE_SEQUENCE); // find all sequences in project + for (int i=0;ito_sequence() == olive::ActiveSequence) { + NewSequenceDialog nsd(this, sequence_items.at(i)); + nsd.exec(); + return; + } + } + QMessageBox::critical(this, tr("Error"), tr("Couldn't locate media wrapper for sequence.")); +} + +void TimelineWidget::show_clip_properties() +{ + // get list of selected clips + QVector selected_clips = olive::ActiveSequence->SelectedClips(); + + // if clips are selected, open the clip properties dialog + if (!selected_clips.isEmpty()) { + ClipPropertiesDialog cpd(this, selected_clips); + cpd.exec(); + } +} + +bool same_sign(int a, int b) { + return (a < 0) == (b < 0); +} + +void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { + bool import_init = false; + + QVector media_list; + panel_timeline->importing_files = false; + + if (panel_project->IsProjectWidget(event->source())) { + QModelIndexList items = panel_project->get_current_selected(); + media_list.resize(items.size()); + for (int i=0;iitem_to_media(items.at(i)); + } + import_init = true; + } + + if (event->source() == panel_footage_viewer) { + if (panel_footage_viewer->seq != olive::ActiveSequence) { // 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::CurrentConfig.enable_drag_files_to_timeline && event->mimeData()->hasUrls()) { + QList urls = event->mimeData()->urls(); + if (!urls.isEmpty()) { + QStringList file_list; + + for (int i=0;iprocess_file_list(file_list); + + for (int i=0;ilast_imported_media.size();i++) { + Footage* f = panel_project->last_imported_media.at(i)->to_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(panel_project->last_imported_media.at(i)); + } + } + + if (media_list.isEmpty()) { + olive::UndoStack.undo(); + } else { + import_init = true; + panel_timeline->importing_files = true; + } + } + } + + if (import_init) { + event->acceptProposedAction(); + + long entry_point; + Sequence* seq = olive::ActiveSequence.get(); + + 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 = create_sequence_from_media(media_list); + seq = self_created_sequence.get(); + } else { + entry_point = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); + panel_timeline->drag_frame_start = entry_point + getFrameFromScreenPoint(panel_timeline->zoom, 50); + panel_timeline->drag_track_start = (bottom_align) ? -1 : 0; + } + + panel_timeline->create_ghosts_from_media(seq, entry_point, media_list); + + panel_timeline->importing = true; + } +} + +void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) { + if (panel_timeline->importing) { + event->acceptProposedAction(); + + if (olive::ActiveSequence != nullptr) { + QPoint pos = event->pos(); + panel_timeline->scroll_to_frame(panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x())); + update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier); + panel_timeline->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing)); + update_ui(false); + } + } +} + +void TimelineWidget::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::CurrentConfig.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::CurrentConfig.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; + } + + panel_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 = panel_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 TimelineWidget::dragLeaveEvent(QDragLeaveEvent* event) { + event->accept(); + if (panel_timeline->importing) { + if (panel_timeline->importing_files) { + olive::UndoStack.undo(); + } + panel_timeline->importing_files = false; + panel_timeline->ghosts.clear(); + panel_timeline->importing = false; + update_ui(false); + } + if (self_created_sequence != nullptr) { + self_created_sequence.reset(); + self_created_sequence = nullptr; + } +} + +void delete_area_under_ghosts(ComboAction* ca) { + // delete areas before adding + QVector delete_areas; + for (int i=0;ighosts.size();i++) { + const Ghost& g = panel_timeline->ghosts.at(i); + Selection sel; + sel.in = g.in; + sel.out = g.out; + sel.track = g.track; + delete_areas.append(sel); + } + panel_timeline->delete_areas_and_relink(ca, delete_areas, false); +} + +void insert_clips(ComboAction* ca) { + 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 = panel_timeline->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 >= 0) { + ignore_clips.append(g.clip); + } else { + // don't try to close old gap if importing + ripple_old_point = false; + } + } + + panel_timeline->split_cache.clear(); + + for (int i=0;iclips.size();i++) { + ClipPtr c = olive::ActiveSequence->clips.at(i); + if (c != nullptr) { + // don't split any clips that are moving + bool found = false; + for (int j=0;jghosts.size();j++) { + if (panel_timeline->ghosts.at(j).clip == i) { + found = true; + break; + } + } + if (!found) { + if (c->timeline_in() < earliest_new_point && c->timeline_out() > earliest_new_point) { + panel_timeline->split_clip_and_relink(ca, i, 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(i)) { + ripple_old_point = false; + } + } + } + } + + long ripple_length = (latest_new_point - earliest_new_point); + + ripple_clips(ca, olive::ActiveSequence.get(), 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); + + ripple_clips(ca, olive::ActiveSequence.get(), latest_old_point, second_ripple_length, ignore_clips); + + if (earliest_old_point < earliest_new_point) { + for (int i=0;ighosts.size();i++) { + Ghost& g = panel_timeline->ghosts[i]; + g.in += second_ripple_length; + g.out += second_ripple_length; + } + for (int i=0;iselections.size();i++) { + Selection& s = olive::ActiveSequence->selections[i]; + s.in += second_ripple_length; + s.out += second_ripple_length; + } + } + } +} + +void TimelineWidget::dropEvent(QDropEvent* event) { + if (panel_timeline->importing && panel_timeline->ghosts.size() > 0) { + event->acceptProposedAction(); + + ComboAction* ca = new ComboAction(); + + Sequence* s = olive::ActiveSequence.get(); + + // if we're dropping into nothing, create a new sequences based on the clip being dragged + if (s == nullptr) { + s = self_created_sequence.get(); + panel_project->create_sequence_internal(ca, self_created_sequence, true, nullptr); + self_created_sequence = nullptr; + } else if (event->keyboardModifiers() & Qt::ControlModifier) { + insert_clips(ca); + } else { + delete_area_under_ghosts(ca); + } + + panel_timeline->add_clips_from_ghosts(ca, s); + + olive::UndoStack.push(ca); + + setFocus(); + + update_ui(true); + } +} + +void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *event) { + if (olive::ActiveSequence != nullptr) { + if (panel_timeline->tool == TIMELINE_TOOL_EDIT) { + int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); + if (clip_index >= 0) { + ClipPtr clip = olive::ActiveSequence->clips.at(clip_index); + if (!(event->modifiers() & Qt::ShiftModifier)) olive::ActiveSequence->selections.clear(); + Selection s; + s.in = clip->timeline_in(); + s.out = clip->timeline_out(); + s.track = clip->track(); + olive::ActiveSequence->selections.append(s); + update_ui(false); + } + } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER) { + int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); + if (clip_index >= 0) { + ClipPtr c = olive::ActiveSequence->clips.at(clip_index); + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { + olive::Global->set_sequence(c->media()->to_sequence()); + } + } + } + } +} + +bool current_tool_shows_cursor() { + return (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR || panel_timeline->creating); +} + +void TimelineWidget::mousePressEvent(QMouseEvent *event) { + if (olive::ActiveSequence != nullptr) { + + int effective_tool = panel_timeline->tool; + + // some user actions will override which tool we'll be using + if (event->button() == Qt::MiddleButton) { + effective_tool = TIMELINE_TOOL_HAND; + panel_timeline->creating = false; + } else if (event->button() == Qt::RightButton) { + effective_tool = TIMELINE_TOOL_MENU; + panel_timeline->creating = false; + } + + // ensure cursor_frame and cursor_track are up to date + mouseMoveEvent(event); + + // store current cursor positions + panel_timeline->drag_x_start = event->pos().x(); + panel_timeline->drag_y_start = event->pos().y(); + + // store current frame/tracks as the values to start dragging from + panel_timeline->drag_frame_start = panel_timeline->cursor_frame; + panel_timeline->drag_track_start = panel_timeline->cursor_track; + + // get the clip the user is currently hovering over, priority to trim_target set from mouseMoveEvent + int hovered_clip = panel_timeline->trim_target == -1 ? + getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track) + : panel_timeline->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) { + panel_timeline->selection_offset = olive::ActiveSequence->selections.size(); + } else { + panel_timeline->selection_offset = 0; + } + + // if the user is creating an object + if (panel_timeline->creating) { + int comp = 0; + switch (panel_timeline->creating_object) { + case ADD_OBJ_TITLE: + case ADD_OBJ_SOLID: + case ADD_OBJ_BARS: + comp = -1; + break; + case ADD_OBJ_TONE: + case ADD_OBJ_NOISE: + case ADD_OBJ_AUDIO: + comp = 1; + break; + } + + // if the track the user clicked is correct for the type of object we're adding + + if ((panel_timeline->drag_track_start < 0) == (comp < 0)) { + Ghost g; + g.in = g.old_in = g.out = g.old_out = panel_timeline->drag_frame_start; + g.track = g.old_track = panel_timeline->drag_track_start; + g.transition = nullptr; + g.clip = -1; + g.trim_type = TRIM_OUT; + panel_timeline->ghosts.append(g); + + panel_timeline->moving_init = true; + panel_timeline->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 TIMELINE_TOOL_POINTER: + case TIMELINE_TOOL_RIPPLE: + case TIMELINE_TOOL_SLIP: + case TIMELINE_TOOL_ROLLING: + case TIMELINE_TOOL_SLIDE: + case TIMELINE_TOOL_MENU: + { + if (track_resizing && effective_tool != TIMELINE_TOOL_MENU) { + + // if the cursor is currently hovering over a track, init track resizing + panel_timeline->moving_init = true; + + } else { + + // check if we're currently hovering over a clip or not + if (hovered_clip >= 0) { + Clip* clip = olive::ActiveSequence->clips.at(hovered_clip).get(); + + if (clip->IsSelected()) { + + if (shift) { + + // if the user clicks a selected clip while holding shift, deselect the clip + panel_timeline->deselect_area(clip->timeline_in(), clip->timeline_out(), clip->track()); + + // if the user isn't holding alt, also deselect all of its links as well + if (!alt) { + for (int i=0;ilinked.size();i++) { + ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(i)); + panel_timeline->deselect_area(link->timeline_in(), link->timeline_out(), link->track()); + } + } + + } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER + && panel_timeline->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 + + panel_timeline->deselect_area(clip->timeline_in(), clip->timeline_out(), clip->track()); + + for (int i=0;ilinked.size();i++) { + ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(i)); + panel_timeline->deselect_area(link->timeline_in(), link->timeline_out(), link->track()); + } + + Selection s; + s.track = clip->track(); + + // select the transition only + if (panel_timeline->transition_select == kTransitionOpening && clip->opening_transition != nullptr) { + s.in = clip->timeline_in(); + + if (clip->opening_transition->secondary_clip != nullptr) { + s.in -= clip->opening_transition->get_true_length(); + } + + s.out = clip->timeline_in() + clip->opening_transition->get_true_length(); + } else if (panel_timeline->transition_select == kTransitionClosing && clip->closing_transition != nullptr) { + s.in = clip->timeline_out() - clip->closing_transition->get_true_length(); + s.out = clip->timeline_out(); + + if (clip->closing_transition->secondary_clip != nullptr) { + s.out += clip->closing_transition->get_true_length(); + } + } + olive::ActiveSequence->selections.append(s); + } + } else { + + // if the clip is not already selected + + // if shift is NOT down, we change clear all current selections + if (!shift) { + olive::ActiveSequence->selections.clear(); + } + + Selection s; + + s.in = clip->timeline_in(); + s.out = clip->timeline_out(); + s.track = clip->track(); + + // 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 (panel_timeline->tool == TIMELINE_TOOL_POINTER) { + if (panel_timeline->transition_select == kTransitionOpening) { + // move the selection to only select the transitoin + s.out = clip->timeline_in() + clip->opening_transition->get_true_length(); + + // if the transition is a "shared" transition, adjust the selection to select both sides + if (clip->opening_transition->secondary_clip != nullptr) { + s.in -= clip->opening_transition->get_true_length(); + } + } else if (panel_timeline->transition_select == kTransitionClosing) { + // move the selection to only select the transitoin + s.in = clip->timeline_out() - clip->closing_transition->get_true_length(); + + // if the transition is a "shared" transition, adjust the selection to select both sides + if (clip->closing_transition->secondary_clip != nullptr) { + s.out += clip->closing_transition->get_true_length(); + } + } + } + + // add the selection to the array + olive::ActiveSequence->selections.append(s); + + // if the config is set to also seek with selections, do so now + if (olive::CurrentConfig.select_also_seeks) { + panel_sequence_viewer->seek(clip->timeline_in()); + } + + // if alt is not down, select links (provided we're not selecting transitions) + if (!alt && panel_timeline->transition_select == kTransitionNone) { + + for (int i=0;ilinked.size();i++) { + + Clip* link = olive::ActiveSequence->clips.at(clip->linked.at(i)).get(); + + // check if the clip is already selected + if (!link->IsSelected()) { + Selection ss; + ss.in = link->timeline_in(); + ss.out = link->timeline_out(); + ss.track = link->track(); + olive::ActiveSequence->selections.append(ss); + } + + } + + } + } + + // authorize the starting of a move action if the mouse moves after this + if (effective_tool != TIMELINE_TOOL_MENU) { + panel_timeline->moving_init = true; + } + + } else { + + // if the user did not click a clip at all, we start a rectangle selection + + if (!shift) { + olive::ActiveSequence->selections.clear(); + } + + panel_timeline->rect_select_init = true; + } + + // update everything + update_ui(false); + } + } + break; + case TIMELINE_TOOL_HAND: + + // initiate moving with the hand tool + panel_timeline->hand_moving = true; + + break; + case TIMELINE_TOOL_EDIT: + + // if the config is set to seek with the edit tool, do so now + if (olive::CurrentConfig.edit_tool_also_seeks) { + panel_sequence_viewer->seek(panel_timeline->drag_frame_start); + } + + // initiate selecting + panel_timeline->selecting = true; + + break; + case TIMELINE_TOOL_RAZOR: + { + + // initiate razor tool + panel_timeline->splitting = true; + + // add this track as a track being split by the razor + panel_timeline->split_tracks.append(panel_timeline->drag_track_start); + + update_ui(false); + } + break; + case TIMELINE_TOOL_TRANSITION: + { + + // if there is a clip to run the transition tool on, initiate the transition tool + if (panel_timeline->transition_tool_open_clip > -1 + || panel_timeline->transition_tool_close_clip > -1) { + panel_timeline->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 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); + + int track = 0; + + // 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; + Selection s; + s.in = transition_start; + s.out = transition_end; + s.track = track; + areas.append(s); + panel_timeline->delete_areas_and_relink(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()); + } + } + } +} + +void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { + QToolTip::hideText(); + if (olive::ActiveSequence != 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 (panel_timeline->creating) { + if (panel_timeline->ghosts.size() > 0) { + const Ghost& g = panel_timeline->ghosts.at(0); + + if (panel_timeline->creating_object == ADD_OBJ_AUDIO) { + olive::MainWindow->statusBar()->clearMessage(); + panel_sequence_viewer->cue_recording(qMin(g.in, g.out), qMax(g.in, g.out), g.track); + panel_timeline->creating = false; + } else if (g.in != g.out) { + ClipPtr c = std::make_shared(olive::ActiveSequence.get()); + 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); + c->set_track(g.track); + + if (ctrl) { + insert_clips(ca); + } else { + Selection s; + s.in = c->timeline_in(); + s.out = c->timeline_out(); + s.track = c->track(); + QVector areas; + areas.append(s); + panel_timeline->delete_areas_and_relink(ca, areas, false); + } + + QVector add; + add.append(c); + ca->append(new AddClipCommand(olive::ActiveSequence.get(), add)); + + if (c->track() < 0 && olive::CurrentConfig.add_default_effects_to_clips) { + // default video effects (before custom effects) + c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT))); + } + + switch (panel_timeline->creating_object) { + case ADD_OBJ_TITLE: + c->set_name(tr("Title")); + c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_RICHTEXT, EFFECT_TYPE_EFFECT))); + break; + case ADD_OBJ_SOLID: + c->set_name(tr("Solid Color")); + c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_SOLID, EFFECT_TYPE_EFFECT))); + break; + case ADD_OBJ_BARS: + { + c->set_name(tr("Bars")); + EffectPtr e = Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_SOLID, EFFECT_TYPE_EFFECT)); + + // Auto-select bars + SolidEffect* solid_effect = static_cast(e.get()); + solid_effect->SetType(SolidEffect::SOLID_TYPE_BARS); + + c->effects.append(e); + } + break; + case ADD_OBJ_TONE: + c->set_name(tr("Tone")); + c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TONE, EFFECT_TYPE_EFFECT))); + break; + case ADD_OBJ_NOISE: + c->set_name(tr("Noise")); + c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_NOISE, EFFECT_TYPE_EFFECT))); + break; + } + + if (c->track() >= 0 && olive::CurrentConfig.add_default_effects_to_clips) { + // default audio effects (after custom 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))); + } + + push_undo = true; + + if (!shift) { + panel_timeline->creating = false; + } + } + } + } else if (panel_timeline->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 = panel_timeline->ghosts.at(i); + if (g.in != g.old_in + || g.out != g.old_out + || g.clip_in != g.old_clip_in + || g.track != g.old_track) { + process_moving = true; + break; + } + } + + if (process_moving) { + const Ghost& first_ghost = panel_timeline->ghosts.at(0); + + // start a ripple movement + if (panel_timeline->tool == 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 (panel_timeline->trim_type == 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++) { + olive::ActiveSequence->selections[i].in += ripple_length; + olive::ActiveSequence->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 - panel_timeline->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 = panel_timeline->ghosts.at(i); + + // for the same reason that we pushed selections forward above, for in trimming, + // we push the ghosts forward here + if (panel_timeline->trim_type == TRIM_IN) { + ignore_clips.append(g.clip); + panel_timeline->ghosts[i].in += ripple_length; + panel_timeline->ghosts[i].out += ripple_length; + } + + // find the earliest ripple point + long comp_point = (panel_timeline->trim_type == 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 (panel_timeline->trim_type == TRIM_OUT) ripple_length = -ripple_length; + + // finally, ripple everything + ripple_clips(ca, olive::ActiveSequence.get(), ripple_point, ripple_length, ignore_clips); + } + + if (panel_timeline->tool == TIMELINE_TOOL_POINTER + && (event->modifiers() & Qt::AltModifier) + && panel_timeline->trim_target == -1) { + + // 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 = panel_timeline->ghosts.at(i); + if (g.old_in != g.in || g.old_out != g.out || g.track != g.old_track || g.clip_in != g.old_clip_in) { + + // create copy of clip + ClipPtr c = olive::ActiveSequence->clips.at(g.clip)->copy(olive::ActiveSequence.get()); + + c->set_timeline_in(g.in); + c->set_timeline_out(g.out); + c->set_track(g.track); + + Selection s; + s.in = g.in; + s.out = g.out; + s.track = g.track; + delete_areas.append(s); + + old_clips.append(g.clip); + new_clips.append(c); + + } + } + + if (new_clips.size() > 0) { + + // delete anything under the new clips + panel_timeline->delete_areas_and_relink(ca, delete_areas, false); + + // relink duplicated clips + panel_timeline->relink_clips_using_ids(old_clips, new_clips); + + // add them + ca->append(new AddClipCommand(olive::ActiveSequence.get(), 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 (panel_timeline->tool == TIMELINE_TOOL_POINTER && ctrl) { + + insert_clips(ca); + + } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == 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 = panel_timeline->ghosts.at(i); + + // set clip to undeletable so it's unaffected by delete_areas_and_relink() below + olive::ActiveSequence->clips.at(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 + Selection s; + s.in = g.in; + s.out = g.out; + s.track = g.track; + delete_areas.append(s); + } + + panel_timeline->delete_areas_and_relink(ca, delete_areas, false); + + // clean up, i.e. make everything not undeletable again + for (int i=0;ighosts.size();i++) { + const Ghost& g = panel_timeline->ghosts.at(i); + olive::ActiveSequence->clips.at(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 = panel_timeline->ghosts[i]; + + Clip* c = olive::ActiveSequence->clips.at(g.clip).get(); + + 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 - g.old_track), 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 == 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, 0, false, true); + g.transition->secondary_clip->move(ca, timeline_in_movement, movement, timeline_in_movement, 0, 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), 0, 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, 0, 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 = panel_timeline->ghosts.at(i); + + // only applies to moving clips, transitions are verified above instead + if (g.transition == nullptr) { + ClipPtr c = olive::ActiveSequence->clips.at(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)) { + + // 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 = panel_timeline->ghosts.at(j); + + if (olive::ActiveSequence->clips.at(other_clip_ghost.clip).get() == 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; + 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, + nullptr, + 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, + nullptr, + 0)); + + } + } + + } + } + } + } + } + } + } + push_undo = true; + } + } else if (panel_timeline->selecting || panel_timeline->rect_select_proc) { + } else if (panel_timeline->transition_tool_proc) { + const Ghost& g = panel_timeline->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 = (panel_timeline->transition_tool_open_clip > -1) + ? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip).get() + : nullptr; + + Clip* close = (panel_timeline->transition_tool_close_clip > -1) + ? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip).get() + : nullptr; + + + + // 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, + panel_timeline->transition_tool_meta, + transition_length)); + + push_undo = true; + } + } else if (panel_timeline->splitting) { + bool split = false; + for (int i=0;isplit_tracks.size();i++) { + int split_index = getClipIndexFromCoords(panel_timeline->drag_frame_start, panel_timeline->split_tracks.at(i)); + if (split_index > -1 && panel_timeline->split_clip_and_relink(ca, split_index, panel_timeline->drag_frame_start, !alt)) { + split = true; + } + } + if (split) { + push_undo = true; + } + panel_timeline->split_cache.clear(); + } + + // remove duplicate selections + panel_timeline->clean_up_selections(olive::ActiveSequence->selections); + + if (selection_command != nullptr) { + selection_command->new_data = olive::ActiveSequence->selections; + ca->append(selection_command); + selection_command = nullptr; + push_undo = true; + } + + if (push_undo) { + olive::UndoStack.push(ca); + } else { + delete ca; + } + + // destroy all ghosts + panel_timeline->ghosts.clear(); + + // clear split tracks + panel_timeline->split_tracks.clear(); + + panel_timeline->selecting = false; + panel_timeline->moving_proc = false; + panel_timeline->moving_init = false; + panel_timeline->splitting = false; + panel_timeline->snapped = false; + panel_timeline->rect_select_init = false; + panel_timeline->rect_select_proc = false; + panel_timeline->transition_tool_init = false; + panel_timeline->transition_tool_proc = false; + pre_clips.clear(); + post_clips.clear(); + + update_ui(true); + } + panel_timeline->hand_moving = false; + } +} + +void TimelineWidget::init_ghosts() { + for (int i=0;ighosts.size();i++) { + Ghost& g = panel_timeline->ghosts[i]; + ClipPtr c = olive::ActiveSequence->clips.at(g.clip); + + g.track = g.old_track = c->track(); + g.clip_in = g.old_clip_in = c->clip_in(); + + if (panel_timeline->tool == 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 = olive::ActiveSequence->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 TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { + int effective_tool = panel_timeline->tool; + if (panel_timeline->importing || panel_timeline->creating) effective_tool = TIMELINE_TOOL_POINTER; + + int mouse_track = getTrackFromScreenPoint(mouse_pos.y()); + long frame_diff = (lock_frame) ? 0 : panel_timeline->getTimelineFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start; + int track_diff = ((effective_tool == TIMELINE_TOOL_SLIDE || panel_timeline->transition_select != kTransitionNone) && !panel_timeline->importing) ? 0 : mouse_track - panel_timeline->drag_track_start; + long validator; + long earliest_in_point = LONG_MAX; + + // first try to snap + long fm; + + if (effective_tool != 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 = panel_timeline->ghosts.at(i); + + // snap ghost's in point + if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1) + || g.trim_type == TRIM_IN + || panel_timeline->transition_tool_open_clip > -1) { + fm = g.old_in + frame_diff; + if (panel_timeline->snap_to_timeline(&fm, true, true, true)) { + frame_diff = fm - g.old_in; + break; + } + } + + // snap ghost's out point + if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1) + || g.trim_type == TRIM_OUT + || panel_timeline->transition_tool_close_clip > -1) { + fm = g.old_out + frame_diff; + if (panel_timeline->snap_to_timeline(&fm, true, true, true)) { + frame_diff = fm - g.old_out; + break; + } + } + + // if the ghost is attached to a clip, snap its markers too + if (panel_timeline->trim_target == -1 && g.clip >= 0 && panel_timeline->tool != TIMELINE_TOOL_TRANSITION) { + ClipPtr c = olive::ActiveSequence->clips.at(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 (panel_timeline->snap_to_timeline(&fm, true, true, true)) { + frame_diff = fm - marker_real_time; + break; + } + } + } + } + } + + bool clips_are_movable = (effective_tool == TIMELINE_TOOL_POINTER || effective_tool == 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 = panel_timeline->ghosts.at(i); + Clip* c = nullptr; + if (g.clip != -1) { + c = olive::ActiveSequence->clips.at(g.clip).get(); + } + + const FootageStream* ms = nullptr; + if (g.clip != -1 && c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + ms = c->media_stream(); + } + + // validate ghosts for trimming + if (panel_timeline->creating) { + // i feel like we might need something here but we haven't so far? + } else if (effective_tool == 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 != TRIM_NONE) { + if (g.trim_type == 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 != 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 == 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 == TRIM_IN) { + frame_diff += g.transition->get_true_length(); + } else { + frame_diff -= g.transition->get_true_length(); + } + } + + // ripple ops + if (effective_tool == TIMELINE_TOOL_RIPPLE) { + for (int j=0;jtrim_type == 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 (panel_timeline->trim_type == 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 clips from crossing tracks + if (same_sign(g.old_track, panel_timeline->drag_track_start)) { + while (!same_sign(g.old_track, g.old_track + track_diff)) { + if (g.old_track < 0) { + track_diff--; + } else { + track_diff++; + } + } + } + } else if (effective_tool == TIMELINE_TOOL_TRANSITION) { + if (panel_timeline->transition_tool_open_clip == -1 + || panel_timeline->transition_tool_close_clip == -1) { + validate_transitions(c, g.media_stream, frame_diff); + } else { + // open transition clip + Clip* otc = olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip).get(); + + // close transition clip + Clip* ctc = olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip).get(); + + 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) { + panel_timeline->snapped = false; + } + + // apply changes to ghosts + for (int i=0;ighosts.size();i++) { + Ghost& g = panel_timeline->ghosts[i]; + + if (effective_tool == TIMELINE_TOOL_SLIP) { + g.clip_in = g.old_clip_in - frame_diff; + } else if (g.trim_type != TRIM_NONE) { + long ghost_diff = frame_diff; + + // prevent trimming clips from overlapping each other + for (int j=0;jghosts.size();j++) { + const Ghost& comp = panel_timeline->ghosts.at(j); + if (i != j && g.track == comp.track) { + long validator; + if (g.trim_type == 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 == 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 == 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.track = g.old_track; + g.in = g.old_in + frame_diff; + g.out = g.old_out + frame_diff; + + if (g.transition != nullptr + && g.transition == olive::ActiveSequence->clips.at(g.clip)->opening_transition) { + g.clip_in = g.old_clip_in + frame_diff; + } + + if (panel_timeline->importing) { + if ((panel_timeline->video_ghosts && mouse_track < 0) + || (panel_timeline->audio_ghosts && mouse_track >= 0)) { + int abs_track_diff = abs(track_diff); + if (g.old_track < 0) { // clip is video + g.track -= abs_track_diff; + } else { // clip is audio + g.track += abs_track_diff; + } + } + } else if (same_sign(g.old_track, panel_timeline->drag_track_start)) { + g.track += track_diff; + } + } else if (effective_tool == TIMELINE_TOOL_TRANSITION) { + if (panel_timeline->transition_tool_open_clip > -1 + && panel_timeline->transition_tool_close_clip > -1) { + g.in = g.old_in - frame_diff; + g.out = g.old_out + frame_diff; + } else if (panel_timeline->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 != TIMELINE_TOOL_SLIP && !panel_timeline->importing && !panel_timeline->creating) { + for (int i=0;iselections.size();i++) { + Selection& s = olive::ActiveSequence->selections[i]; + if (panel_timeline->trim_target > -1) { + if (panel_timeline->trim_type == 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 = olive::ActiveSequence->selections[i]; + s.in = s.old_in + frame_diff; + s.out = s.old_out + frame_diff; + s.track = s.old_track; + + if (panel_timeline->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, panel_timeline->drag_track_start)) s.track += track_diff; + } + } + } + } + } + + if (panel_timeline->importing) { + QToolTip::showText(mapToGlobal(mouse_pos), frame_to_timecode(earliest_in_point, olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate)); + } else { + QString tip = ((frame_diff < 0) ? "-" : "+") + frame_to_timecode(qAbs(frame_diff), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate); + if (panel_timeline->trim_target > -1) { + // find which clip is being moved + const Ghost* g = nullptr; + for (int i=0;ighosts.size();i++) { + if (panel_timeline->ghosts.at(i).clip == panel_timeline->trim_target) { + g = &panel_timeline->ghosts.at(i); + break; + } + } + + if (g != nullptr) { + tip += " " + tr("Duration:") + " "; + long len = (g->old_out-g->old_in); + if (panel_timeline->trim_type == TRIM_IN) { + len -= frame_diff; + } else { + len += frame_diff; + } + tip += frame_to_timecode(len, olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate); + } + } + QToolTip::showText(mapToGlobal(mouse_pos), tip); + } +} + +void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { + // interrupt any potential tooltip about to show + tooltip_timer.stop(); + + if (olive::ActiveSequence != nullptr) { + bool alt = (event->modifiers() & Qt::AltModifier); + + // store current frame/track corresponding to the cursor + panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); + panel_timeline->cursor_track = getTrackFromScreenPoint(event->pos().y()); + + // if holding the mouse button down, let's scroll to that location + if (event->buttons() != 0 && panel_timeline->tool != TIMELINE_TOOL_HAND) { + panel_timeline->scroll_to_frame(panel_timeline->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 + panel_timeline->move_insert = ((event->modifiers() & Qt::ControlModifier) + && (panel_timeline->tool == TIMELINE_TOOL_POINTER + || panel_timeline->importing + || panel_timeline->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 (!panel_timeline->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()) { + panel_timeline->snap_to_timeline(&panel_timeline->cursor_frame, + + // 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::CurrentConfig.edit_tool_also_seeks || !panel_timeline->selecting, + + true, + true); + } + + if (panel_timeline->selecting) { + + // get number of selections based on tracks in selection area + int selection_tool_count = 1 + qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start) - qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); + + // 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 + panel_timeline->selection_offset; + + // resize selection object array to new count + if (olive::ActiveSequence->selections.size() != selection_count) { + olive::ActiveSequence->selections.resize(selection_count); + } + + // loop through tracks in selection area and adjust them accordingly + int minimum_selection_track = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); + int maximum_selection_track = qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start); + long selection_in = qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); + long selection_out = qMax(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); + for (int i=panel_timeline->selection_offset;iselections[i]; + s.track = minimum_selection_track + i - panel_timeline->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::CurrentConfig.edit_tool_selects_links) { + + // find which clips are selected + for (int j=0;jclips.size();j++) { + + Clip* c = olive::ActiveSequence->clips.at(j).get(); + + if (c != nullptr && c->IsSelected(false)) { + + // loop through linked clips + for (int k=0;klinked.size();k++) { + + ClipPtr link = olive::ActiveSequence->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 selection area, time to select it + Selection link_sel; + link_sel.in = selection_in; + link_sel.out = selection_out; + link_sel.track = link->track(); + olive::ActiveSequence->selections.append(link_sel); + + } + + } + + } + } + } + + // if the config is set to seek with the edit too, do so now + if (olive::CurrentConfig.edit_tool_also_seeks) { + panel_sequence_viewer->seek(qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame)); + } else { + // if not, repaint (seeking will trigger a repaint) + panel_timeline->repaint_timeline(); + } + + } else if (panel_timeline->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 + panel_timeline->block_repaints = true; + panel_timeline->horizontalScrollBar->setValue(panel_timeline->horizontalScrollBar->value() + panel_timeline->drag_x_start - event->pos().x()); + scrollBar->setValue(scrollBar->value() + panel_timeline->drag_y_start - event->pos().y()); + panel_timeline->block_repaints = false; + + // finally repaint + panel_timeline->repaint_timeline(); + + // store current cursor position for next hand move event + panel_timeline->drag_x_start = event->pos().x(); + panel_timeline->drag_y_start = event->pos().y(); + + } else if (panel_timeline->moving_init) { + + if (track_resizing) { + + // get cursor movement + int diff = (event->pos().y() - panel_timeline->drag_y_start); + + // add it to the current track height + int new_height = panel_timeline->GetTrackHeight(track_target); + if (bottom_align) { + new_height -= diff; + } else { + new_height += diff; + } + + // limit track height to track minimum height constant + new_height = qMax(new_height, olive::timeline::kTrackMinHeight); + + // set the track height + panel_timeline->SetTrackHeight(track_target, new_height); + + // store current cursor position for next track resize event + panel_timeline->drag_y_start = event->pos().y(); + + update(); + } else if (panel_timeline->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 + for (int i=0;iclips.size();i++) { + + Clip* c = olive::ActiveSequence->clips.at(i).get(); + + if (c != nullptr) { + Ghost g; + g.transition = nullptr; + + // check if whole clip is added + bool add = false; + + // check if a transition is selected (prioritize transition selection) + // (only the pointer tool supports moving transitions) + if (panel_timeline->tool == TIMELINE_TOOL_POINTER + && (c->opening_transition != nullptr || c->closing_transition != nullptr)) { + + // check if any selections contain a whole transition + for (int j=0;jselections.size();j++) { + + const Selection& s = olive::ActiveSequence->selections.at(j); + + if (s.track == c->track()) { + if (selection_contains_transition(s, c, kTransitionOpening)) { + + g.transition = c->opening_transition; + add = true; + break; + + } else if (selection_contains_transition(s, c, kTransitionClosing)) { + + g.transition = c->closing_transition; + add = true; + break; + + } + } + + } + + } + + // if a transition isn't selected, check if the whole clip is + if (!add) { + add = c->IsSelected(); + } + + if (add) { + + if (g.transition != nullptr) { + + // transition may be a dual transition, check if it's already been added elsewhere + for (int j=0;jghosts.size();j++) { + if (panel_timeline->ghosts.at(j).transition == g.transition) { + add = false; + break; + } + } + + } + + if (add) { + g.clip = i; + g.trim_type = panel_timeline->trim_type; + panel_timeline->ghosts.append(g); + } + + } + } + } + + if (panel_timeline->tool == 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 = panel_timeline->ghosts.size(); + + // loop through clips for any that are "touching" the selected clips + for (int j=0;jclips.size();j++) { + + ClipPtr c = olive::ActiveSequence->clips.at(j); + if (c != nullptr) { + + for (int i=0;ighosts[i]; + g.trim_type = TRIM_NONE; // the selected clips will be moving, not trimming + + ClipPtr ghost_clip = olive::ActiveSequence->clips.at(g.clip); + + if (c->track() == ghost_clip->track()) { + + // see if this clip is currently selected, if so we won't add it as a "touching" clip + bool found = false; + for (int k=0;kghosts.at(k).clip == j) { + found = true; + break; + } + } + + if (!found) { // the clip is not currently selected + + // check if this clip is indeed touching + bool is_in = (c->timeline_in() == ghost_clip->timeline_out()); + if (is_in || c->timeline_out() == ghost_clip->timeline_in()) { + Ghost gh; + gh.transition = nullptr; + gh.clip = j; + gh.trim_type = is_in ? TRIM_IN : TRIM_OUT; + panel_timeline->ghosts.append(gh); + } + } + } + } + } + } + } + + // set up ghost defaults + init_ghosts(); + + // if the ripple tool is selected, prepare to ripple + if (panel_timeline->tool == 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(panel_timeline->ghosts.size()); + + for (int i=0;ighosts.size();i++) { + ClipPtr c = olive::ActiveSequence->clips.at(panel_timeline->ghosts.at(i).clip); + if (panel_timeline->trim_type == 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 + for (int i=0;iclips.size();i++) { + ClipPtr c = olive::ActiveSequence->clips.at(i); + if (c != nullptr && !ghost_clips.contains(c)) { + bool clip_is_post = (c->timeline_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(olive::ActiveSequence.get()); + selection_command->old_data = olive::ActiveSequence->selections; + + // ready to start moving clips + panel_timeline->moving_proc = true; + } + + update_ui(false); + + } else if (panel_timeline->splitting) { + + // get the range of tracks currently dragged + int track_start = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); + int track_end = qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start); + int track_size = 1 + track_end - track_start; + + // set tracks to be split + panel_timeline->split_tracks.resize(track_size); + for (int i=0;isplit_tracks[i] = track_start + i; + } + + // if alt isn't being held, also add the tracks of the clip's links + if (!alt) { + for (int i=0;idrag_frame_start, panel_timeline->split_tracks[i]); + + if (clip_index > -1) { + ClipPtr clip = olive::ActiveSequence->clips.at(clip_index); + for (int j=0;jlinked.size();j++) { + + ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(j)); + + // if this clip isn't already in the list of tracks to split + if (link->track() < track_start || link->track() > track_end) { + panel_timeline->split_tracks.append(link->track()); + } + + } + } + } + } + + update_ui(false); + + } else if (panel_timeline->rect_select_init) { + + // set if the user started dragging at point where there was no clip + + if (panel_timeline->rect_select_proc) { + + // we're currently rectangle selecting + + // set the right/bottom coords to the current mouse position + // (left/top were set to the starting drag position earlier) + panel_timeline->rect_select_rect.setRight(event->pos().x()); + + if (bottom_align) { + panel_timeline->rect_select_rect.setBottom(event->pos().y() - height()); + } else { + panel_timeline->rect_select_rect.setBottom(event->pos().y()); + } + + long frame_min = qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); + long frame_max = qMax(panel_timeline->drag_frame_start, panel_timeline->cursor_frame); + + int track_min = qMin(panel_timeline->drag_track_start, panel_timeline->cursor_track); + int track_max = qMax(panel_timeline->drag_track_start, panel_timeline->cursor_track); + + // determine which clips are in this rectangular selection + QVector selected_clips; + for (int i=0;iclips.size();i++) { + ClipPtr clip = olive::ActiveSequence->clips.at(i); + if (clip != nullptr && + clip->track() >= track_min && + clip->track() <= track_max && + !(clip->timeline_in() < frame_min && clip->timeline_out() < frame_min) && + !(clip->timeline_in() > frame_max && clip->timeline_out() > 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) { + for (int j=0;jlinked.size();j++) { + session_clips.append(olive::ActiveSequence->clips.at(clip->linked.at(j))); + } + } + + // 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;jselections.resize(selected_clips.size() + panel_timeline->selection_offset); + for (int i=0;iselections[i+panel_timeline->selection_offset]; + ClipPtr clip = selected_clips.at(i); + s.old_in = s.in = clip->timeline_in(); + s.old_out = s.out = clip->timeline_out(); + s.old_track = s.track = clip->track(); + } + + panel_timeline->repaint_timeline(); + } else { + + // set up rectangle selecting + panel_timeline->rect_select_rect.setX(event->pos().x()); + + if (bottom_align) { + // bottom aligned widgets start with 0 at the bottom and go down to a negative number + panel_timeline->rect_select_rect.setY(event->pos().y() - height()); + } else { + panel_timeline->rect_select_rect.setY(event->pos().y()); + } + + panel_timeline->rect_select_rect.setWidth(0); + panel_timeline->rect_select_rect.setHeight(0); + + panel_timeline->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 + panel_timeline->repaint_timeline(); + + } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || + panel_timeline->tool == TIMELINE_TOOL_RIPPLE || + panel_timeline->tool == 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 = 5; + 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 whether the cursor is within the rect of a clip + bool cursor_contains_clip = false; + + // used to determine how close the cursor is to a trim point + // (and more specifically, whether another point is closer or not) + int closeness = INT_MAX; + + // while we loop through the clips, we cache the maximum/minimum tracks in this sequence + int min_track = INT_MAX; + int max_track = INT_MIN; + + // we default to selecting no transition, but set this accordingly if the cursor is on a transition + panel_timeline->transition_select = kTransitionNone; + + // we also default to no trimming which may be changed later in this function + panel_timeline->trim_type = TRIM_NONE; + + // set currently trimming clip to -1 (aka null) + panel_timeline->trim_target = -1; + + // loop through current clips in the sequence + for (int i=0;iclips.size();i++) { + ClipPtr c = olive::ActiveSequence->clips.at(i); + if (c != nullptr) { + + // cache track range + min_track = qMin(min_track, c->track()); + max_track = qMax(max_track, c->track()); + + // if this clip is on the same track the mouse is + if (c->track() == panel_timeline->cursor_track) { + + // if this cursor is inside the boundaries of this clip (hovering over the clip) + if (panel_timeline->cursor_frame >= c->timeline_in() && + panel_timeline->cursor_frame <= c->timeline_out()) { + + // acknowledge that we are hovering over a clip + cursor_contains_clip = true; + + // start a timer to show a tooltip about this clip + tooltip_timer.start(); + tooltip_clip = i; + + // check if the cursor is specifically hovering over one of the clip's transitions + if (c->opening_transition != nullptr + && panel_timeline->cursor_frame <= c->timeline_in() + c->opening_transition->get_true_length()) { + + panel_timeline->transition_select = kTransitionOpening; + + } else if (c->closing_transition != nullptr + && panel_timeline->cursor_frame >= c->timeline_out() - c->closing_transition->get_true_length()) { + + panel_timeline->transition_select = kTransitionClosing; + + } + } + + int visual_in_point = panel_timeline->getTimelineScreenPointFromFrame(c->timeline_in()); + int visual_out_point = panel_timeline->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) + panel_timeline->trim_target = i; + panel_timeline->trim_type = 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) + panel_timeline->trim_target = i; + panel_timeline->trim_type = 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 (panel_timeline->tool == 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 = panel_timeline->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) { + panel_timeline->trim_target = i; + panel_timeline->trim_type = TRIM_OUT; + panel_timeline->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 = panel_timeline->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) { + panel_timeline->trim_target = i; + panel_timeline->trim_type = TRIM_IN; + panel_timeline->transition_select = kTransitionClosing; + closeness = nc; + found = true; + } + } + } + } + } + } + } + + // if the cursor is indeed on a clip edge, we set the cursor accordingly + if (found) { + + if (panel_timeline->trim_type == TRIM_IN) { // if we're trimming an IN point + setCursor(panel_timeline->tool == TIMELINE_TOOL_RIPPLE ? olive::cursor::LeftRipple : olive::cursor::LeftTrim); + } else { // if we're trimming an OUT point + setCursor(panel_timeline->tool == 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 test_range = 5; + int mouse_pos = event->pos().y(); + int hover_track = getTrackFromScreenPoint(mouse_pos); + int track_y_edge = getScreenPointFromTrack(hover_track); + + if (!bottom_align) { + track_y_edge += panel_timeline->GetTrackHeight(hover_track); + } + + if (mouse_pos > track_y_edge - test_range + && mouse_pos < track_y_edge + test_range) { + if (cursor_contains_clip + || (olive::CurrentConfig.show_track_lines + && panel_timeline->cursor_track >= min_track + && panel_timeline->cursor_track <= max_track)) { + track_resizing = true; + track_target = hover_track; + setCursor(Qt::SizeVerCursor); + } + } + } + } else if (panel_timeline->tool == 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 (getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track) > -1) { + setCursor(olive::cursor::Slip); + } else { + unsetCursor(); + } + + } else if (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) { + + if (panel_timeline->transition_tool_init) { + + // the transition tool has started + + if (panel_timeline->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 + int primary_type = kTransitionOpening; + int primary = panel_timeline->transition_tool_open_clip; + if (primary == -1) { + primary_type = kTransitionClosing; + primary = panel_timeline->transition_tool_close_clip; + } + + ClipPtr c = olive::ActiveSequence->clips.at(primary); + + Ghost g; + + g.in = g.old_in = g.out = g.old_out = (primary_type == kTransitionOpening) ? + c->timeline_in() + : c->timeline_out(); + + g.track = c->track(); + g.clip = primary; + g.media_stream = primary_type; + g.trim_type = TRIM_NONE; + + panel_timeline->ghosts.append(g); + + panel_timeline->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 + + int mouse_clip = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); + + // set default transition tool references to no clip + panel_timeline->transition_tool_open_clip = -1; + panel_timeline->transition_tool_close_clip = -1; + + if (mouse_clip > -1) { + + // cursor is hovering over a clip + + ClipPtr c = olive::ActiveSequence->clips.at(mouse_clip); + + // check if the clip and transition are both the same sign (meaning video/audio are the same) + if (same_sign(c->track(), panel_timeline->transition_tool_side)) { + + // 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(panel_timeline->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 (panel_timeline->cursor_frame > (c->timeline_in() + (c->length()/2))) { + panel_timeline->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 (panel_timeline->cursor_frame > c->timeline_out() - between_range) { + panel_timeline->transition_tool_open_clip = getClipIndexFromCoords(c->timeline_out()+1, c->track()); + } + } else { + panel_timeline->transition_tool_open_clip = mouse_clip; + + if (panel_timeline->cursor_frame < c->timeline_in() + between_range) { + panel_timeline->transition_tool_close_clip = getClipIndexFromCoords(c->timeline_in()-1, c->track()); + } + } + + } + } + } + + panel_timeline->repaint_timeline(); + } + } +} + +void TimelineWidget::leaveEvent(QEvent*) { + tooltip_timer.stop(); +} + +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) { + // audio channels multiplied by the number of bytes in a 16-bit audio sample + int divider = ms->audio_channels*2; + + 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 mid = (olive::CurrentConfig.rectified_waveforms) ? clip_rect.top()+channel_height*(j+1) : clip_rect.top()+channel_height*j+(channel_height/2); + + 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); + + qint8 min = qint8(qRound(double(ms->audio_preview.at(offset_range_min)) / 128.0 * (channel_height/2))); + qint8 max = qint8(qRound(double(ms->audio_preview.at(offset_range_min+1)) / 128.0 * (channel_height/2))); + + 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 = qMin(min, qint8(qRound(double(ms->audio_preview.at(k)) / 128.0 * (channel_height/2)))); + max = qMax(max, qint8(qRound(double(ms->audio_preview.at(k+1)) / 128.0 * (channel_height/2)))); + } + + // draw waveforms + if (olive::CurrentConfig.rectified_waveforms) { + + // rectified waveforms start from the bottom and draw upwards + p->drawLine(clip_rect.left()+i, mid, clip_rect.left()+i, mid - (max - min)); + } else { + + // non-rectified waveforms start from the center and draw outwards + p->drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); + + } + } + } + last_waveform_index = waveform_index; + } +} + +void draw_transition(QPainter& p, ClipPtr 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(panel_timeline->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->meta->name, &transition_text_rect); + } + } + p.setPen(Qt::black); + p.drawRect(transition_rect); + } + +} + +void TimelineWidget::paintEvent(QPaintEvent*) { + // Draw clips + if (olive::ActiveSequence != nullptr) { + QPainter p(this); + + // get widget width and height + int video_track_limit = 0; + int audio_track_limit = 0; + for (int i=0;iclips.size();i++) { + ClipPtr clip = olive::ActiveSequence->clips.at(i); + if (clip != nullptr) { + video_track_limit = qMin(video_track_limit, clip->track()); + audio_track_limit = qMax(audio_track_limit, clip->track()); + } + } + + // start by adding a track height worth of padding + int panel_height = olive::timeline::kTrackDefaultHeight; + + // loop through tracks for maximum panel height + if (bottom_align) { + for (int i=-1;i>=video_track_limit;i--) { + panel_height += panel_timeline->GetTrackHeight(i); + } + } else { + for (int i=0;i<=audio_track_limit;i++) { + panel_height += panel_timeline->GetTrackHeight(i); + } + } + if (bottom_align) { + scrollBar->setMinimum(qMin(0, - panel_height + height())); + } else { + scrollBar->setMaximum(qMax(0, panel_height - height())); + } + + for (int i=0;iclips.size();i++) { + ClipPtr clip = olive::ActiveSequence->clips.at(i); + if (clip != nullptr && is_track_visible(clip->track())) { + QRect clip_rect(panel_timeline->getTimelineScreenPointFromFrame(clip->timeline_in()), getScreenPointFromTrack(clip->track()), getScreenPointFromFrame(panel_timeline->zoom, clip->length()), panel_timeline->GetTrackHeight(clip->track())); + 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->track() < 0) { + // 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(panel_timeline->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(panel_timeline->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(panel_timeline->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(panel_timeline->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); + } + + draw_waveform(clip, ms, media_length, &p, clip_rect, waveform_start, waveform_limit, panel_timeline->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 = panel_timeline->getTimelineScreenPointFromFrame(marker_time); + if (marker_x > clip_rect.x() && marker_x < clip_rect.right()) { + draw_marker(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 (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) { + + bool shared_transition = (panel_timeline->transition_tool_open_clip > -1 + && panel_timeline->transition_tool_close_clip > -1); + + QRect transition_tool_rect = clip_rect; + bool draw_transition_tool_rect = false; + + if (panel_timeline->transition_tool_open_clip == i) { + 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 (panel_timeline->transition_tool_close_clip == i) { + 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() && is_track_visible(panel_sequence_viewer->recording_track)) { + int rec_track_x = panel_timeline->getTimelineScreenPointFromFrame(panel_sequence_viewer->recording_start); + int rec_track_y = getScreenPointFromTrack(panel_sequence_viewer->recording_track); + int rec_track_height = panel_timeline->GetTrackHeight(panel_sequence_viewer->recording_track); + if (panel_sequence_viewer->recording_start != panel_sequence_viewer->recording_end) { + QRect rec_rect( + rec_track_x, + rec_track_y, + getScreenPointFromFrame(panel_timeline->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(panel_timeline->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 track lines + if (olive::CurrentConfig.show_track_lines) { + p.setPen(QColor(0, 0, 0, 96)); + audio_track_limit++; + if (video_track_limit == 0) video_track_limit--; + + if (bottom_align) { + // only draw lines for video tracks + for (int i=video_track_limit;i<0;i++) { + int line_y = getScreenPointFromTrack(i) - 1; + p.drawLine(0, line_y, rect().width(), line_y); + } + } else { + // only draw lines for audio tracks + for (int i=0;iGetTrackHeight(i); + p.drawLine(0, line_y, rect().width(), line_y); + } + } + } + + // Draw selections + for (int i=0;iselections.size();i++) { + const Selection& s = olive::ActiveSequence->selections.at(i); + if (is_track_visible(s.track)) { + int selection_y = getScreenPointFromTrack(s.track); + int selection_x = panel_timeline->getTimelineScreenPointFromFrame(s.in); + p.setPen(Qt::NoPen); + p.setBrush(Qt::NoBrush); + p.fillRect(selection_x, selection_y, panel_timeline->getTimelineScreenPointFromFrame(s.out) - selection_x, panel_timeline->GetTrackHeight(s.track), QColor(0, 0, 0, 64)); + } + } + + // draw rectangle select + if (panel_timeline->rect_select_proc) { + QRect rect_select = panel_timeline->rect_select_rect; + + if (bottom_align) { + rect_select.translate(0, height()); + } + + draw_selection_rectangle(p, rect_select); + } + + // Draw ghosts + if (!panel_timeline->ghosts.isEmpty()) { + QVector insert_points; + long first_ghost = LONG_MAX; + for (int i=0;ighosts.size();i++) { + const Ghost& g = panel_timeline->ghosts.at(i); + first_ghost = qMin(first_ghost, g.in); + if (is_track_visible(g.track)) { + int ghost_x = panel_timeline->getTimelineScreenPointFromFrame(g.in); + int ghost_y = getScreenPointFromTrack(g.track); + int ghost_width = panel_timeline->getTimelineScreenPointFromFrame(g.out) - ghost_x - 1; + int ghost_height = panel_timeline->GetTrackHeight(g.track) - 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 = panel_timeline->getTimelineScreenPointFromFrame(first_ghost); + int tri_size = olive::timeline::kTrackMinHeight>>2; + + for (int i=0;isplitting) { + for (int i=0;isplit_tracks.size();i++) { + if (is_track_visible(panel_timeline->split_tracks.at(i))) { + int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->drag_frame_start); + int cursor_y = getScreenPointFromTrack(panel_timeline->split_tracks.at(i)); + + p.setPen(QColor(64, 64, 64)); + p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->GetTrackHeight(panel_timeline->split_tracks.at(i))); + } + } + } + + // Draw playhead + p.setPen(Qt::red); + int playhead_x = panel_timeline->getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead); + p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); + + // Draw single frame highlight + int playhead_frame_width = panel_timeline->getTimelineScreenPointFromFrame(olive::ActiveSequence->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 = (bottom_align) ? rect().height()-1 : 0; + p.drawLine(0, edge_y, rect().width(), edge_y); + + // draw snap point + if (panel_timeline->snapped) { + p.setPen(Qt::white); + int snap_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->snap_point); + p.drawLine(snap_x, 0, snap_x, height()); + } + + // Draw edit cursor + if (current_tool_shows_cursor() && is_track_visible(panel_timeline->cursor_track)) { + int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->cursor_frame); + int cursor_y = getScreenPointFromTrack(panel_timeline->cursor_track); + + p.setPen(Qt::gray); + p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->GetTrackHeight(panel_timeline->cursor_track)); + } + } +} + +void TimelineWidget::resizeEvent(QResizeEvent *) { + scrollBar->setPageStep(height()); +} + +bool TimelineWidget::is_track_visible(int track) { + return (bottom_align == (track < 0)); +} + +// ************************************** +// screen point <-> frame/track functions +// ************************************** + +int TimelineWidget::getTrackFromScreenPoint(int y) { + int track_candidate = 0; + + y += scroll; + + if (bottom_align) { + y -= height(); + } + + if (y < 0) { + track_candidate--; + } + + int compounded_heights = 0; + + while (true) { + int track_height = panel_timeline->GetTrackHeight(track_candidate); + if (olive::CurrentConfig.show_track_lines) track_height++; + if (y < 0) { + track_height = -track_height; + } + + int next_compounded_height = compounded_heights + track_height; + + + if (y >= qMin(next_compounded_height, compounded_heights) && y < qMax(next_compounded_height, compounded_heights)) { + return track_candidate; + } + + compounded_heights = next_compounded_height; + + if (y < 0) { + track_candidate--; + } else { + track_candidate++; + } + } +} + +int TimelineWidget::getScreenPointFromTrack(int track) { + int point = 0; + + int start = (track < 0) ? -1 : 0; + int interval = (track < 0) ? -1 : 1; + + if (track < 0) track--; + + for (int i=start;i!=track;i+=interval) { + point += panel_timeline->GetTrackHeight(i); + if (olive::CurrentConfig.show_track_lines) point++; + } + + if (bottom_align) { + return height() - point - scroll; + } else { + return point - scroll; + } +} + +int TimelineWidget::getClipIndexFromCoords(long frame, int track) { + for (int i=0;iclips.size();i++) { + ClipPtr c = olive::ActiveSequence->clips.at(i); + if (c != nullptr && c->track() == track && frame >= c->timeline_in() && frame < c->timeline_out()) { + return i; + } + } + return -1; +} + +void TimelineWidget::setScroll(int s) { + scroll = s; + update(); +} + +void TimelineWidget::reveal_media() { + panel_project->reveal_media(rc_reveal_media); +} diff --git a/ui/updatenotification.cpp b/ui/updatenotification.cpp index a2fdc5275..e26ecfec6 100644 --- a/ui/updatenotification.cpp +++ b/ui/updatenotification.cpp @@ -1,58 +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 . - -***/ - -#include "updatenotification.h" - -#include -#include - -#include "mainwindow.h" - -UpdateNotification olive::update_notifier; - -UpdateNotification::UpdateNotification() -{ - -} - -void UpdateNotification::check() -{ -#if defined(GITHASH) && defined(UPDATEMSG) - QNetworkAccessManager* manager = new QNetworkAccessManager(); - - connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(finished_slot(QNetworkReply *))); - connect(manager, SIGNAL(finished(QNetworkReply *)), manager, SLOT(deleteLater())); - - QString update_url = QString("http://olivevideoeditor.org/update.php?version=0&hash=%1"); - - QNetworkRequest request(QUrl(update_url.arg(GITHASH))); - manager->get(request); -#endif -} - -void UpdateNotification::finished_slot(QNetworkReply *reply) -{ - QString response = QString::fromUtf8(reply->readAll()); - - if (response == "1") { - olive::MainWindow->statusBar()->showMessage(tr("An update is available from the Olive website. " - "Visit www.olivevideoeditor.org to download it.")); - } -} +/*** + + 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 +#include + +#include "mainwindow.h" + +UpdateNotification olive::update_notifier; + +UpdateNotification::UpdateNotification() +{ + +} + +void UpdateNotification::check() +{ +#if defined(GITHASH) && defined(UPDATEMSG) + QNetworkAccessManager* manager = new QNetworkAccessManager(); + + connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(finished_slot(QNetworkReply *))); + connect(manager, SIGNAL(finished(QNetworkReply *)), manager, SLOT(deleteLater())); + + QString update_url = QString("http://olivevideoeditor.org/update.php?version=0&hash=%1"); + + QNetworkRequest request(QUrl(update_url.arg(GITHASH))); + manager->get(request); +#endif +} + +void UpdateNotification::finished_slot(QNetworkReply *reply) +{ + QString response = QString::fromUtf8(reply->readAll()); + + if (response == "1") { + olive::MainWindow->statusBar()->showMessage(tr("An update is available from the Olive website. " + "Visit www.olivevideoeditor.org to download it.")); + } +} diff --git a/ui/updatenotification.h b/ui/updatenotification.h index 16c4c2f74..0fbc06282 100644 --- a/ui/updatenotification.h +++ b/ui/updatenotification.h @@ -1,39 +1,39 @@ -/*** - - 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 - -#include - -class UpdateNotification : public QObject { - Q_OBJECT -public: - UpdateNotification(); - void check(); -private slots: - void finished_slot(QNetworkReply*); -}; - -namespace olive { - extern UpdateNotification update_notifier; -} - -#endif // UPDATENOTIFICATION_H +/*** + + 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 + +#include + +class UpdateNotification : public QObject { + Q_OBJECT +public: + UpdateNotification(); + void check(); +private slots: + void finished_slot(QNetworkReply*); +}; + +namespace olive { + extern UpdateNotification update_notifier; +} + +#endif // UPDATENOTIFICATION_H diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index 02fbfced9..0c2b689fd 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -1,174 +1,174 @@ -/*** - - 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 "viewercontainer.h" - -#include -#include -#include -#include -#include - -#include "viewerwidget.h" -#include "panels/viewer.h" -#include "timeline/sequence.h" -#include "global/debug.h" - -// enforces aspect ratio -ViewerContainer::ViewerContainer(QWidget *parent) : - QWidget(parent), - fit(true), - child(nullptr) -{ - horizontal_scrollbar = new QScrollBar(Qt::Horizontal, this); - vertical_scrollbar = new QScrollBar(Qt::Vertical, this); - - horizontal_scrollbar->setVisible(false); - vertical_scrollbar->setVisible(false); - - horizontal_scrollbar->setSingleStep(20); - vertical_scrollbar->setSingleStep(20); - - child = new ViewerWidget(this); - child->container = this; - - connect(horizontal_scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scroll_changed())); - connect(vertical_scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scroll_changed())); -} - -ViewerContainer::~ViewerContainer() {} - -void ViewerContainer::dragScrollPress(const QPoint &p) { - drag_start_x = p.x(); - drag_start_y = p.y(); - - horiz_start = horizontal_scrollbar->value(); - vert_start = vertical_scrollbar->value(); -} - -void ViewerContainer::dragScrollMove(const QPoint &p) { - int this_x = p.x(); - int this_y = p.y(); - - horizontal_scrollbar->setValue(horiz_start + (drag_start_x-this_x)); - vertical_scrollbar->setValue(vert_start + (drag_start_y-this_y)); -} - -void ViewerContainer::parseWheelEvent(QWheelEvent *event) { - if (event->modifiers() & Qt::AltModifier) { - QApplication::sendEvent(horizontal_scrollbar, event); - } else { - QApplication::sendEvent(vertical_scrollbar, event); - } -} - -void ViewerContainer::adjust() { - if (viewer->seq != nullptr) { - if (child->waveform) { - child->move(0, 0); - child->resize(size()); - } else { - horizontal_scrollbar->setVisible(false); - vertical_scrollbar->setVisible(false); - - int zoomed_width = qRound(double(viewer->seq->width())*zoom); - int zoomed_height = qRound(double(viewer->seq->height())*zoom); - - if (fit || zoomed_width > width() || zoomed_height > height()) { - // if the zoom size is greater than or equal to the available area, only use the available area - - double aspect_ratio = double(viewer->seq->width())/double(viewer->seq->height()); - - int widget_x = 0; - int widget_y = 0; - int widget_width = width(); - int widget_height = height(); - - if (!fit) { - widget_width -= vertical_scrollbar->sizeHint().width(); - widget_height -= horizontal_scrollbar->sizeHint().height(); - } - - double widget_ar = double(widget_width) / double(widget_height); - - bool widget_is_wider_than_sequence = widget_ar > aspect_ratio; - - if (widget_is_wider_than_sequence) { - widget_width = widget_height * aspect_ratio; - widget_x = (width() / 2) - (widget_width / 2); - } else { - widget_height = widget_width / aspect_ratio; - widget_y = (height() / 2) - (widget_height / 2); - } - - child->move(widget_x, widget_y); - child->resize(widget_width, widget_height); - - if (fit) { - zoom = double(widget_width) / double(viewer->seq->width()); - } else if (zoomed_width > width() || zoomed_height > height()) { - horizontal_scrollbar->setVisible(true); - vertical_scrollbar->setVisible(true); - - horizontal_scrollbar->setMaximum(zoomed_width - width()); - vertical_scrollbar->setMaximum(zoomed_height - height()); - - horizontal_scrollbar->setValue(horizontal_scrollbar->maximum()/2); - vertical_scrollbar->setValue(vertical_scrollbar->maximum()/2); - - adjust_scrollbars(); - } - } else { - // if the zoom size is smaller than the available area, scale the surface down - - int zoomed_x = 0; - int zoomed_y = 0; - - if (zoomed_width < width()) zoomed_x = (width()>>1)-(zoomed_width>>1); - if (zoomed_height < height()) zoomed_y = (height()>>1)-(zoomed_height>>1); - - child->move(zoomed_x, zoomed_y); - child->resize(zoomed_width, zoomed_height); - } - } - } -} - -void ViewerContainer::adjust_scrollbars() { - horizontal_scrollbar->move(0, height()-horizontal_scrollbar->height()); - horizontal_scrollbar->setFixedWidth(qMax(0, width()-vertical_scrollbar->width())); - horizontal_scrollbar->setPageStep(width()); - - vertical_scrollbar->move(width() - vertical_scrollbar->width(), 0); - vertical_scrollbar->setFixedHeight(qMax(0, height()-horizontal_scrollbar->height())); - vertical_scrollbar->setPageStep(height()); -} - -void ViewerContainer::resizeEvent(QResizeEvent *event) { - event->accept(); - adjust(); -} - -void ViewerContainer::scroll_changed() { - child->set_scroll( - double(horizontal_scrollbar->value())/double(horizontal_scrollbar->maximum()), - double(vertical_scrollbar->value())/double(vertical_scrollbar->maximum()) - ); -} +/*** + + 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 "viewercontainer.h" + +#include +#include +#include +#include +#include + +#include "viewerwidget.h" +#include "panels/viewer.h" +#include "timeline/sequence.h" +#include "global/debug.h" + +// enforces aspect ratio +ViewerContainer::ViewerContainer(QWidget *parent) : + QWidget(parent), + fit(true), + child(nullptr) +{ + horizontal_scrollbar = new QScrollBar(Qt::Horizontal, this); + vertical_scrollbar = new QScrollBar(Qt::Vertical, this); + + horizontal_scrollbar->setVisible(false); + vertical_scrollbar->setVisible(false); + + horizontal_scrollbar->setSingleStep(20); + vertical_scrollbar->setSingleStep(20); + + child = new ViewerWidget(this); + child->container = this; + + connect(horizontal_scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scroll_changed())); + connect(vertical_scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scroll_changed())); +} + +ViewerContainer::~ViewerContainer() {} + +void ViewerContainer::dragScrollPress(const QPoint &p) { + drag_start_x = p.x(); + drag_start_y = p.y(); + + horiz_start = horizontal_scrollbar->value(); + vert_start = vertical_scrollbar->value(); +} + +void ViewerContainer::dragScrollMove(const QPoint &p) { + int this_x = p.x(); + int this_y = p.y(); + + horizontal_scrollbar->setValue(horiz_start + (drag_start_x-this_x)); + vertical_scrollbar->setValue(vert_start + (drag_start_y-this_y)); +} + +void ViewerContainer::parseWheelEvent(QWheelEvent *event) { + if (event->modifiers() & Qt::AltModifier) { + QApplication::sendEvent(horizontal_scrollbar, event); + } else { + QApplication::sendEvent(vertical_scrollbar, event); + } +} + +void ViewerContainer::adjust() { + if (viewer->seq != nullptr) { + if (child->waveform) { + child->move(0, 0); + child->resize(size()); + } else { + horizontal_scrollbar->setVisible(false); + vertical_scrollbar->setVisible(false); + + int zoomed_width = qRound(double(viewer->seq->width())*zoom); + int zoomed_height = qRound(double(viewer->seq->height())*zoom); + + if (fit || zoomed_width > width() || zoomed_height > height()) { + // if the zoom size is greater than or equal to the available area, only use the available area + + double aspect_ratio = double(viewer->seq->width())/double(viewer->seq->height()); + + int widget_x = 0; + int widget_y = 0; + int widget_width = width(); + int widget_height = height(); + + if (!fit) { + widget_width -= vertical_scrollbar->sizeHint().width(); + widget_height -= horizontal_scrollbar->sizeHint().height(); + } + + double widget_ar = double(widget_width) / double(widget_height); + + bool widget_is_wider_than_sequence = widget_ar > aspect_ratio; + + if (widget_is_wider_than_sequence) { + widget_width = widget_height * aspect_ratio; + widget_x = (width() / 2) - (widget_width / 2); + } else { + widget_height = widget_width / aspect_ratio; + widget_y = (height() / 2) - (widget_height / 2); + } + + child->move(widget_x, widget_y); + child->resize(widget_width, widget_height); + + if (fit) { + zoom = double(widget_width) / double(viewer->seq->width()); + } else if (zoomed_width > width() || zoomed_height > height()) { + horizontal_scrollbar->setVisible(true); + vertical_scrollbar->setVisible(true); + + horizontal_scrollbar->setMaximum(zoomed_width - width()); + vertical_scrollbar->setMaximum(zoomed_height - height()); + + horizontal_scrollbar->setValue(horizontal_scrollbar->maximum()/2); + vertical_scrollbar->setValue(vertical_scrollbar->maximum()/2); + + adjust_scrollbars(); + } + } else { + // if the zoom size is smaller than the available area, scale the surface down + + int zoomed_x = 0; + int zoomed_y = 0; + + if (zoomed_width < width()) zoomed_x = (width()>>1)-(zoomed_width>>1); + if (zoomed_height < height()) zoomed_y = (height()>>1)-(zoomed_height>>1); + + child->move(zoomed_x, zoomed_y); + child->resize(zoomed_width, zoomed_height); + } + } + } +} + +void ViewerContainer::adjust_scrollbars() { + horizontal_scrollbar->move(0, height()-horizontal_scrollbar->height()); + horizontal_scrollbar->setFixedWidth(qMax(0, width()-vertical_scrollbar->width())); + horizontal_scrollbar->setPageStep(width()); + + vertical_scrollbar->move(width() - vertical_scrollbar->width(), 0); + vertical_scrollbar->setFixedHeight(qMax(0, height()-horizontal_scrollbar->height())); + vertical_scrollbar->setPageStep(height()); +} + +void ViewerContainer::resizeEvent(QResizeEvent *event) { + event->accept(); + adjust(); +} + +void ViewerContainer::scroll_changed() { + child->set_scroll( + double(horizontal_scrollbar->value())/double(horizontal_scrollbar->maximum()), + double(vertical_scrollbar->value())/double(vertical_scrollbar->maximum()) + ); +} diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index 8a5da99fe..ed6845782 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -1,69 +1,69 @@ -/*** - - 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 VIEWERCONTAINER_H -#define VIEWERCONTAINER_H - -#include -class Viewer; -class ViewerWidget; -class QScrollBar; - -class ViewerContainer : public QWidget -{ - Q_OBJECT -public: - explicit ViewerContainer(QWidget *parent = 0); - ~ViewerContainer(); - - bool fit; - double zoom; - - void dragScrollPress(const QPoint&); - void dragScrollMove(const QPoint&); - void parseWheelEvent(QWheelEvent* event); - - Viewer* viewer; - ViewerWidget* child; - void adjust(); - - // manually moves scrollbars into the correct position - void adjust_scrollbars(); - -protected: - void resizeEvent(QResizeEvent *event); - -signals: - -public slots: - -private slots: - void scroll_changed(); - -private: - int drag_start_x; - int drag_start_y; - int horiz_start; - int vert_start; - QScrollBar* horizontal_scrollbar; - QScrollBar* vertical_scrollbar; -}; - -#endif // VIEWERCONTAINER_H +/*** + + 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 VIEWERCONTAINER_H +#define VIEWERCONTAINER_H + +#include +class Viewer; +class ViewerWidget; +class QScrollBar; + +class ViewerContainer : public QWidget +{ + Q_OBJECT +public: + explicit ViewerContainer(QWidget *parent = 0); + ~ViewerContainer(); + + bool fit; + double zoom; + + void dragScrollPress(const QPoint&); + void dragScrollMove(const QPoint&); + void parseWheelEvent(QWheelEvent* event); + + Viewer* viewer; + ViewerWidget* child; + void adjust(); + + // manually moves scrollbars into the correct position + void adjust_scrollbars(); + +protected: + void resizeEvent(QResizeEvent *event); + +signals: + +public slots: + +private slots: + void scroll_changed(); + +private: + 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 ecef98126..3f54c33ad 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -1,806 +1,806 @@ -/*** - - 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 "viewerwidget.h" - -extern "C" { -#include -} - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "panels/panels.h" -#include "project/projectelements.h" -#include "rendering/renderfunctions.h" -#include "rendering/audio.h" -#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/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), - waveform(false), - waveform_zoom(1.0), - waveform_scroll(0), - dragging(false), - gizmos(nullptr), - selected_gizmo(nullptr), - x_scroll(0), - y_scroll(0) -{ - setMouseTracking(true); - setFocusPolicy(Qt::ClickFocus); - - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); - - renderer.start(QThread::HighestPriority); - connect(&renderer, SIGNAL(ready()), this, SLOT(queue_repaint())); - - window = new ViewerWindow(this); -} - -ViewerWidget::~ViewerWidget() { - renderer.cancel(); -} - -void ViewerWidget::set_waveform_scroll(int s) { - if (waveform) { - waveform_scroll = s; - update(); - } -} - -void ViewerWidget::set_fullscreen(int screen) { - if (screen >= 0 && screen < QGuiApplication::screens().size()) { - QScreen* selected_screen = QGuiApplication::screens().at(screen); - window->showFullScreen(); - window->setGeometry(selected_screen->geometry()); - } else { - qCritical() << "Failed to find requested screen" << screen << "to set fullscreen to"; - } -} - -void ViewerWidget::show_context_menu() { - Menu menu(this); - - QAction* save_frame_as_image = menu.addAction(tr("Save Frame as Image...")); - connect(save_frame_as_image, SIGNAL(triggered(bool)), this, SLOT(save_frame())); - - Menu* fullscreen_menu = new Menu(tr("Show Fullscreen")); - menu.addMenu(fullscreen_menu); - QList screens = QGuiApplication::screens(); - if (window->isVisible()) { - fullscreen_menu->addAction(tr("Disable")); - } - for (int i=0;iaddAction(tr("Screen %1: %2x%3").arg( - QString::number(i), - QString::number(screens.at(i)->size().width()), - QString::number(screens.at(i)->size().height()))); - screen_action->setData(i); - } - connect(fullscreen_menu, SIGNAL(triggered(QAction*)), this, SLOT(fullscreen_menu_action(QAction*))); - - Menu zoom_menu(tr("Zoom")); - QAction* fit_zoom = zoom_menu.addAction(tr("Fit")); - connect(fit_zoom, SIGNAL(triggered(bool)), this, SLOT(set_fit_zoom())); - zoom_menu.addAction("10%")->setData(0.1); - zoom_menu.addAction("25%")->setData(0.25); - zoom_menu.addAction("50%")->setData(0.5); - zoom_menu.addAction("75%")->setData(0.75); - zoom_menu.addAction("100%")->setData(1.0); - zoom_menu.addAction("150%")->setData(1.5); - zoom_menu.addAction("200%")->setData(2.0); - zoom_menu.addAction("400%")->setData(4.0); - QAction* custom_zoom = zoom_menu.addAction(tr("Custom")); - connect(custom_zoom, SIGNAL(triggered(bool)), this, SLOT(set_custom_zoom())); - connect(&zoom_menu, SIGNAL(triggered(QAction*)), this, SLOT(set_menu_zoom(QAction*))); - menu.addMenu(&zoom_menu); - - if (viewer->mode() != Viewer::kTimelineMode) { - menu.addAction(tr("Close Media"), viewer, SLOT(close_media())); - } - - menu.exec(QCursor::pos()); -} - -void ViewerWidget::save_frame() { - QFileDialog fd(this); - fd.setAcceptMode(QFileDialog::AcceptSave); - fd.setFileMode(QFileDialog::AnyFile); - fd.setWindowTitle(tr("Save Frame")); - fd.setNameFilter("Portable Network Graphic (*.png);;JPEG (*.jpg);;Windows Bitmap (*.bmp);;Portable Pixmap (*.ppm);;X11 Bitmap (*.xbm);;X11 Pixmap (*.xpm)"); - - if (fd.exec()) { - QString fn = fd.selectedFiles().at(0); - QString selected_ext = fd.selectedNameFilter().mid(fd.selectedNameFilter().indexOf(QRegExp("\\*.[a-z][a-z][a-z]")) + 1, 4); - if (!fn.endsWith(selected_ext, Qt::CaseInsensitive)) { - fn += selected_ext; - } - - renderer.start_render(context(), viewer->seq.get(), 1, fn); - } -} - -void ViewerWidget::queue_repaint() { - update(); -} - -void ViewerWidget::fullscreen_menu_action(QAction *action) { - if (action->data().isNull()) { - window->hide(); - } else { - set_fullscreen(action->data().toInt()); - } -} - -void ViewerWidget::set_fit_zoom() { - container->fit = true; - container->adjust(); -} - -void ViewerWidget::set_custom_zoom() { - bool ok; - double d = QInputDialog::getDouble(this, - tr("Viewer Zoom"), - tr("Set Custom Zoom Value:"), - container->zoom*100, 0, 2147483647, 2, &ok); - if (ok) { - container->fit = false; - container->zoom = d*0.01; - container->adjust(); - } -} - -void ViewerWidget::set_menu_zoom(QAction* action) { - const QVariant& data = action->data(); - if (!data.isNull()) { - container->fit = false; - container->zoom = data.toDouble(); - container->adjust(); - } -} - -void ViewerWidget::retry() { - update(); -} - -void ViewerWidget::initializeGL() { - 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() { - if (viewer->seq != nullptr) { - // send context to other thread for drawing - if (waveform) { - update(); - } else { - doneCurrent(); - renderer.start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); - } - - // render the audio - olive::rendering::compose_audio(viewer, viewer->seq.get(), viewer->get_playback_speed(), false); - } -} - -RenderThread *ViewerWidget::get_renderer() { - return &renderer; -} - -void ViewerWidget::set_scroll(double x, double y) { - x_scroll = x; - y_scroll = y; - update(); -} - -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(); - - renderer.delete_ctx(); - - title_safe_area_buffer_.destroy(); - - if (gizmo_buffer_.isCreated()) { - gizmo_buffer_.destroy(); - } - - vao_.destroy(); - - pipeline_ = nullptr; - - doneCurrent(); -} - -EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { - if (gizmos != nullptr) { - double multiplier = double(viewer->seq->width()) / double(width()); - QPoint mouse_pos(qRound(x*multiplier), qRound((height()-y)*multiplier)); - int dot_size = 2 * qRound(GIZMO_DOT_SIZE * multiplier); - int target_size = 2 * qRound(GIZMO_TARGET_SIZE * multiplier); - for (int i=0;igizmo_count();i++) { - EffectGizmo* g = gizmos->gizmo(i); - - switch (g->get_type()) { - case GIZMO_TYPE_DOT: - if (mouse_pos.x() > g->screen_pos[0].x() - dot_size - && mouse_pos.y() > g->screen_pos[0].y() - dot_size - && mouse_pos.x() < g->screen_pos[0].x() + dot_size - && mouse_pos.y() < g->screen_pos[0].y() + dot_size) { - return g; - } - break; - case GIZMO_TYPE_POLY: - if (QPolygon(g->screen_pos).containsPoint(mouse_pos, Qt::OddEvenFill)) { - return g; - } - break; - case GIZMO_TYPE_TARGET: - if (mouse_pos.x() > g->screen_pos[0].x() - target_size - && mouse_pos.y() > g->screen_pos[0].y() - target_size - && mouse_pos.x() < g->screen_pos[0].x() + target_size - && mouse_pos.y() < g->screen_pos[0].y() + target_size) { - return g; - } - break; - } - } - } - return nullptr; -} - -void ViewerWidget::move_gizmos(QMouseEvent *event, bool done) { - if (selected_gizmo != nullptr) { - double multiplier = double(viewer->seq->width()) / double(width()); - - 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->track()->sequence()->playhead), - done); - - gizmo_x_mvmt += x_movement; - gizmo_y_mvmt += y_movement; - - drag_start_x = event->pos().x(); - drag_start_y = event->pos().y(); - } -} - -void ViewerWidget::mousePressEvent(QMouseEvent* event) { - if (waveform) { - seek_from_click(event->x()); - } 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(); - drag_start_y = event->pos().y(); - - gizmo_x_mvmt = 0; - gizmo_y_mvmt = 0; - - selected_gizmo = get_gizmo_from_mouse(event->pos().x(), event->pos().y()); - } - dragging = true; -} - -void ViewerWidget::mouseMoveEvent(QMouseEvent* event) { - unsetCursor(); - 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 || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { - container->dragScrollMove(event->pos()*container->zoom); - } else if (event->buttons() & Qt::LeftButton) { - if (gizmos == nullptr) { - viewer->initiate_drag(olive::timeline::kImportBoth); - dragging = false; - } else { - move_gizmos(event, false); - } - } - } else { - EffectGizmo* g = get_gizmo_from_mouse(event->pos().x(), event->pos().y()); - if (g != nullptr) { - if (g->get_cursor() > -1) { - setCursor(static_cast(g->get_cursor())); - } - } - } -} - -void ViewerWidget::mouseReleaseEvent(QMouseEvent *event) { - if (dragging - && gizmos != nullptr - && event->button() == Qt::LeftButton - && olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_HAND) { - move_gizmos(event, true); - } - dragging = false; -} - -void ViewerWidget::wheelEvent(QWheelEvent *event) { - container->parseWheelEvent(event); -} - -void ViewerWidget::close_window() { - window->hide(); -} - -void ViewerWidget::wait_until_render_is_paused() -{ - renderer.wait_until_paused(); -} - -void ViewerWidget::draw_waveform_func() { - QPainter p(this); - if (viewer->seq->using_workarea) { - int in_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_in) - waveform_scroll; - int out_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_out) - waveform_scroll; - - p.fillRect(QRect(in_x, 0, out_x - in_x, height()), QColor(255, 255, 255, 64)); - p.setPen(Qt::white); - p.drawLine(in_x, 0, in_x, height()); - p.drawLine(out_x, 0, out_x, height()); - } - QRect wr = rect(); - wr.setX(wr.x() - waveform_scroll); - - p.setPen(Qt::green); - 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() { - QOpenGLFunctions* func = context()->functions(); - - 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 { - 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; - } - - float adjusted_cross_x1 = 0.5f - horizontal_cross_size; - float adjusted_cross_x2 = 0.5f + horizontal_cross_size; - - pipeline_->setUniformValue("mvp_matrix", matrix); - pipeline_->setUniformValue("color_only", true); - pipeline_->setUniformValue("color_only_color", QColor(192, 192, 192, 255)); - - - - GLfloat vertices[] = { - // action safe lines - 0.05f, 0.05f, 0.0f, - 0.95f, 0.05f, 0.0f, - - 0.95f, 0.05f, 0.0f, - 0.95f, 0.95f, 0.0f, - - 0.95f, 0.95f, 0.0f, - 0.05f, 0.95f, 0.0f, - - 0.05f, 0.95f, 0.0f, - 0.05f, 0.05f, 0.0f, - - // title safe lines - 0.1f, 0.1f, 0.0f, - 0.9f, 0.1f, 0.0f, - - 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(); - -} - -void ViewerWidget::draw_gizmos() { - QOpenGLFunctions* func = context()->functions(); - - pipeline_->bind(); - - double zoom_factor = container->zoom/(double(width())/double(viewer->seq->width())); - - 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))); - - // 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; - - for (int j=0;jgizmo_count();j++) { - - EffectGizmo* g = gizmos->gizmo(j); - - switch (g->get_type()) { - 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 an arbitrary polygon with lines - - for (int k=1;kget_point_count();k++) { - - 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); - - } - - 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" gizmo (square with two lines through the middle) - - 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()+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); - - break; - } - } - - 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 (viewer->seq != nullptr) { - - QOpenGLFunctions* f = context()->functions(); - - viewer->seq->SetGLContext(context()); - - makeCurrent(); - - // clear to solid black - f->glClearColor(0.0, 0.0, 0.0, 0.0); - f->glClear(GL_COLOR_BUFFER_BIT); - - - // draw texture from render thread - - f->glViewport(0, 0, width(), height()); - - GLuint tex = viewer->seq->texture(); - - f->glBindTexture(GL_TEXTURE_2D, tex); - - qDebug() << "drawing texture" << tex; - - olive::rendering::Blit(pipeline_.get(), true, get_matrix()); - - f->glBindTexture(GL_TEXTURE_2D, 0); - - } - - /* - if (waveform) { - draw_waveform_func(); - } else { - 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 - f->glClearColor(0.0, 0.0, 0.0, 0.0); - f->glClear(GL_COLOR_BUFFER_BIT); - - - // draw texture from render thread - - f->glViewport(0, 0, width(), height()); - - f->glBindTexture(GL_TEXTURE_2D, tex); - - olive::rendering::Blit(pipeline_.get(), true, get_matrix()); - - f->glBindTexture(GL_TEXTURE_2D, 0); - - // draw title/action safe area - if (olive::config.show_title_safe_area) { - draw_title_safe_area(); - } - - gizmos = renderer.gizmos; - if (gizmos != nullptr) { - draw_gizmos(); - } - - 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) { - doneCurrent(); - renderer.start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); - } - } - */ -} +/*** + + 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 "viewerwidget.h" + +extern "C" { +#include +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "panels/panels.h" +#include "project/projectelements.h" +#include "rendering/renderfunctions.h" +#include "rendering/audio.h" +#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/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), + waveform(false), + waveform_zoom(1.0), + waveform_scroll(0), + dragging(false), + gizmos(nullptr), + selected_gizmo(nullptr), + x_scroll(0), + y_scroll(0) +{ + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); + + renderer.start(QThread::HighestPriority); + connect(&renderer, SIGNAL(ready()), this, SLOT(queue_repaint())); + + window = new ViewerWindow(this); +} + +ViewerWidget::~ViewerWidget() { + renderer.cancel(); +} + +void ViewerWidget::set_waveform_scroll(int s) { + if (waveform) { + waveform_scroll = s; + update(); + } +} + +void ViewerWidget::set_fullscreen(int screen) { + if (screen >= 0 && screen < QGuiApplication::screens().size()) { + QScreen* selected_screen = QGuiApplication::screens().at(screen); + window->showFullScreen(); + window->setGeometry(selected_screen->geometry()); + } else { + qCritical() << "Failed to find requested screen" << screen << "to set fullscreen to"; + } +} + +void ViewerWidget::show_context_menu() { + Menu menu(this); + + QAction* save_frame_as_image = menu.addAction(tr("Save Frame as Image...")); + connect(save_frame_as_image, SIGNAL(triggered(bool)), this, SLOT(save_frame())); + + Menu* fullscreen_menu = new Menu(tr("Show Fullscreen")); + menu.addMenu(fullscreen_menu); + QList screens = QGuiApplication::screens(); + if (window->isVisible()) { + fullscreen_menu->addAction(tr("Disable")); + } + for (int i=0;iaddAction(tr("Screen %1: %2x%3").arg( + QString::number(i), + QString::number(screens.at(i)->size().width()), + QString::number(screens.at(i)->size().height()))); + screen_action->setData(i); + } + connect(fullscreen_menu, SIGNAL(triggered(QAction*)), this, SLOT(fullscreen_menu_action(QAction*))); + + Menu zoom_menu(tr("Zoom")); + QAction* fit_zoom = zoom_menu.addAction(tr("Fit")); + connect(fit_zoom, SIGNAL(triggered(bool)), this, SLOT(set_fit_zoom())); + zoom_menu.addAction("10%")->setData(0.1); + zoom_menu.addAction("25%")->setData(0.25); + zoom_menu.addAction("50%")->setData(0.5); + zoom_menu.addAction("75%")->setData(0.75); + zoom_menu.addAction("100%")->setData(1.0); + zoom_menu.addAction("150%")->setData(1.5); + zoom_menu.addAction("200%")->setData(2.0); + zoom_menu.addAction("400%")->setData(4.0); + QAction* custom_zoom = zoom_menu.addAction(tr("Custom")); + connect(custom_zoom, SIGNAL(triggered(bool)), this, SLOT(set_custom_zoom())); + connect(&zoom_menu, SIGNAL(triggered(QAction*)), this, SLOT(set_menu_zoom(QAction*))); + menu.addMenu(&zoom_menu); + + if (viewer->mode() != Viewer::kTimelineMode) { + menu.addAction(tr("Close Media"), viewer, SLOT(close_media())); + } + + menu.exec(QCursor::pos()); +} + +void ViewerWidget::save_frame() { + QFileDialog fd(this); + fd.setAcceptMode(QFileDialog::AcceptSave); + fd.setFileMode(QFileDialog::AnyFile); + fd.setWindowTitle(tr("Save Frame")); + fd.setNameFilter("Portable Network Graphic (*.png);;JPEG (*.jpg);;Windows Bitmap (*.bmp);;Portable Pixmap (*.ppm);;X11 Bitmap (*.xbm);;X11 Pixmap (*.xpm)"); + + if (fd.exec()) { + QString fn = fd.selectedFiles().at(0); + QString selected_ext = fd.selectedNameFilter().mid(fd.selectedNameFilter().indexOf(QRegExp("\\*.[a-z][a-z][a-z]")) + 1, 4); + if (!fn.endsWith(selected_ext, Qt::CaseInsensitive)) { + fn += selected_ext; + } + + renderer.start_render(context(), viewer->seq.get(), 1, fn); + } +} + +void ViewerWidget::queue_repaint() { + update(); +} + +void ViewerWidget::fullscreen_menu_action(QAction *action) { + if (action->data().isNull()) { + window->hide(); + } else { + set_fullscreen(action->data().toInt()); + } +} + +void ViewerWidget::set_fit_zoom() { + container->fit = true; + container->adjust(); +} + +void ViewerWidget::set_custom_zoom() { + bool ok; + double d = QInputDialog::getDouble(this, + tr("Viewer Zoom"), + tr("Set Custom Zoom Value:"), + container->zoom*100, 0, 2147483647, 2, &ok); + if (ok) { + container->fit = false; + container->zoom = d*0.01; + container->adjust(); + } +} + +void ViewerWidget::set_menu_zoom(QAction* action) { + const QVariant& data = action->data(); + if (!data.isNull()) { + container->fit = false; + container->zoom = data.toDouble(); + container->adjust(); + } +} + +void ViewerWidget::retry() { + update(); +} + +void ViewerWidget::initializeGL() { + 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() { + if (viewer->seq != nullptr) { + // send context to other thread for drawing + if (waveform) { + update(); + } else { + doneCurrent(); + renderer.start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); + } + + // render the audio + olive::rendering::compose_audio(viewer, viewer->seq.get(), viewer->get_playback_speed(), false); + } +} + +RenderThread *ViewerWidget::get_renderer() { + return &renderer; +} + +void ViewerWidget::set_scroll(double x, double y) { + x_scroll = x; + y_scroll = y; + update(); +} + +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(); + + renderer.delete_ctx(); + + title_safe_area_buffer_.destroy(); + + if (gizmo_buffer_.isCreated()) { + gizmo_buffer_.destroy(); + } + + vao_.destroy(); + + pipeline_ = nullptr; + + doneCurrent(); +} + +EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { + if (gizmos != nullptr) { + double multiplier = double(viewer->seq->width()) / double(width()); + QPoint mouse_pos(qRound(x*multiplier), qRound((height()-y)*multiplier)); + int dot_size = 2 * qRound(GIZMO_DOT_SIZE * multiplier); + int target_size = 2 * qRound(GIZMO_TARGET_SIZE * multiplier); + for (int i=0;igizmo_count();i++) { + EffectGizmo* g = gizmos->gizmo(i); + + switch (g->get_type()) { + case GIZMO_TYPE_DOT: + if (mouse_pos.x() > g->screen_pos[0].x() - dot_size + && mouse_pos.y() > g->screen_pos[0].y() - dot_size + && mouse_pos.x() < g->screen_pos[0].x() + dot_size + && mouse_pos.y() < g->screen_pos[0].y() + dot_size) { + return g; + } + break; + case GIZMO_TYPE_POLY: + if (QPolygon(g->screen_pos).containsPoint(mouse_pos, Qt::OddEvenFill)) { + return g; + } + break; + case GIZMO_TYPE_TARGET: + if (mouse_pos.x() > g->screen_pos[0].x() - target_size + && mouse_pos.y() > g->screen_pos[0].y() - target_size + && mouse_pos.x() < g->screen_pos[0].x() + target_size + && mouse_pos.y() < g->screen_pos[0].y() + target_size) { + return g; + } + break; + } + } + } + return nullptr; +} + +void ViewerWidget::move_gizmos(QMouseEvent *event, bool done) { + if (selected_gizmo != nullptr) { + double multiplier = double(viewer->seq->width()) / double(width()); + + 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->track()->sequence()->playhead), + done); + + gizmo_x_mvmt += x_movement; + gizmo_y_mvmt += y_movement; + + drag_start_x = event->pos().x(); + drag_start_y = event->pos().y(); + } +} + +void ViewerWidget::mousePressEvent(QMouseEvent* event) { + if (waveform) { + seek_from_click(event->x()); + } 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(); + drag_start_y = event->pos().y(); + + gizmo_x_mvmt = 0; + gizmo_y_mvmt = 0; + + selected_gizmo = get_gizmo_from_mouse(event->pos().x(), event->pos().y()); + } + dragging = true; +} + +void ViewerWidget::mouseMoveEvent(QMouseEvent* event) { + unsetCursor(); + 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 || olive::timeline::current_tool == olive::timeline::TIMELINE_TOOL_HAND) { + container->dragScrollMove(event->pos()*container->zoom); + } else if (event->buttons() & Qt::LeftButton) { + if (gizmos == nullptr) { + viewer->initiate_drag(olive::timeline::kImportBoth); + dragging = false; + } else { + move_gizmos(event, false); + } + } + } else { + EffectGizmo* g = get_gizmo_from_mouse(event->pos().x(), event->pos().y()); + if (g != nullptr) { + if (g->get_cursor() > -1) { + setCursor(static_cast(g->get_cursor())); + } + } + } +} + +void ViewerWidget::mouseReleaseEvent(QMouseEvent *event) { + if (dragging + && gizmos != nullptr + && event->button() == Qt::LeftButton + && olive::timeline::current_tool != olive::timeline::TIMELINE_TOOL_HAND) { + move_gizmos(event, true); + } + dragging = false; +} + +void ViewerWidget::wheelEvent(QWheelEvent *event) { + container->parseWheelEvent(event); +} + +void ViewerWidget::close_window() { + window->hide(); +} + +void ViewerWidget::wait_until_render_is_paused() +{ + renderer.wait_until_paused(); +} + +void ViewerWidget::draw_waveform_func() { + QPainter p(this); + if (viewer->seq->using_workarea) { + int in_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_in) - waveform_scroll; + int out_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_out) - waveform_scroll; + + p.fillRect(QRect(in_x, 0, out_x - in_x, height()), QColor(255, 255, 255, 64)); + p.setPen(Qt::white); + p.drawLine(in_x, 0, in_x, height()); + p.drawLine(out_x, 0, out_x, height()); + } + QRect wr = rect(); + wr.setX(wr.x() - waveform_scroll); + + p.setPen(Qt::green); + 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() { + QOpenGLFunctions* func = context()->functions(); + + 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 { + 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; + } + + float adjusted_cross_x1 = 0.5f - horizontal_cross_size; + float adjusted_cross_x2 = 0.5f + horizontal_cross_size; + + pipeline_->setUniformValue("mvp_matrix", matrix); + pipeline_->setUniformValue("color_only", true); + pipeline_->setUniformValue("color_only_color", QColor(192, 192, 192, 255)); + + + + GLfloat vertices[] = { + // action safe lines + 0.05f, 0.05f, 0.0f, + 0.95f, 0.05f, 0.0f, + + 0.95f, 0.05f, 0.0f, + 0.95f, 0.95f, 0.0f, + + 0.95f, 0.95f, 0.0f, + 0.05f, 0.95f, 0.0f, + + 0.05f, 0.95f, 0.0f, + 0.05f, 0.05f, 0.0f, + + // title safe lines + 0.1f, 0.1f, 0.0f, + 0.9f, 0.1f, 0.0f, + + 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(); + +} + +void ViewerWidget::draw_gizmos() { + QOpenGLFunctions* func = context()->functions(); + + pipeline_->bind(); + + double zoom_factor = container->zoom/(double(width())/double(viewer->seq->width())); + + 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))); + + // 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; + + for (int j=0;jgizmo_count();j++) { + + EffectGizmo* g = gizmos->gizmo(j); + + switch (g->get_type()) { + 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 an arbitrary polygon with lines + + for (int k=1;kget_point_count();k++) { + + 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); + + } + + 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" gizmo (square with two lines through the middle) + + 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()+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); + + break; + } + } + + 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 (viewer->seq != nullptr) { + + QOpenGLFunctions* f = context()->functions(); + + viewer->seq->SetGLContext(context()); + + makeCurrent(); + + // clear to solid black + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); + + + // draw texture from render thread + + f->glViewport(0, 0, width(), height()); + + GLuint tex = viewer->seq->texture(); + + f->glBindTexture(GL_TEXTURE_2D, tex); + + qDebug() << "drawing texture" << tex; + + olive::rendering::Blit(pipeline_.get(), true, get_matrix()); + + f->glBindTexture(GL_TEXTURE_2D, 0); + + } + + /* + if (waveform) { + draw_waveform_func(); + } else { + 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 + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); + + + // draw texture from render thread + + f->glViewport(0, 0, width(), height()); + + f->glBindTexture(GL_TEXTURE_2D, tex); + + olive::rendering::Blit(pipeline_.get(), true, get_matrix()); + + f->glBindTexture(GL_TEXTURE_2D, 0); + + // draw title/action safe area + if (olive::config.show_title_safe_area) { + draw_title_safe_area(); + } + + gizmos = renderer.gizmos; + if (gizmos != nullptr) { + draw_gizmos(); + } + + 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) { + doneCurrent(); + renderer.start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); + } + } + */ +} diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 2b16bf96b..f66006b6c 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -1,115 +1,115 @@ -/*** - - 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 VIEWERWIDGET_H -#define VIEWERWIDGET_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "timeline/clip.h" -#include "project/footage.h" -#include "nodes/oldeffectnode.h" -#include "ui/viewerwindow.h" -#include "ui/viewercontainer.h" -#include "rendering/renderthread.h" - -class Viewer; -class QOpenGLFramebufferObject; -struct GLTextureCoords; - -class ViewerWidget : public QOpenGLWidget -{ - Q_OBJECT -public: - ViewerWidget(QWidget *parent = nullptr); - ~ViewerWidget(); - - void close_window(); - void wait_until_render_is_paused(); - - void paintGL(); - void initializeGL(); - Viewer* viewer; - ViewerContainer* container; - - bool waveform; - ClipPtr waveform_clip; - const FootageStream* waveform_ms; - double waveform_zoom; - int waveform_scroll; - - void frame_update(); - RenderThread* get_renderer(); - void set_scroll(double x, double y); -public slots: - void set_waveform_scroll(int s); - void set_fullscreen(int screen = 0); -protected: - void mousePressEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void wheelEvent(QWheelEvent* event); -private: - void draw_waveform_func(); - void draw_title_safe_area(); - void draw_gizmos(); - EffectGizmo* get_gizmo_from_mouse(int x, int y); - void move_gizmos(QMouseEvent *event, bool done); - bool dragging; - void seek_from_click(int x); - QMatrix4x4 get_matrix(); - OldEffectNode* gizmos; - int drag_start_x; - int drag_start_y; - int gizmo_x_mvmt; - int gizmo_y_mvmt; - EffectGizmo* selected_gizmo; - 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(); - void show_context_menu(); - void save_frame(); - void queue_repaint(); - void fullscreen_menu_action(QAction* action); - void set_fit_zoom(); - void set_custom_zoom(); - void set_menu_zoom(QAction *action); -}; - -#endif // VIEWERWIDGET_H +/*** + + 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 VIEWERWIDGET_H +#define VIEWERWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "timeline/clip.h" +#include "project/footage.h" +#include "nodes/oldeffectnode.h" +#include "ui/viewerwindow.h" +#include "ui/viewercontainer.h" +#include "rendering/renderthread.h" + +class Viewer; +class QOpenGLFramebufferObject; +struct GLTextureCoords; + +class ViewerWidget : public QOpenGLWidget +{ + Q_OBJECT +public: + ViewerWidget(QWidget *parent = nullptr); + ~ViewerWidget(); + + void close_window(); + void wait_until_render_is_paused(); + + void paintGL(); + void initializeGL(); + Viewer* viewer; + ViewerContainer* container; + + bool waveform; + ClipPtr waveform_clip; + const FootageStream* waveform_ms; + double waveform_zoom; + int waveform_scroll; + + void frame_update(); + RenderThread* get_renderer(); + void set_scroll(double x, double y); +public slots: + void set_waveform_scroll(int s); + void set_fullscreen(int screen = 0); +protected: + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void wheelEvent(QWheelEvent* event); +private: + void draw_waveform_func(); + void draw_title_safe_area(); + void draw_gizmos(); + EffectGizmo* get_gizmo_from_mouse(int x, int y); + void move_gizmos(QMouseEvent *event, bool done); + bool dragging; + void seek_from_click(int x); + QMatrix4x4 get_matrix(); + OldEffectNode* gizmos; + int drag_start_x; + int drag_start_y; + int gizmo_x_mvmt; + int gizmo_y_mvmt; + EffectGizmo* selected_gizmo; + 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(); + void show_context_menu(); + void save_frame(); + void queue_repaint(); + void fullscreen_menu_action(QAction* action); + void set_fit_zoom(); + void set_custom_zoom(); + void set_menu_zoom(QAction *action); +}; + +#endif // VIEWERWIDGET_H diff --git a/ui/viewerwindow.cpp b/ui/viewerwindow.cpp index 5fb5edb69..9582c3e32 100644 --- a/ui/viewerwindow.cpp +++ b/ui/viewerwindow.cpp @@ -1,193 +1,193 @@ -/*** - - 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 "viewerwindow.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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) -{ - setMouseTracking(true); - - 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; - update(); -} - -void ViewerWindow::shortcut_copier(QVector& shortcuts, QMenu* menu) { - QList menu_action = menu->actions(); - for (int i=0;imenu() != nullptr) { - shortcut_copier(shortcuts, menu_action.at(i)->menu()); - } else if (!menu_action.at(i)->isSeparator() && !menu_action.at(i)->shortcut().isEmpty()) { - QShortcut* sc = new QShortcut(this); - sc->setKey(menu_action.at(i)->shortcut()); - connect(sc, SIGNAL(activated()), menu_action.at(i), SLOT(trigger())); - shortcuts.append(sc); - } - } -} - -void ViewerWindow::showEvent(QShowEvent *) -{ - // Here, we copy all shortcuts from the MainWindow to this window. I don't like this solution, but messing around - // with Qt's event system proved fruitless. Also setting the shortcuts to ApplicationShortcut rather than - // WindowShortcut caused issues elsewhere (shortcuts being picked up in comboboxes and dialog boxes - we only - // want the shortcuts to be shared to this window). Therefore, this and shortcut_copier() are so far the best - // solutions I can find. - - // Clear any existing shortcuts in case they've changed since the last showing - for (int i=0;i menubar_actions = olive::MainWindow->menuBar()->actions(); - for (int i=0;imenu()); - } -} - -void ViewerWindow::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape) { - hide(); - } -} - -void ViewerWindow::mousePressEvent(QMouseEvent *e) { - 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; - update(); - } -} - -void ViewerWindow::initializeGL() -{ - pipeline_ = olive::shader::GetPipeline(); -} - -void ViewerWindow::paintGL() { - if (texture_ > 0) { - if (mutex_ != nullptr) mutex_->lock(); - - QOpenGLFunctions* f = context()->functions(); - //QOpenGLExtraFunctions* xf = context()->extraFunctions(); - - makeCurrent(); - - // clear to solid black - f->glClearColor(0.0, 0.0, 0.0, 0.0); - f->glClear(GL_COLOR_BUFFER_BIT); - - - // draw texture from render thread - - - QMatrix4x4 matrix; - - double widget_ar = (double(width()) / double(height())); - if (widget_ar > ar_) { - matrix.scale(ar_ / widget_ar, 1.0); - } else { - matrix.scale(1.0f, widget_ar / ar_); - } - - - f->glViewport(0, 0, width(), height()); - - f->glBindTexture(GL_TEXTURE_2D, texture_); - - olive::rendering::Blit(pipeline_.get(), true, matrix); - - f->glBindTexture(GL_TEXTURE_2D, 0); - - - - if (mutex_ != nullptr) mutex_->unlock(); - } - - if (show_fullscreen_msg_) { - QPainter p(this); - - QFont f = p.font(); - f.setPointSize(24); - p.setFont(f); - - QFontMetrics fm(f); - - QString fs_str = tr("Exit Fullscreen"); - - p.setPen(Qt::white); - p.setBrush(QColor(0, 0, 0, 128)); - - int text_width = fm.width(fs_str); - int text_x = (width()/2)-(text_width/2); - int text_y = fm.height()+fm.ascent(); - - int rect_padding = 8; - - 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.drawText(text_x, text_y, fs_str); - } -} - -void ViewerWindow::fullscreen_msg_timeout() { - fullscreen_msg_timer_.stop(); - if (show_fullscreen_msg_) { - show_fullscreen_msg_ = false; - update(); - } -} +/*** + + 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 "viewerwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) +{ + setMouseTracking(true); + + 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; + update(); +} + +void ViewerWindow::shortcut_copier(QVector& shortcuts, QMenu* menu) { + QList menu_action = menu->actions(); + for (int i=0;imenu() != nullptr) { + shortcut_copier(shortcuts, menu_action.at(i)->menu()); + } else if (!menu_action.at(i)->isSeparator() && !menu_action.at(i)->shortcut().isEmpty()) { + QShortcut* sc = new QShortcut(this); + sc->setKey(menu_action.at(i)->shortcut()); + connect(sc, SIGNAL(activated()), menu_action.at(i), SLOT(trigger())); + shortcuts.append(sc); + } + } +} + +void ViewerWindow::showEvent(QShowEvent *) +{ + // Here, we copy all shortcuts from the MainWindow to this window. I don't like this solution, but messing around + // with Qt's event system proved fruitless. Also setting the shortcuts to ApplicationShortcut rather than + // WindowShortcut caused issues elsewhere (shortcuts being picked up in comboboxes and dialog boxes - we only + // want the shortcuts to be shared to this window). Therefore, this and shortcut_copier() are so far the best + // solutions I can find. + + // Clear any existing shortcuts in case they've changed since the last showing + for (int i=0;i menubar_actions = olive::MainWindow->menuBar()->actions(); + for (int i=0;imenu()); + } +} + +void ViewerWindow::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape) { + hide(); + } +} + +void ViewerWindow::mousePressEvent(QMouseEvent *e) { + 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; + update(); + } +} + +void ViewerWindow::initializeGL() +{ + pipeline_ = olive::shader::GetPipeline(); +} + +void ViewerWindow::paintGL() { + if (texture_ > 0) { + if (mutex_ != nullptr) mutex_->lock(); + + QOpenGLFunctions* f = context()->functions(); + //QOpenGLExtraFunctions* xf = context()->extraFunctions(); + + makeCurrent(); + + // clear to solid black + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); + + + // draw texture from render thread + + + QMatrix4x4 matrix; + + double widget_ar = (double(width()) / double(height())); + if (widget_ar > ar_) { + matrix.scale(ar_ / widget_ar, 1.0); + } else { + matrix.scale(1.0f, widget_ar / ar_); + } + + + f->glViewport(0, 0, width(), height()); + + f->glBindTexture(GL_TEXTURE_2D, texture_); + + olive::rendering::Blit(pipeline_.get(), true, matrix); + + f->glBindTexture(GL_TEXTURE_2D, 0); + + + + if (mutex_ != nullptr) mutex_->unlock(); + } + + if (show_fullscreen_msg_) { + QPainter p(this); + + QFont f = p.font(); + f.setPointSize(24); + p.setFont(f); + + QFontMetrics fm(f); + + QString fs_str = tr("Exit Fullscreen"); + + p.setPen(Qt::white); + p.setBrush(QColor(0, 0, 0, 128)); + + int text_width = fm.width(fs_str); + int text_x = (width()/2)-(text_width/2); + int text_y = fm.height()+fm.ascent(); + + int rect_padding = 8; + + 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.drawText(text_x, text_y, fs_str); + } +} + +void ViewerWindow::fullscreen_msg_timeout() { + fullscreen_msg_timer_.stop(); + if (show_fullscreen_msg_) { + show_fullscreen_msg_ = false; + update(); + } +} diff --git a/ui/viewerwindow.h b/ui/viewerwindow.h index 2f5dec7f7..49179c2cf 100644 --- a/ui/viewerwindow.h +++ b/ui/viewerwindow.h @@ -1,63 +1,63 @@ -/*** - - 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 VIEWERWINDOW_H -#define VIEWERWINDOW_H - -#include -#include -#include -#include -#include - -#include "rendering/qopenglshaderprogramptr.h" - -class ViewerWindow : public QOpenGLWidget { - Q_OBJECT -public: - ViewerWindow(QWidget *parent); - void set_texture(GLuint t, double iar, QMutex *imutex); -protected: - virtual void showEvent(QShowEvent*) override; - virtual void keyPressEvent(QKeyEvent*) override; - 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_; - 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_; -private slots: - void fullscreen_msg_timeout(); -}; - -#endif // VIEWERWINDOW_H +/*** + + 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 VIEWERWINDOW_H +#define VIEWERWINDOW_H + +#include +#include +#include +#include +#include + +#include "rendering/qopenglshaderprogramptr.h" + +class ViewerWindow : public QOpenGLWidget { + Q_OBJECT +public: + ViewerWindow(QWidget *parent); + void set_texture(GLuint t, double iar, QMutex *imutex); +protected: + virtual void showEvent(QShowEvent*) override; + virtual void keyPressEvent(QKeyEvent*) override; + 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_; + 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_; +private slots: + void fullscreen_msg_timeout(); +}; + +#endif // VIEWERWINDOW_H diff --git a/undo/comboaction.cpp b/undo/comboaction.cpp index 16035fb01..0c93e61d3 100644 --- a/undo/comboaction.cpp +++ b/undo/comboaction.cpp @@ -1,60 +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 "comboaction.h" - -ComboAction::ComboAction() {} - -ComboAction::~ComboAction() { - for (int i=0;i=0;i--) { - commands.at(i)->undo(); - } - for (int i=0;iundo(); - } -} - -void ComboAction::redo() { - for (int i=0;iredo(); - } - for (int i=0;iredo(); - } -} - -void ComboAction::append(QUndoCommand* u) { - commands.append(u); -} - -void ComboAction::appendPost(QUndoCommand* u) { - post_commands.append(u); -} - -bool ComboAction::hasActions() -{ - return commands.size() > 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 "comboaction.h" + +ComboAction::ComboAction() {} + +ComboAction::~ComboAction() { + for (int i=0;i=0;i--) { + commands.at(i)->undo(); + } + for (int i=0;iundo(); + } +} + +void ComboAction::redo() { + for (int i=0;iredo(); + } + for (int i=0;iredo(); + } +} + +void ComboAction::append(QUndoCommand* u) { + commands.append(u); +} + +void ComboAction::appendPost(QUndoCommand* u) { + post_commands.append(u); +} + +bool ComboAction::hasActions() +{ + return commands.size() > 0; +} diff --git a/undo/comboaction.h b/undo/comboaction.h index d5ed2e7af..1a845165c 100644 --- a/undo/comboaction.h +++ b/undo/comboaction.h @@ -1,116 +1,116 @@ -/*** - - 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 - -#include -#include - -/** - * @brief The ComboAction class - * - * The Undo/Redo system works by stacking an action that knows how to "do" and also "undo" itself. As Olive is - * a very complex program, there are many actions that can in one "user action". For example, moving a clip over - * another will delete the clip under it, which is at least two actions that need to be undone if the user clicks - * undo, however the user only (knowingly) did one thing and would find it confusing if this one user action required - * to undos to complete undo. - * - * To address this, ComboAction is an undo action that simply compiles several possible actions into one, doing them - * all on every redo, and undoing them all on every undo. - */ -class ComboAction : public QUndoCommand { -public: - /** - * @brief ComboAction Constructor. Currently empty. - */ - ComboAction(); - - /** - * @brief ~ComboAction Destructor. Cleans up all QUndoCommand classes that have been added to it. - */ - virtual ~ComboAction() override; - - /** - * @brief Undo Function - * - * Called by the QUndoStack to undo. - * - * Calls QUndoCommand::undo() on all QUndoCommand objects added by append() - * in REVERSE order to how they were added. Then calls QUndoCommand::redo() on every action added by appendPost() - * in the order they were added (not in reverse). - */ - virtual void undo() override; - - /** - * @brief Redo Function - * - * Called by the QUndoStack to redo. - * - * Calls QUndoCommand::redo() on all QUndoCommand objects added by append() - * in the order they were added. Then calls QUndoCommand::redo() on every action added by appendPost() in - * the order they were added. - */ - virtual void redo() override; - - /** - * @brief Add an undo action - * - * Add an action to be done/undone. ComboAction takes ownership of this QUndoCommand and will delete it when - * it is deleted. - * - * @param u - * - * The QUndoCommand to add. - */ - void append(QUndoCommand* u); - - /** - * @brief Add a post-undo PostAction - * - * Sometimes the results of all the actions require another function to be called (e.g. repainting the Viewer). - * QUndoCommand objects added by appendPost() will run after EVERY QUndoCommand added by append() has been run. - * - * @param u - * - * The PostAction to add - */ - void appendPost(QUndoCommand* u); - - /** - * @brief Returns whether actions have been appended or not - * - * @return **TRUE** if actions have been appended, **FALSE** if not. - */ - bool hasActions(); - -private: - /** - * @brief Internal array of QUndoCommand objects - */ - QVector commands; - - /** - * @brief Internal array of PostAction objects - */ - QVector post_commands; -}; - -#endif // COMBOACTION_H +/*** + + 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 + +#include +#include + +/** + * @brief The ComboAction class + * + * The Undo/Redo system works by stacking an action that knows how to "do" and also "undo" itself. As Olive is + * a very complex program, there are many actions that can in one "user action". For example, moving a clip over + * another will delete the clip under it, which is at least two actions that need to be undone if the user clicks + * undo, however the user only (knowingly) did one thing and would find it confusing if this one user action required + * to undos to complete undo. + * + * To address this, ComboAction is an undo action that simply compiles several possible actions into one, doing them + * all on every redo, and undoing them all on every undo. + */ +class ComboAction : public QUndoCommand { +public: + /** + * @brief ComboAction Constructor. Currently empty. + */ + ComboAction(); + + /** + * @brief ~ComboAction Destructor. Cleans up all QUndoCommand classes that have been added to it. + */ + virtual ~ComboAction() override; + + /** + * @brief Undo Function + * + * Called by the QUndoStack to undo. + * + * Calls QUndoCommand::undo() on all QUndoCommand objects added by append() + * in REVERSE order to how they were added. Then calls QUndoCommand::redo() on every action added by appendPost() + * in the order they were added (not in reverse). + */ + virtual void undo() override; + + /** + * @brief Redo Function + * + * Called by the QUndoStack to redo. + * + * Calls QUndoCommand::redo() on all QUndoCommand objects added by append() + * in the order they were added. Then calls QUndoCommand::redo() on every action added by appendPost() in + * the order they were added. + */ + virtual void redo() override; + + /** + * @brief Add an undo action + * + * Add an action to be done/undone. ComboAction takes ownership of this QUndoCommand and will delete it when + * it is deleted. + * + * @param u + * + * The QUndoCommand to add. + */ + void append(QUndoCommand* u); + + /** + * @brief Add a post-undo PostAction + * + * Sometimes the results of all the actions require another function to be called (e.g. repainting the Viewer). + * QUndoCommand objects added by appendPost() will run after EVERY QUndoCommand added by append() has been run. + * + * @param u + * + * The PostAction to add + */ + void appendPost(QUndoCommand* u); + + /** + * @brief Returns whether actions have been appended or not + * + * @return **TRUE** if actions have been appended, **FALSE** if not. + */ + bool hasActions(); + +private: + /** + * @brief Internal array of QUndoCommand objects + */ + QVector commands; + + /** + * @brief Internal array of PostAction objects + */ + QVector post_commands; +}; + +#endif // COMBOACTION_H diff --git a/undo/undostack.cpp b/undo/undostack.cpp index 7fa229777..3fa41abdc 100644 --- a/undo/undostack.cpp +++ b/undo/undostack.cpp @@ -1,23 +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::undo_stack; +/*** + + 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::undo_stack; diff --git a/undo/undostack.h b/undo/undostack.h index 35bc92584..a2312642e 100644 --- a/undo/undostack.h +++ b/undo/undostack.h @@ -1,33 +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 . - -***/ - -#ifndef UNDOSTACK_H -#define UNDOSTACK_H - -#include - -namespace olive { -/** - * @brief Global undo stack object - */ -extern QUndoStack undo_stack; -} - -#endif // UNDOSTACK_H +/*** + + 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 + +#include + +namespace olive { +/** + * @brief Global undo stack object + */ +extern QUndoStack undo_stack; +} + +#endif // UNDOSTACK_H