ui: implemented keyboard controls for inserting/overwriting footage into the

timeline

Allows users to use the keyboard to place footage into the timeline rather than
forcing them to drag.
This commit is contained in:
itsmattkc
2020-03-03 20:03:50 +11:00
parent a9577d72ee
commit 9b73892c41
10 changed files with 300 additions and 176 deletions
+33
View File
@@ -25,6 +25,7 @@
#include "core.h"
#include "panel/footageviewer/footageviewer.h"
#include "panel/timeline/timeline.h"
#include "panel/panelmanager.h"
#include "project/item/sequence/sequence.h"
#include "widget/menu/menushared.h"
@@ -113,6 +114,24 @@ void ProjectPanel::DeselectAll()
explorer_->DeselectAll();
}
void ProjectPanel::Insert()
{
TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused<TimelinePanel>();
if (timeline) {
timeline->InsertFootageAtPlayhead(GetSelectedFootage());
}
}
void ProjectPanel::Overwrite()
{
TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused<TimelinePanel>();
if (timeline) {
timeline->OverwriteFootageAtPlayhead(GetSelectedFootage());
}
}
void ProjectPanel::Edit(Item* item)
{
explorer_->Edit(item);
@@ -156,3 +175,17 @@ void ProjectPanel::ProjectNameChanged()
SetSubtitle(project()->name());
}
}
QList<Footage *> ProjectPanel::GetSelectedFootage()
{
QList<Item*> items = SelectedItems();
QList<Footage*> footage;
foreach (Item* i, items) {
if (i->type() == Item::kFootage) {
footage.append(static_cast<Footage*>(i));
}
}
return footage;
}
+6
View File
@@ -46,6 +46,9 @@ public:
virtual void SelectAll() override;
virtual void DeselectAll() override;
virtual void Insert() override;
virtual void Overwrite() override;
public slots:
void Edit(Item *item);
@@ -60,6 +63,9 @@ private slots:
void ShowNewMenu();
void ProjectNameChanged();
QList<Footage*> GetSelectedFootage();
};
#endif // PROJECT_PANEL_H
+10
View File
@@ -89,6 +89,16 @@ void TimelinePanel::DecreaseTrackHeight()
static_cast<TimelineWidget*>(GetTimeBasedWidget())->DecreaseTrackHeight();
}
void TimelinePanel::InsertFootageAtPlayhead(const QList<Footage *> &footage)
{
static_cast<TimelineWidget*>(GetTimeBasedWidget())->InsertFootageAtPlayhead(footage);
}
void TimelinePanel::OverwriteFootageAtPlayhead(const QList<Footage *> &footage)
{
static_cast<TimelineWidget*>(GetTimeBasedWidget())->OverwriteFootageAtPlayhead(footage);
}
void TimelinePanel::Retranslate()
{
TimeBasedPanel::Retranslate();
+4
View File
@@ -55,6 +55,10 @@ public:
virtual void DecreaseTrackHeight() override;
void InsertFootageAtPlayhead(const QList<Footage *> &footage);
void OverwriteFootageAtPlayhead(const QList<Footage *> &footage);
protected:
virtual void Retranslate() override;
+6
View File
@@ -108,6 +108,12 @@ public:
virtual void DeleteSelected(){}
virtual void RippleDelete(){}
virtual void Insert(){}
virtual void Overwrite(){}
virtual void IncreaseTrackHeight(){}
virtual void DecreaseTrackHeight(){}
@@ -418,6 +418,11 @@ void TimelineWidget::DeleteSelected()
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void TimelineWidget::RippleDelete()
{
}
void TimelineWidget::IncreaseTrackHeight()
{
if (!GetConnectedNode()) {
@@ -446,6 +451,16 @@ void TimelineWidget::DecreaseTrackHeight()
}
}
void TimelineWidget::InsertFootageAtPlayhead(const QList<Footage*>& footage)
{
import_tool_->PlaceAt(footage, GetTime(), true);
}
void TimelineWidget::OverwriteFootageAtPlayhead(const QList<Footage *> &footage)
{
import_tool_->PlaceAt(footage, GetTime(), false);
}
QList<TimelineViewBlockItem *> TimelineWidget::GetSelectedBlocks()
{
QList<TimelineViewBlockItem *> list;
+13 -1
View File
@@ -49,10 +49,16 @@ public:
void DeleteSelected();
void RippleDelete();
void IncreaseTrackHeight();
void DecreaseTrackHeight();
void InsertFootageAtPlayhead(const QList<Footage *> &footage);
void OverwriteFootageAtPlayhead(const QList<Footage *> &footage);
QList<TimelineViewBlockItem*> GetSelectedBlocks();
signals:
@@ -217,9 +223,15 @@ private:
virtual void DragLeave(QDragLeaveEvent *event) override;
virtual void DragDrop(TimelineViewMouseEvent *event) override;
void PlaceAt(const QList<Footage*>& footage, const rational& start, bool insert);
private:
void FootageToGhosts(rational ghost_start, const QList<Footage*>& footage, const rational &dest_tb, const int &track_start);
void PrepGhosts(const rational &frame, const int &track_index);
void DropGhosts(bool insert);
QList<Footage*> dragged_footage_;
int import_pre_buffer_;
@@ -355,7 +367,7 @@ private:
QVector<Tool*> tools_;
Tool* import_tool_;
ImportTool* import_tool_;
Tool* active_tool_;
+193 -175
View File
@@ -99,12 +99,8 @@ void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event)
}
}
if (parent()->GetConnectedNode()) {
FootageToGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_),
dragged_footage_,
parent()->timebase(),
drag_start_.GetTrack().index());
}
PrepGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_),
drag_start_.GetTrack().index());
event->accept();
} else {
@@ -179,175 +175,7 @@ void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event)
{
if (!dragged_footage_.isEmpty()) {
QUndoCommand* command = new QUndoCommand();
NodeGraph* dst_graph = nullptr;
ViewerOutput* viewer_node = nullptr;
Sequence* open_sequence = nullptr;
if (parent()->GetConnectedNode()) {
viewer_node = parent()->GetConnectedNode();
dst_graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
} else {
// There's no active timeline here, ask the user what to do
DropWithoutSequenceBehavior behavior = static_cast<DropWithoutSequenceBehavior>(Config::Current()["DropWithoutSequenceBehavior"].toInt());
if (behavior == kDWSAsk) {
QCheckBox* dont_ask_again_box = new QCheckBox(tr("Don't ask me again"));
QMessageBox mbox(parent());
mbox.setIcon(QMessageBox::Question);
mbox.setWindowTitle(tr("No Active Sequence"));
mbox.setText(tr("No sequence is currently open. Would you like to create one?"));
mbox.setCheckBox(dont_ask_again_box);
QPushButton* auto_params_btn = mbox.addButton(tr("Automatically Detect Parameters From Footage"), QMessageBox::YesRole);
QPushButton* manual_params_btn = mbox.addButton(tr("Set Parameters Manually"), QMessageBox::NoRole);
mbox.addButton(QMessageBox::Cancel);
mbox.exec();
if (mbox.clickedButton() == auto_params_btn) {
behavior = kDWSAuto;
} else if (mbox.clickedButton() == manual_params_btn) {
behavior = kDWSManual;
} else {
behavior = kDWSDisable;
}
if (behavior != kDWSDisable && dont_ask_again_box->isChecked()) {
Config::Current()["DropWithoutSequenceBehavior"] = behavior;
}
}
if (behavior != kDWSDisable) {
Project* active_project = Core::instance()->GetActiveProject();
if (active_project) {
SequencePtr new_sequence = Core::instance()->CreateNewSequenceForProject(active_project);
new_sequence->set_default_parameters();
bool sequence_is_valid = true;
if (behavior == kDWSAuto) {
new_sequence->set_parameters_from_footage(dragged_footage_);
} else {
SequenceDialog sd(new_sequence.get(), SequenceDialog::kNew, parent());
sd.SetUndoable(false);
if (sd.exec() != QDialog::Accepted) {
sequence_is_valid = false;
}
}
if (sequence_is_valid) {
new_sequence->add_default_nodes();
new ProjectViewModel::AddItemCommand(Core::instance()->GetActiveProjectModel(),
Core::instance()->GetSelectedFolderInActiveProject(),
new_sequence,
command);
FootageToGhosts(0, dragged_footage_, new_sequence->video_params().time_base(), 0);
dst_graph = new_sequence.get();
viewer_node = new_sequence->viewer_output();
// Set this as the sequence to open
open_sequence = new_sequence.get();
}
}
}
}
if (dst_graph) {
QVector<Block*> block_items(parent()->ghost_items_.size());
// Check if we're inserting
if (event->GetModifiers() & Qt::ControlModifier) {
InsertGapsAtGhostDestination(parent()->ghost_items_, command);
}
for (int i=0;i<parent()->ghost_items_.size();i++) {
TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
ClipBlock* clip = new ClipBlock();
clip->set_length_and_media_out(ghost->Length());
clip->set_block_name(footage_stream->footage()->name());
new NodeAddCommand(dst_graph, clip, command);
switch (footage_stream->type()) {
case Stream::kVideo:
case Stream::kImage:
{
VideoInput* video_input = new VideoInput();
video_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, video_input, command);
new NodeEdgeAddCommand(video_input->output(), clip->texture_input(), command);
TransformDistort* transform = new TransformDistort();
new NodeAddCommand(dst_graph, transform, command);
new NodeEdgeAddCommand(transform->output(), video_input->matrix_input(), command);
//OpacityNode* opacity = new OpacityNode();
//NodeParam::ConnectEdge(opacity->texture_output(), clip->texture_input());
//NodeParam::ConnectEdge(media->texture_output(), opacity->texture_input());
break;
}
case Stream::kAudio:
{
AudioInput* audio_input = new AudioInput();
audio_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, audio_input, command);
VolumeNode* volume_node = new VolumeNode();
new NodeAddCommand(dst_graph, volume_node, command);
new NodeEdgeAddCommand(audio_input->output(), volume_node->samples_input(), command);
new NodeEdgeAddCommand(volume_node->output(), clip->texture_input(), command);
break;
}
default:
break;
}
new TrackPlaceBlockCommand(viewer_node->track_list(ghost->GetAdjustedTrack().type()),
ghost->GetAdjustedTrack().index(),
clip,
ghost->GetAdjustedIn(),
command);
block_items.replace(i, clip);
// Link any clips so far that share the same Footage with this one
for (int j=0;j<i;j++) {
StreamPtr footage_compare = parent()->ghost_items_.at(j)->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
if (footage_compare->footage() == footage_stream->footage()) {
Block::Link(block_items.at(j), clip);
}
}
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
parent()->ClearGhosts();
dragged_footage_.clear();
if (open_sequence) {
Sequence::Open(open_sequence);
}
DropGhosts(event->GetModifiers() & Qt::ControlModifier);
event->accept();
} else {
@@ -355,6 +183,13 @@ void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::ImportTool::PlaceAt(const QList<Footage *> &footage, const rational &start, bool insert)
{
dragged_footage_ = footage;
PrepGhosts(start, 0);
DropGhosts(insert);
}
void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QList<Footage *> &footage_list, const rational& dest_tb, const int& track_start)
{
foreach (Footage* footage, footage_list) {
@@ -413,3 +248,186 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
}
}
void TimelineWidget::ImportTool::PrepGhosts(const rational& frame, const int& track_index)
{
if (parent()->GetConnectedNode()) {
FootageToGhosts(frame,
dragged_footage_,
parent()->timebase(),
track_index);
}
}
void TimelineWidget::ImportTool::DropGhosts(bool insert)
{
QUndoCommand* command = new QUndoCommand();
NodeGraph* dst_graph = nullptr;
ViewerOutput* viewer_node = nullptr;
Sequence* open_sequence = nullptr;
if (parent()->GetConnectedNode()) {
viewer_node = parent()->GetConnectedNode();
dst_graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
} else {
// There's no active timeline here, ask the user what to do
DropWithoutSequenceBehavior behavior = static_cast<DropWithoutSequenceBehavior>(Config::Current()["DropWithoutSequenceBehavior"].toInt());
if (behavior == kDWSAsk) {
QCheckBox* dont_ask_again_box = new QCheckBox(tr("Don't ask me again"));
QMessageBox mbox(parent());
mbox.setIcon(QMessageBox::Question);
mbox.setWindowTitle(tr("No Active Sequence"));
mbox.setText(tr("No sequence is currently open. Would you like to create one?"));
mbox.setCheckBox(dont_ask_again_box);
QPushButton* auto_params_btn = mbox.addButton(tr("Automatically Detect Parameters From Footage"), QMessageBox::YesRole);
QPushButton* manual_params_btn = mbox.addButton(tr("Set Parameters Manually"), QMessageBox::NoRole);
mbox.addButton(QMessageBox::Cancel);
mbox.exec();
if (mbox.clickedButton() == auto_params_btn) {
behavior = kDWSAuto;
} else if (mbox.clickedButton() == manual_params_btn) {
behavior = kDWSManual;
} else {
behavior = kDWSDisable;
}
if (behavior != kDWSDisable && dont_ask_again_box->isChecked()) {
Config::Current()["DropWithoutSequenceBehavior"] = behavior;
}
}
if (behavior != kDWSDisable) {
Project* active_project = Core::instance()->GetActiveProject();
if (active_project) {
SequencePtr new_sequence = Core::instance()->CreateNewSequenceForProject(active_project);
new_sequence->set_default_parameters();
bool sequence_is_valid = true;
if (behavior == kDWSAuto) {
new_sequence->set_parameters_from_footage(dragged_footage_);
} else {
SequenceDialog sd(new_sequence.get(), SequenceDialog::kNew, parent());
sd.SetUndoable(false);
if (sd.exec() != QDialog::Accepted) {
sequence_is_valid = false;
}
}
if (sequence_is_valid) {
new_sequence->add_default_nodes();
new ProjectViewModel::AddItemCommand(Core::instance()->GetActiveProjectModel(),
Core::instance()->GetSelectedFolderInActiveProject(),
new_sequence,
command);
FootageToGhosts(0, dragged_footage_, new_sequence->video_params().time_base(), 0);
dst_graph = new_sequence.get();
viewer_node = new_sequence->viewer_output();
// Set this as the sequence to open
open_sequence = new_sequence.get();
}
}
}
}
if (dst_graph) {
QVector<Block*> block_items(parent()->ghost_items_.size());
// Check if we're inserting
if (insert) {
InsertGapsAtGhostDestination(parent()->ghost_items_, command);
}
for (int i=0;i<parent()->ghost_items_.size();i++) {
TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
ClipBlock* clip = new ClipBlock();
clip->set_length_and_media_out(ghost->Length());
clip->set_block_name(footage_stream->footage()->name());
new NodeAddCommand(dst_graph, clip, command);
switch (footage_stream->type()) {
case Stream::kVideo:
case Stream::kImage:
{
VideoInput* video_input = new VideoInput();
video_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, video_input, command);
new NodeEdgeAddCommand(video_input->output(), clip->texture_input(), command);
TransformDistort* transform = new TransformDistort();
new NodeAddCommand(dst_graph, transform, command);
new NodeEdgeAddCommand(transform->output(), video_input->matrix_input(), command);
//OpacityNode* opacity = new OpacityNode();
//NodeParam::ConnectEdge(opacity->texture_output(), clip->texture_input());
//NodeParam::ConnectEdge(media->texture_output(), opacity->texture_input());
break;
}
case Stream::kAudio:
{
AudioInput* audio_input = new AudioInput();
audio_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, audio_input, command);
VolumeNode* volume_node = new VolumeNode();
new NodeAddCommand(dst_graph, volume_node, command);
new NodeEdgeAddCommand(audio_input->output(), volume_node->samples_input(), command);
new NodeEdgeAddCommand(volume_node->output(), clip->texture_input(), command);
break;
}
default:
break;
}
new TrackPlaceBlockCommand(viewer_node->track_list(ghost->GetAdjustedTrack().type()),
ghost->GetAdjustedTrack().index(),
clip,
ghost->GetAdjustedIn(),
command);
block_items.replace(i, clip);
// Link any clips so far that share the same Footage with this one
for (int j=0;j<i;j++) {
StreamPtr footage_compare = parent()->ghost_items_.at(j)->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
if (footage_compare->footage() == footage_stream->footage()) {
Block::Link(block_items.at(j), clip);
}
}
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
parent()->ClearGhosts();
dragged_footage_.clear();
if (open_sequence) {
Sequence::Open(open_sequence);
}
}
+15
View File
@@ -75,6 +75,9 @@ MainMenu::MainMenu(QMainWindow *parent) :
edit_menu_->addSeparator();
MenuShared::instance()->AddItemsForClipEditMenu(edit_menu_);
edit_menu_->addSeparator();
edit_insert_item_ = edit_menu_->AddItem("insert", this, SLOT(InsertTriggered()), ",");
edit_overwrite_item_ = edit_menu_->AddItem("overwrite", this, SLOT(OverwriteTriggered()), ".");
edit_menu_->addSeparator();
edit_ripple_to_in_item_ = edit_menu_->AddItem("rippletoin", this, SLOT(RippleToInTriggered()), "Q");
edit_ripple_to_out_item_ = edit_menu_->AddItem("rippletoout", this, SLOT(RippleToOutTriggered()), "W");
edit_edit_to_in_item_ = edit_menu_->AddItem("edittoin", this, SLOT(EditToInTriggered()), "Ctrl+Alt+Q");
@@ -442,6 +445,16 @@ void MainMenu::DeselectAllTriggered()
PanelManager::instance()->CurrentlyFocused()->DeselectAll();
}
void MainMenu::InsertTriggered()
{
PanelManager::instance()->CurrentlyFocused()->Insert();
}
void MainMenu::OverwriteTriggered()
{
PanelManager::instance()->CurrentlyFocused()->Overwrite();
}
void MainMenu::RippleToInTriggered()
{
PanelManager::instance()->CurrentlyFocused()->RippleToIn();
@@ -516,6 +529,8 @@ void MainMenu::Retranslate()
edit_menu_->setTitle(tr("&Edit"));
//edit_undo_item_->setText(tr("&Undo")); FIXME: Does Qt translate these automatically?
//edit_redo_item_->setText(tr("Redo"));
edit_insert_item_->setText(tr("Insert"));
edit_overwrite_item_->setText(tr("Overwrite"));
edit_select_all_item_->setText(tr("Select &All"));
edit_deselect_all_item_->setText(tr("Deselect All"));
edit_ripple_to_in_item_->setText(tr("Ripple to In Point"));
+5
View File
@@ -123,6 +123,9 @@ private slots:
void SelectAllTriggered();
void DeselectAllTriggered();
void InsertTriggered();
void OverwriteTriggered();
void RippleToInTriggered();
void RippleToOutTriggered();
void EditToInTriggered();
@@ -160,6 +163,8 @@ private:
QAction* edit_redo_item_;
QAction* edit_select_all_item_;
QAction* edit_deselect_all_item_;
QAction* edit_insert_item_;
QAction* edit_overwrite_item_;
QAction* edit_ripple_to_in_item_;
QAction* edit_ripple_to_out_item_;
QAction* edit_edit_to_in_item_;