First commit of brand new rewrite

This commit is contained in:
itsmattkc
2018-05-30 03:24:12 +10:00
commit 244c092591
86 changed files with 8016 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
#include "collapsiblewidget.h"
#include <QDebug>
#include <QLabel>
#include <QCheckBox>
#include <QLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
CollapsibleWidget::CollapsibleWidget(QWidget* parent) : QWidget(parent)
{
layout = new QVBoxLayout(this);
layout->setMargin(0);
title_bar = new QHBoxLayout();
title_bar->setMargin(0);
enabled_check = new QCheckBox();
enabled_check->setChecked(true);
header = new QLabel();
collapse_button = new QPushButton("-");
collapse_button->setMaximumWidth(25);
setText("<untitled>");
title_bar->addWidget(collapse_button);
title_bar->addWidget(enabled_check);
title_bar->addWidget(header);
title_bar->addStretch();
layout->addLayout(title_bar);
line = new QFrame();
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
layout->addWidget(line);
contents = NULL;
}
void CollapsibleWidget::setContents(QWidget* c) {
bool existing = (contents != NULL);
contents = c;
if (!existing) {
layout->addWidget(contents);
connect(enabled_check, SIGNAL(toggled(bool)), this, SLOT(on_enabled_change(bool)));
connect(collapse_button, SIGNAL(clicked()), this, SLOT(on_visible_change()));
}
}
void CollapsibleWidget::setText(const QString &s) {
header->setText(s);
}
void CollapsibleWidget::on_enabled_change(bool b) {
contents->setEnabled(b);
}
void CollapsibleWidget::on_visible_change() {
contents->setVisible(!contents->isVisible());
collapse_button->setText(contents->isVisible() ? "-" : "+");
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef COLLAPSIBLEWIDGET_H
#define COLLAPSIBLEWIDGET_H
#include <QWidget>
class QLabel;
class QCheckBox;
class QHBoxLayout;
class QVBoxLayout;
class QPushButton;
class QFrame;
class CollapsibleWidget : public QWidget
{
Q_OBJECT
public:
CollapsibleWidget(QWidget* parent = 0);
void setContents(QWidget* c);
void setText(const QString &);
private:
QLabel* header;
QCheckBox* enabled_check;
QHBoxLayout* title_bar;
QVBoxLayout* layout;
QPushButton* collapse_button;
QWidget* contents;
QFrame* line;
private slots:
void on_enabled_change(bool b);
void on_visible_change();
};
#endif // COLLAPSIBLEWIDGET_H
+9
View File
@@ -0,0 +1,9 @@
#include "playbutton.h"
PlayButton::PlayButton(QWidget* parent) : QPushButton(parent)
{
play_text = ">";
pause_text = "||";
setText(play_text);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef PLAYBUTTON_H
#define PLAYBUTTON_H
#include <QPushButton>
class PlayButton : public QPushButton
{
public:
PlayButton(QWidget* parent = 0);
private:
QString play_text;
QString pause_text;
};
#endif // PLAYBUTTON_H
+30
View File
@@ -0,0 +1,30 @@
#include "sourcetable.h"
#include "panels/project.h"
#include "io/media.h"
#include "panels/timeline.h"
#include "panels/viewer.h"
#include "panels/panels.h"
#include <QDragEnterEvent>
SourceTable::SourceTable(QWidget* parent) : QTreeWidget(parent) {
this->sortByColumn(0, Qt::AscendingOrder);
}
void SourceTable::mouseDoubleClickEvent(QMouseEvent* )
{
if (selectedItems().count() == 0) {
panel_project->import_dialog();
} else if (selectedItems().count() == 1) {
Media* m = reinterpret_cast<Media*>(selectedItems().at(0)->data(0, Qt::UserRole + 1).value<quintptr>());
if (m->is_sequence) {
panel_timeline->set_sequence(m->sequence);
panel_viewer->set_sequence(m->sequence);
}
}
}
//void SourceTable::dragEnterEvent(QDragEnterEvent *event) {
// event->accept();
//}
+18
View File
@@ -0,0 +1,18 @@
#ifndef SOURCETABLE_H
#define SOURCETABLE_H
#include <QTreeWidget>
class Project;
class SourceTable : public QTreeWidget
{
public:
SourceTable(QWidget* parent = 0);
protected:
void mouseDoubleClickEvent(QMouseEvent *event) override;
// void dragEnterEvent(QDragEnterEvent *event) override;
private:
};
#endif // SOURCETABLE_H
+15
View File
@@ -0,0 +1,15 @@
#ifndef TIMELINETOOLS_H
#define TIMELINETOOLS_H
enum TimelineTools {
TIMELINE_TOOL_POINTER,
TIMELINE_TOOL_EDIT,
TIMELINE_TOOL_RAZOR,
TIMELINE_TOOL_RIPPLE,
TIMELINE_TOOL_ROLLING,
TIMELINE_TOOL_SLIP,
TIMELINE_TOOL_HAND,
TIMELINE_TOOL_ZOOM
};
#endif // TIMELINETOOLS_H
+799
View File
@@ -0,0 +1,799 @@
#include "timelinewidget.h"
#include "panels/panels.h"
#include "io/config.h"
#include "project/sequence.h"
#include "project/clip.h"
#include "panels/project.h"
#include "panels/timeline.h"
#include "io/media.h"
#include "ui/sourcetable.h"
#include "panels/effectcontrols.h"
#include "effects/effects.h"
#include <QPainter>
#include <QColor>
#include <QDebug>
#include <QMouseEvent>
#include <QObject>
#include <QVariant>
#include <QPointF>
TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent)
{
bottom_align = false;
setMouseTracking(true);
track_height = 40;
clip_pixmap = NULL;
setAcceptDrops(true);
}
bool same_sign(int a, int b) {
return (a < 0) == (b < 0);
}
void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) {
if (static_cast<SourceTable*>(event->source()) == panel_project->source_table) {
QPoint pos = event->pos();
event->accept();
QList<QTreeWidgetItem*> items = panel_project->source_table->selectedItems();
long entry_point = getFrameFromScreenPoint(pos.x(), false);
panel_timeline->drag_frame_start = entry_point + getFrameFromScreenPoint(50, false);
panel_timeline->drag_track_start = (bottom_align) ? -1 : 0;
for (int i=0;i<items.size();i++) {
bool ignore_infinite_length = false;
Media* m = reinterpret_cast<Media*>(items.at(i)->data(0, Qt::UserRole + 1).value<quintptr>());
long duration = m->get_length_in_frames(panel_timeline->sequence->frame_rate);
Ghost g = {NULL, entry_point, entry_point + duration};
g.media = m;
g.clip_in = 0;
for (int j=0;j<m->audio_tracks.size();j++) {
g.track = j;
g.media_stream = &m->audio_tracks[j];
ignore_infinite_length = true;
panel_timeline->ghosts.append(g);
}
for (int j=0;j<m->video_tracks.size();j++) {
g.track = -1-j;
g.media_stream = &m->video_tracks[j];
if (m->video_tracks[j].infinite_length && !ignore_infinite_length) g.out = g.in + 100;
panel_timeline->ghosts.append(g);
}
entry_point += duration;
}
init_ghosts();
panel_timeline->importing = true;
}
}
void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) {
if (panel_timeline->importing) {
QPoint pos = event->pos();
update_ghosts(pos);
panel_timeline->repaint_timeline();
}
}
void TimelineWidget::dragLeaveEvent(QDragLeaveEvent *event) {
if (panel_timeline->importing) {
panel_timeline->ghosts.clear();
panel_timeline->importing = false;
panel_timeline->repaint_timeline();
}
}
void TimelineWidget::dropEvent(QDropEvent* event) {
if (panel_timeline->importing) {
event->accept();
for (int i=0;i<panel_timeline->ghosts.size();i++) {
const Ghost& g = panel_timeline->ghosts.at(i);
panel_timeline->sequence->delete_area(g.in, g.out, g.track);
Clip& c = panel_timeline->sequence->new_clip();
c.media = g.media;
c.media_stream = g.media_stream;
c.timeline_in = g.in;
c.timeline_out = g.out;
c.clip_in = g.clip_in;
c.color_r = 128;
c.color_g = 128;
c.color_b = 192;
c.sequence = panel_timeline->sequence;
c.track = g.track;
c.name = c.media->name;
if (c.track < 0) {
// add default video effects
c.effects.append(new TransformEffect(&c));
} else {
// add default audio effects
c.effects.append(new VolumeEffect(&c));
c.effects.append(new PanEffect(&c));
}
}
panel_timeline->ghosts.clear();
panel_timeline->importing = false;
panel_timeline->redraw_all_clips();
}
}
void TimelineWidget::mousePressEvent(QMouseEvent *event) {
QPoint pos = event->pos();
panel_timeline->drag_frame_start = getFrameFromScreenPoint(pos.x(), false);
panel_timeline->drag_track_start = getTrackFromScreenPoint(pos.y());
int clip_index = panel_timeline->trim_target;
if (clip_index == -1) clip_index = getClipIndexFromCoords(panel_timeline->drag_frame_start, panel_timeline->drag_track_start);
switch (panel_timeline->tool) {
case TIMELINE_TOOL_POINTER:
case TIMELINE_TOOL_RIPPLE:
{
if (clip_index >= 0) {
if (panel_timeline->is_clip_selected(clip_index)) {
// TODO if shift is down, deselect it
} else {
// if "shift" is not down
if (!(event->modifiers() & Qt::ShiftModifier)) {
panel_timeline->selections.clear();
}
Clip& clip = panel_timeline->sequence->get_clip(clip_index);
panel_timeline->selections.append({clip.timeline_in, clip.timeline_out, clip.track});
}
panel_timeline->moving_init = true;
} else {
panel_timeline->selections.clear();
}
panel_timeline->repaint_timeline();
}
break;
case TIMELINE_TOOL_EDIT:
panel_timeline->seek(panel_timeline->drag_frame_start);
panel_timeline->selecting = true;
panel_timeline->repaint_timeline();
break;
case TIMELINE_TOOL_RAZOR:
{
if (clip_index >= 0) {
panel_timeline->sequence->split_clip(clip_index, panel_timeline->drag_frame_start);
}
panel_timeline->splitting = true;
panel_timeline->redraw_all_clips();
}
break;
}
}
void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
if (panel_timeline->moving_proc) {
if (event->modifiers() & Qt::AltModifier) { // if holding alt, duplicate rather than move
// duplicate clips
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
Clip& c = panel_timeline->sequence->insert_clip(*g.clip);
c.timeline_in = g.in;
c.timeline_out = g.out;
c.track = g.track;
c.undeletable = true;
// step 2 - delete anything that exists in area that clip is moving to
panel_timeline->sequence->delete_area(g.in, g.out, g.track);
c.undeletable = false;
}
} else {
// move clips
// TODO can we do this better than 3 consecutive for loops?
for (int i=0;i<panel_timeline->ghosts.size();i++) {
// step 1 - set clips that are moving to "undeletable" (to avoid step 2 deleting any part of them)
panel_timeline->ghosts[i].clip->undeletable = true;
}
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
if (panel_timeline->tool == TIMELINE_TOOL_POINTER) {
// step 2 - delete anything that exists in area that clip is moving to
// note: ripples are non-destructive so this is pointer-tool exclusive
panel_timeline->sequence->delete_area(g.in, g.out, g.track);
}
// step 3 - move clips
g.clip->timeline_in = g.in;
g.clip->timeline_out = g.out;
g.clip->track = g.track;
g.clip->clip_in = g.clip_in;
}
for (int i=0;i<panel_timeline->ghosts.size();i++) {
// step 4 - set clips back to deletable
panel_timeline->ghosts[i].clip->undeletable = false;
}
}
// ripple ops
if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
long ripple_length, ripple_point;
if (panel_timeline->trim_in) {
ripple_length = panel_timeline->ghosts.at(0).old_in - panel_timeline->ghosts.at(0).in;
ripple_point = (ripple_length < 0) ? panel_timeline->ghosts.at(0).old_in : panel_timeline->ghosts.at(0).in;
} else {
ripple_length = panel_timeline->ghosts.at(0).old_out - panel_timeline->ghosts.at(0).out;
ripple_point = (ripple_length < 0) ? panel_timeline->ghosts.at(0).old_out : panel_timeline->ghosts.at(0).out;
}
for (int i=0;i<panel_timeline->ghosts.size();i++) {
long comp_point;
if (panel_timeline->trim_in) {
comp_point = (ripple_length < 0) ? panel_timeline->ghosts.at(i).old_in : panel_timeline->ghosts.at(i).in;
} else {
comp_point = (ripple_length < 0) ? panel_timeline->ghosts.at(i).old_out : panel_timeline->ghosts.at(i).out;
}
if (ripple_point > comp_point) ripple_point = comp_point;
}
if (panel_timeline->trim_in) {
panel_timeline->ripple(ripple_point, ripple_length);
} else {
panel_timeline->ripple(ripple_point, -ripple_length);
}
}
panel_timeline->redraw_all_clips();
}
// destroy all ghosts
panel_timeline->ghosts.clear();
panel_timeline->selecting = false;
panel_timeline->moving_proc = false;
panel_timeline->moving_init = false;
panel_timeline->splitting = false;
pre_clips.clear();
post_clips.clear();
// find out how many clips are selected
bool single_select = false;
int selected_clip = 0;
for (int i=0;i<panel_timeline->sequence->clip_count();i++) {
if (panel_timeline->is_clip_selected(i)) {
if (!single_select) {
// found ONE selected clip
selected_clip = i;
single_select = true;
} else {
// more than one clip is selected
single_select = false;
break;
}
}
}
if (single_select) {
panel_effect_controls->set_clip(&panel_timeline->sequence->get_clip(selected_clip));
} else {
panel_effect_controls->set_clip(NULL);
}
}
void TimelineWidget::init_ghosts() {
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
g.old_in = g.in;
g.old_out = g.out;
g.old_track = g.track;
g.old_clip_in = g.clip_in;
if (panel_timeline->trim_target > -1) {
// used for trim ops
g.ghost_length = g.old_out - g.old_in;
g.media_length = g.clip->media->get_length_in_frames(panel_timeline->sequence->frame_rate);
}
}
for (int i=0;i<panel_timeline->selections.size();i++) {
Selection& s = panel_timeline->selections[i];
s.old_in = s.in;
s.old_out = s.out;
s.old_track = s.track;
}
}
void TimelineWidget::update_ghosts(QPoint& mouse_pos) {
int mouse_track = getTrackFromScreenPoint(mouse_pos.y());
long frame_diff = getFrameFromScreenPoint(mouse_pos.x(), false) - panel_timeline->drag_frame_start;
int track_diff = mouse_track - panel_timeline->drag_track_start;
long validator;
if (panel_timeline->trim_target > -1) {
// trim ops
// validate ghosts
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
if (panel_timeline->trim_in) {
// prevent clip 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
validator = g.old_in + frame_diff;
if (validator < 0) frame_diff -= validator;
if (!g.clip->media_stream->infinite_length) {
// prevent clip_in from going below 0
validator = g.old_clip_in + frame_diff;
if (validator < 0) frame_diff -= validator;
}
// ripple ops
if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
for (int j=0;j<post_clips.size();j++) {
// prevent any rippled clip from going below 0
Clip* post = post_clips.at(j);
validator = post->timeline_in - frame_diff;
if (validator < 0) frame_diff += validator;
// prevent any post-clips colliding with pre-clips
for (int k=0;k<pre_clips.size();k++) {
Clip* pre = pre_clips.at(k);
if (pre->track == post->track) {
validator = post->timeline_in - frame_diff - pre->timeline_out;
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);
if (!g.clip->media_stream->infinite_length) {
// prevent clip length exceeding media length
validator = g.ghost_length + frame_diff;
if (validator > g.media_length) frame_diff -= validator - g.media_length;
}
// ripple ops
if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
for (int j=0;j<post_clips.size();j++) {
Clip* post = post_clips.at(j);
// prevent any post-clips colliding with pre-clips
for (int k=0;k<pre_clips.size();k++) {
Clip* pre = pre_clips.at(k);
if (pre->track == post->track) {
validator = post->timeline_in + frame_diff - pre->timeline_out;
if (validator < 0) frame_diff -= validator;
}
}
}
}
}
}
// resize ghosts
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
if (panel_timeline->trim_in) {
g.in = g.old_in + frame_diff;
g.clip_in = g.old_clip_in + frame_diff;
} else {
g.out = g.old_out + frame_diff;
}
}
// resize selections
for (int i=0;i<panel_timeline->selections.size();i++) {
Selection& s = panel_timeline->selections[i];
if (panel_timeline->trim_in) {
s.in = s.old_in + frame_diff;
} else {
s.out = s.old_out + frame_diff;
}
}
} else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing) { // only move clips on pointer (not ripple or rolling)
// validate ghosts
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
// prevent clips from moving below 0 on the timeline
validator = g.old_in + frame_diff;
if (validator < 0) frame_diff -= validator;
// 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++;
}
}
}
}
// move ghosts
for (int i=0;i<panel_timeline->ghosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
g.in = g.old_in + frame_diff;
g.out = g.old_out + frame_diff;
g.track = g.old_track;
if (panel_timeline->importing) {
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;
}
}
// move selections
if (!panel_timeline->importing) {
for (int i=0;i<panel_timeline->selections.size();i++) {
Selection& s = panel_timeline->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;
}
}
}
}
}
void TimelineWidget::mouseMoveEvent(QMouseEvent *event) {
if (panel_timeline->selecting) {
QPoint pos = event->pos();
int current_selection_track = getTrackFromScreenPoint(pos.y());
long current_selection_frame = getFrameFromScreenPoint(pos.x(), false);;
int selection_count = 1 + qMax(current_selection_track, panel_timeline->drag_track_start) - qMin(current_selection_track, panel_timeline->drag_track_start);
if (panel_timeline->selections.size() != selection_count) {
panel_timeline->selections.resize(selection_count);
}
int minimum_selection_track = qMin(current_selection_track, panel_timeline->drag_track_start);
for (int i=0;i<selection_count;i++) {
Selection* s = &panel_timeline->selections[i];
s->track = minimum_selection_track + i;
long in = panel_timeline->drag_frame_start;
long out = current_selection_frame;
s->in = qMin(in, out);
s->out = qMax(in, out);
}
panel_timeline->playhead = qMin(panel_timeline->drag_frame_start, current_selection_frame);
panel_timeline->repaint_timeline();
} else if (panel_timeline->moving_init) {
QPoint pos = event->pos();
if (panel_timeline->moving_proc) {
update_ghosts(pos);
} else {
// set up movement
// create ghosts
for (int i=0;i<panel_timeline->sequence->clip_count();i++) {
if (panel_timeline->is_clip_selected(i)) {
Clip& c = panel_timeline->sequence->get_clip(i);
panel_timeline->ghosts.append({&c, c.timeline_in, c.timeline_out, c.track, c.clip_in});
}
}
// ripple edit prep
if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
for (int i=0;i<panel_timeline->ghosts.size();i++) {
// get clips before and after ripple point
for (int j=0;j<panel_timeline->sequence->clip_count();j++) {
// don't cache any currently selected clips
Clip* c = panel_timeline->ghosts.at(i).clip;
Clip& cc = panel_timeline->sequence->get_clip(j);
bool is_selected = false;
for (int k=0;k<panel_timeline->ghosts.size();k++) {
if (panel_timeline->ghosts.at(k).clip == &cc) {
is_selected = true;
break;
}
}
if (!is_selected) {
if (cc.timeline_in < c->timeline_in) {
// add clip to pre-cache UNLESS there is already a clip on that track closer to the ripple point
bool found = false;
for (int k=0;k<pre_clips.size();k++) {
Clip* ccc = pre_clips.at(k);
if (ccc->track == cc.track) {
if (ccc->timeline_in < cc.timeline_in) {
// clip is closer to ripple point than the one in cache, replace it
ccc = &cc;
}
found = true;
}
}
if (!found) {
// no clip from that track in the cache, add it
pre_clips.append(&cc);
}
} else {
// add clip to post-cache UNLESS there is already a clip on that track closer to the ripple point
bool found = false;
for (int k=0;k<post_clips.size();k++) {
Clip* ccc = post_clips.at(k);
if (ccc->track == cc.track) {
if (ccc->timeline_in > cc.timeline_in) {
// clip is closer to ripple point than the one in cache, replace it
ccc = &cc;
}
found = true;
}
}
if (!found) {
// no clip from that track in the cache, add it
post_clips.append(&cc);
}
}
}
}
}
// debug code - print the information we got
qDebug() << "found" << pre_clips.size() << "preceding clips and" << post_clips.size() << "following";
}
init_ghosts();
panel_timeline->moving_proc = true;
}
panel_timeline->repaint_timeline();
} else if (panel_timeline->splitting) {
QPoint pos = event->pos();
int track = getTrackFromScreenPoint(pos.y());
bool repaint = false;
for (int i=0;i<panel_timeline->sequence->clip_count();i++) {
if (panel_timeline->sequence->get_clip(i).track == track) {
panel_timeline->sequence->split_clip(i, panel_timeline->drag_frame_start);
repaint = true;
}
}
// redraw clips since we changed them
if (repaint) panel_timeline->redraw_all_clips();
} else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
QPoint pos = event->pos();
int lim = 5;
int mouse_track = getTrackFromScreenPoint(pos.y());
long mouse_frame_lower = getFrameFromScreenPoint(pos.x()-lim, false)-1;
long mouse_frame_upper = getFrameFromScreenPoint(pos.x()+lim, false)+1;
bool found = false;
for (int i=0;i<panel_timeline->sequence->clip_count();i++) {
Clip& c = panel_timeline->sequence->get_clip(i);
if (c.track == mouse_track) {
if (c.timeline_in > mouse_frame_lower && c.timeline_in < mouse_frame_upper) {
panel_timeline->trim_target = i;
panel_timeline->trim_in = true;
found = true;
break;
} else if (c.timeline_out > mouse_frame_lower && c.timeline_out < mouse_frame_upper) {
panel_timeline->trim_target = i;
panel_timeline->trim_in = false;
found = true;
break;
}
}
}
if (found) {
setCursor(Qt::SizeHorCursor);
} else {
unsetCursor();
panel_timeline->trim_target = -1;
}
} else if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) {
// redraw because we have a cursor
panel_timeline->repaint_timeline();
}
}
int color_brightness(int r, int g, int b) {
return (0.2126*r + 0.7152*g + 0.0722*b);
}
void TimelineWidget::redraw_clips() {
// Draw clips
if (clip_pixmap != NULL) delete clip_pixmap;
int panel_width = getScreenPointFromFrame(panel_timeline->sequence->getEndFrame()) + 100;
setMinimumWidth(panel_width);
clip_pixmap = new QPixmap(panel_width, height());
clip_pixmap->fill(Qt::transparent);
QPainter clip_painter(clip_pixmap);
int video_track_limit = 0;
int audio_track_limit = 0;
for (int i=0;i<panel_timeline->sequence->clip_count();i++) {
Clip& clip = panel_timeline->sequence->get_clip(i);
if (is_track_visible(clip.track)) {
if (clip.track < 0 && clip.track < video_track_limit) { // video clip
video_track_limit = clip.track;
} else if (clip.track > audio_track_limit) {
audio_track_limit = clip.track;
}
QRect clip_rect(getScreenPointFromFrame(clip.timeline_in), getScreenPointFromTrack(clip.track), clip.getLength() * panel_timeline->zoom, track_height);
clip_painter.fillRect(clip_rect, QColor(clip.color_r, clip.color_g, clip.color_b));
clip_painter.setPen(Qt::white);
clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.topLeft());
clip_painter.drawLine(clip_rect.topLeft(), clip_rect.topRight());
clip_painter.setPen(QColor(0, 0, 0, 128));
clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.bottomRight());
clip_painter.drawLine(clip_rect.bottomRight(), clip_rect.topRight());
if (color_brightness(clip.color_r, clip.color_g, clip.color_b) > 160) {
clip_painter.setPen(Qt::black);
} else {
clip_painter.setPen(Qt::white);
}
QRect text_rect(clip_rect.left() + CLIP_TEXT_PADDING, clip_rect.top() + CLIP_TEXT_PADDING, clip_rect.width() - CLIP_TEXT_PADDING - CLIP_TEXT_PADDING, clip_rect.height() - CLIP_TEXT_PADDING - CLIP_TEXT_PADDING);
clip_painter.drawText(text_rect, 0, clip.name, &text_rect);
}
}
// Draw track lines
if (show_track_lines) {
clip_painter.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;
clip_painter.drawLine(0, line_y, rect().width(), line_y);
}
} else {
// only draw lines for audio tracks
for (int i=0;i<audio_track_limit;i++) {
int line_y = getScreenPointFromTrack(i) + track_height;
clip_painter.drawLine(0, line_y, rect().width(), line_y);
}
}
}
update();
}
void TimelineWidget::paintEvent(QPaintEvent*) {
if (panel_timeline->sequence != NULL) {
QPainter p(this);
if (clip_pixmap != NULL) p.drawPixmap(0, 0, minimumWidth(), height(), *clip_pixmap);
// Draw selections
for (int i=0;i<panel_timeline->selections.size();i++) {
const Selection& s = panel_timeline->selections.at(i);
if (is_track_visible(s.track)) {
int selection_y = getScreenPointFromTrack(s.track);
int selection_x = getScreenPointFromFrame(s.in);
p.fillRect(selection_x, selection_y, getScreenPointFromFrame(s.out) - selection_x, track_height, QColor(0, 0, 0, 64));
}
}
// Draw ghosts
for (int i=0;i<panel_timeline->ghosts.size();i++) {
const Ghost& g = panel_timeline->ghosts.at(i);
if (is_track_visible(g.track)) {
int ghost_x = getScreenPointFromFrame(g.in);
int ghost_y = getScreenPointFromTrack(g.track);
int ghost_width = getScreenPointFromFrame(g.out - g.in) - 1;
int ghost_height = track_height - 1;
p.setPen(QColor(255, 255, 0));
for (int j=0;j<GHOST_THICKNESS;j++) {
p.drawRect(ghost_x+j, ghost_y+j, ghost_width-j-j, ghost_height-j-j);
}
}
}
// Draw edit cursor
if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) {
QPoint mouse_pos = mapFromGlobal(QCursor::pos());
int track = getTrackFromScreenPoint(mouse_pos.y());
if (is_track_visible(track)) {
int cursor_x = getScreenPointFromFrame(getFrameFromScreenPoint(mouse_pos.x(), false));
int cursor_y = getScreenPointFromTrack(track);
p.setPen(Qt::gray);
p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + track_height);
}
}
// Draw playhead
p.setPen(Qt::red);
int playhead_x = getScreenPointFromFrame(panel_timeline->playhead);
p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom());
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);
}
}
bool TimelineWidget::is_track_visible(int track) {
return ((bottom_align && track < 0) || (!bottom_align && track >= 0));
}
// **************************************
// screen point <-> frame/track functions
// **************************************
long TimelineWidget::getFrameFromScreenPoint(int x, bool f) {
float div = (float) x / panel_timeline->zoom;
if (div < 0) {
return 0;
}
if (f) {
return floor(div);
} else {
return round(div);
}
}
int TimelineWidget::getTrackFromScreenPoint(int y) {
if (bottom_align) {
y -= rect().bottom();
if (show_track_lines) y -= 1;
} else {
y -= 1;
}
int temp_track_height = track_height;
if (show_track_lines) temp_track_height--;
return (int)floor((float) (y)/ (float) temp_track_height);
}
int TimelineWidget::getScreenPointFromFrame(long frame) {
return (int) round(frame*panel_timeline->zoom);
}
int TimelineWidget::getScreenPointFromTrack(int track) {
int temp_track_height = track_height;
if (show_track_lines) temp_track_height++;
int y = track * temp_track_height;
if (bottom_align) { // video track
y += rect().bottom();
if (show_track_lines) y += 1;
} else { // audio track
y += 1;
}
return y;
}
int TimelineWidget::getClipIndexFromCoords(long frame, int track) {
for (int i=0;i<panel_timeline->sequence->clip_count();i++) {
Clip& c = panel_timeline->sequence->get_clip(i);
if (c.track == track) {
if (frame >= c.timeline_in && frame < c.timeline_out) {
return i;
}
}
}
return -1;
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef TIMELINEWIDGET_H
#define TIMELINEWIDGET_H
#include <QWidget>
#include "timeline-tools.h"
#define GHOST_THICKNESS 2 // thiccccc
#define CLIP_TEXT_PADDING 3
struct Sequence;
struct Clip;
class Timeline;
class TimelineWidget : public QWidget
{
Q_OBJECT
public:
explicit TimelineWidget(QWidget *parent = nullptr);
bool bottom_align;
void redraw_clips();
protected:
void paintEvent(QPaintEvent*) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dropEvent(QDropEvent* event);
void dragMoveEvent(QDragMoveEvent *event);
private:
void init_ghosts();
void update_ghosts(QPoint& mouse_pos);
bool is_track_visible(int track);
long getFrameFromScreenPoint(int x, bool floor);
int getTrackFromScreenPoint(int y);
int getScreenPointFromFrame(long frame);
int getScreenPointFromTrack(int track);
int getClipIndexFromCoords(long frame, int track);
int track_height;
QList<Clip*> pre_clips;
QList<Clip*> post_clips;
QPixmap* clip_pixmap;
// QPixmap selection_pixmap;
signals:
public slots:
};
#endif // TIMELINEWIDGET_H
+40
View File
@@ -0,0 +1,40 @@
#include "viewercontainer.h"
#include <QWidget>
#include <QResizeEvent>
// enforces aspect ratio
ViewerContainer::ViewerContainer(QWidget *parent) : QWidget(parent)
{
child = NULL;
aspect_ratio = 1;
}
void ViewerContainer::adjust() {
if (child != NULL) {
QSize widget_size = size();
int widget_x = 0;
int widget_y = 0;
int widget_width = widget_size.width();
int widget_height = widget_size.height();
float widget_ar = (float) widget_width /(float) widget_height;
bool widget_is_larger_than_sequence = widget_ar > aspect_ratio;
if (widget_is_larger_than_sequence) {
widget_width = widget_height * aspect_ratio;
widget_x = (widget_size.width() / 2) - (widget_width / 2);
} else {
widget_height = widget_width / aspect_ratio;
widget_y = (widget_size.height() / 2) - (widget_height / 2);
}
child->move(widget_x, widget_y);
child->resize(widget_width, widget_height);
}
}
void ViewerContainer::resizeEvent(QResizeEvent *event) {
event->accept();
adjust();
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef VIEWERCONTAINER_H
#define VIEWERCONTAINER_H
#include <QWidget>
class ViewerContainer : public QWidget
{
Q_OBJECT
public:
explicit ViewerContainer(QWidget *parent = nullptr);
float aspect_ratio;
QWidget* child;
void adjust();
protected:
void resizeEvent(QResizeEvent *event) override;
signals:
public slots:
};
#endif // VIEWERCONTAINER_H
+157
View File
@@ -0,0 +1,157 @@
#include "viewerwidget.h"
#include "panels/panels.h"
#include "panels/viewer.h"
#include "panels/timeline.h"
#include "project/sequence.h"
#include "project/effect.h"
#include "playback/playback.h"
#include "playback/audio.h"
#include "io/media.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
}
ViewerWidget::ViewerWidget(QWidget *parent) : QOpenGLWidget(parent)
{
multithreaded = true;
QSurfaceFormat format;
format.setDepthBufferSize(24);
setFormat(format);
// error handler - retries after 200ms if we couldn't get the entire image
retry_timer.setInterval(200);
connect(&retry_timer, SIGNAL(timeout()), this, SLOT(retry()));
}
void ViewerWidget::retry() {
update();
}
void ViewerWidget::initializeGL() {
initializeOpenGLFunctions();
glClearColor(0, 0, 0, 1);
glMatrixMode(GL_PROJECTION);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
//void ViewerWidget::resizeGL(int w, int h)
//{
//}
int audio_bytes_written = 0;
void ViewerWidget::paintGL()
{
if (multithreaded) retry_timer.stop();
glClear(GL_COLOR_BUFFER_BIT);
long playhead = panel_timeline->playhead;
handle_media(panel_viewer->sequence, playhead, multithreaded);
texture_failed = false;
bool render_audio = (panel_timeline->playing);
if (render_audio && switch_audio_cache) {
reading_audio_cache_A = !reading_audio_cache_A;
audio_bytes_written = 0;
clear_cache(!reading_audio_cache_A, reading_audio_cache_A);
}
cc_lock.lock();
for (int i=0;i<current_clips.size();i++) {
Clip* c = current_clips.at(i);
if (!c->open) {
qDebug() << "[WARNING] Tried to display clip" << i << "but it's closed";
texture_failed = true;
} else if (is_clip_active(c, playhead)) {
if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
// start preparing cache
get_clip_frame(c, playhead);
if (c->texture == NULL) {
qDebug() << "[WARNING] Texture hasn't been created yet";
texture_failed = true;
} else if (playhead >= c->timeline_in) {
glLoadIdentity();
int half_width = c->sequence->width/2;
int half_height = c->sequence->height/2;
glOrtho(-half_width, half_width, half_height,- half_height, -1, 1);
int anchor_x = 0;
int anchor_y = 0;
// perform all transform effects
for (unsigned int j=0;j<c->effects.size();j++) {
c->effects.at(j)->process_gl(&anchor_x, &anchor_y);
}
int anchor_right = c->media_stream->video_width - anchor_x;
int anchor_bottom = c->media_stream->video_height - anchor_y;
c->texture->bind();
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(-anchor_x, -anchor_y);
glTexCoord2f(1.0, 0.0);
glVertex2f(anchor_right, -anchor_y);
glTexCoord2f(1.0, 1.0);
glVertex2f(anchor_right, anchor_bottom);
glTexCoord2f(0.0, 1.0);
glVertex2f(-anchor_x, anchor_bottom);
glEnd();
c->texture->release();
}
} else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
playhead >= c->timeline_in &&
render_audio &&
!c->reached_end &&
(switch_audio_cache || c->reset_audio)) {
// TODO doesn't new clips appearing in the timeline (they'll ALWAYS end up in the other cache with a 1/8 sec delay)
// cache audio
if (c->lock.tryLock()) {
// clip is not caching, start cache
c->lock.unlock();
cache_clip(c, playhead, !reading_audio_cache_A, reading_audio_cache_A, c->reset_audio);
}
}
}
}
if (render_audio) {
switch_audio_cache = false;
if (audio_cache_A != NULL && audio_bytes_written < audio_cache_size) {
// send cached/buffered audio to QIODevice/QAudioOutput
uint8_t* cache = reading_audio_cache_A ? audio_cache_A : audio_cache_B;
audio_bytes_written += audio_io_device->write((const char*) cache+audio_bytes_written, audio_cache_size-audio_bytes_written);
}
if (audio_bytes_written == audio_cache_size) {
// switch_cache
switch_audio_cache = true;
}
}
cc_lock.unlock();
if (texture_failed) {
if (multithreaded) {
retry_timer.start();
} else {
paintGL();
}
}
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef VIEWERWIDGET_H
#define VIEWERWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QMatrix4x4>
#include <QOpenGLTexture>
#include <QTimer>
class Viewer;
class ViewerWidget : public QOpenGLWidget, public QOpenGLFunctions
{
Q_OBJECT
public:
ViewerWidget(QWidget *parent = 0);
void initializeGL();
// void resizeGL(int w, int h);
void paintGL();
bool multithreaded;
private:
QTimer retry_timer;
private slots:
void retry();
};
#endif // VIEWERWIDGET_H