Files
oak-editor/ui/effectui.cpp
T

359 lines
10 KiB
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 <http://www.gnu.org/licenses/>.
***/
#include "effectui.h"
#include <QPoint>
#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<Transition*>(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;i<e->ParameterCount();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;j<row->FieldCount();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;i<effect_->ParameterCount();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;j<row->FieldCount();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;j<effect->ParameterCount();j++) {
NodeIO* row = effect->Parameter(j);
for (int k=0;k<row->FieldCount();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<additional_effects_.size();i++) {
EffectField* previous_field = 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;i<additional_effects_.size();i++) {
if (additional_effects_.at(i)->parent_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;i<additional_effects_.size();i++) {
if (move_up_action != nullptr) {
connect(move_up_action, SIGNAL(triggered(bool)), additional_effects_.at(i), SLOT(move_up()));
}
if (move_down_action != nullptr) {
connect(move_down_action, SIGNAL(triggered(bool)), additional_effects_.at(i), SLOT(move_down()));
}
connect(delete_action, SIGNAL(triggered(bool)), additional_effects_.at(i), SLOT(delete_self()));
}
menu.addSeparator();
menu.addAction(tr("Load Settings From File"), GetEffect(), SLOT(load_from_file()));
menu.addAction(tr("Save Settings to File"), GetEffect(), SLOT(save_to_file()));
menu.exec(title_bar->mapToGlobal(pos));
}
}