/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General 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);
}