Merge branch 'master' into typos

This commit is contained in:
itsmattkc
2020-03-19 12:16:09 +11:00
committed by GitHub
126 changed files with 2951 additions and 934 deletions
+66 -16
View File
@@ -34,25 +34,25 @@ MenuShared::MenuShared()
new_folder_item_ = Menu::CreateItem(this, "newfolder", Core::instance(), SLOT(CreateNewFolder()));
// "Edit" menu shared items
edit_cut_item_ = Menu::CreateItem(this, "cut", nullptr, nullptr, "Ctrl+X");
edit_copy_item_ = Menu::CreateItem(this, "copy", nullptr, nullptr, "Ctrl+C");
edit_paste_item_ = Menu::CreateItem(this, "paste", nullptr, nullptr, "Ctrl+V");
edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", nullptr, nullptr, "Ctrl+Shift+V");
edit_cut_item_ = Menu::CreateItem(this, "cut", this, SLOT(CutTriggered()), "Ctrl+X");
edit_copy_item_ = Menu::CreateItem(this, "copy", this, SLOT(CopyTriggered()), "Ctrl+C");
edit_paste_item_ = Menu::CreateItem(this, "paste", this, SLOT(PasteTriggered()), "Ctrl+V");
edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", this, SLOT(PasteInsertTriggered()), "Ctrl+Shift+V");
edit_duplicate_item_ = Menu::CreateItem(this, "duplicate", nullptr, nullptr, "Ctrl+D");
edit_delete_item_ = Menu::CreateItem(this, "delete", this, SLOT(DeleteSelected()), "Del");
edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, SLOT(RippleDelete()), "Shift+Del");
edit_split_item_ = Menu::CreateItem(this, "split", this, SLOT(SplitAtPlayhead()), "Ctrl+K");
edit_delete_item_ = Menu::CreateItem(this, "delete", this, SLOT(DeleteSelectedTriggered()), "Del");
edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, SLOT(RippleDeleteTriggered()), "Shift+Del");
edit_split_item_ = Menu::CreateItem(this, "split", this, SLOT(SplitAtPlayheadTriggered()), "Ctrl+K");
// "In/Out" menu shared items
inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", nullptr, nullptr, "I");
inout_set_out_item_ = Menu::CreateItem(this, "setoutpoint", nullptr, nullptr, "O");
inout_reset_in_item_ = Menu::CreateItem(this, "resetin", nullptr, nullptr);
inout_reset_out_item_ = Menu::CreateItem(this, "resetout", nullptr, nullptr);
inout_clear_inout_item_ = Menu::CreateItem(this, "clearinout", nullptr, nullptr, "G");
inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", this, SLOT(SetInTriggered()), "I");
inout_set_out_item_ = Menu::CreateItem(this, "setoutpoint", this, SLOT(SetOutTriggered()), "O");
inout_reset_in_item_ = Menu::CreateItem(this, "resetin", this, SLOT(ResetInTriggered()));
inout_reset_out_item_ = Menu::CreateItem(this, "resetout", this, SLOT(ResetOutTriggered()));
inout_clear_inout_item_ = Menu::CreateItem(this, "clearinout", this, SLOT(ClearInOutTriggered()), "G");
// "Clip Edit" menu shared items
clip_add_default_transition_item_ = Menu::CreateItem(this, "deftransition", nullptr, nullptr, "Ctrl+Shift+D");
clip_link_unlink_item_ = Menu::CreateItem(this, "linkunlink", nullptr, nullptr, "Ctrl+L");
clip_link_unlink_item_ = Menu::CreateItem(this, "linkunlink", this, SLOT(ToggleLinksTriggered()), "Ctrl+L");
clip_enable_disable_item_ = Menu::CreateItem(this, "enabledisable", nullptr, nullptr, "Shift+E");
clip_nest_item_ = Menu::CreateItem(this, "nest", nullptr, nullptr);
@@ -112,7 +112,7 @@ MenuShared *MenuShared::instance()
return instance_;
}
void MenuShared::SplitAtPlayhead()
void MenuShared::SplitAtPlayheadTriggered()
{
TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused<TimelinePanel>();
@@ -121,16 +121,66 @@ void MenuShared::SplitAtPlayhead()
}
}
void MenuShared::DeleteSelected()
void MenuShared::DeleteSelectedTriggered()
{
PanelManager::instance()->CurrentlyFocused()->DeleteSelected();
}
void MenuShared::RippleDelete()
void MenuShared::RippleDeleteTriggered()
{
PanelManager::instance()->CurrentlyFocused()->RippleDelete();
}
void MenuShared::SetInTriggered()
{
PanelManager::instance()->CurrentlyFocused()->SetIn();
}
void MenuShared::SetOutTriggered()
{
PanelManager::instance()->CurrentlyFocused()->SetOut();
}
void MenuShared::ResetInTriggered()
{
PanelManager::instance()->CurrentlyFocused()->ResetIn();
}
void MenuShared::ResetOutTriggered()
{
PanelManager::instance()->CurrentlyFocused()->ResetOut();
}
void MenuShared::ClearInOutTriggered()
{
PanelManager::instance()->CurrentlyFocused()->ClearInOut();
}
void MenuShared::ToggleLinksTriggered()
{
PanelManager::instance()->CurrentlyFocused()->ToggleLinks();
}
void MenuShared::CutTriggered()
{
PanelManager::instance()->CurrentlyFocused()->CutSelected();
}
void MenuShared::CopyTriggered()
{
PanelManager::instance()->CurrentlyFocused()->CopySelected();
}
void MenuShared::PasteTriggered()
{
PanelManager::instance()->CurrentlyFocused()->Paste();
}
void MenuShared::PasteInsertTriggered()
{
PanelManager::instance()->CurrentlyFocused()->PasteInsert();
}
void MenuShared::Retranslate()
{
// "New" menu shared items
+23 -3
View File
@@ -75,11 +75,31 @@ private:
static MenuShared* instance_;
private slots:
void SplitAtPlayhead();
void SplitAtPlayheadTriggered();
void DeleteSelected();
void DeleteSelectedTriggered();
void RippleDelete();
void RippleDeleteTriggered();
void SetInTriggered();
void SetOutTriggered();
void ResetInTriggered();
void ResetOutTriggered();
void ClearInOutTriggered();
void ToggleLinksTriggered();
void CutTriggered();
void CopyTriggered();
void PasteTriggered();
void PasteInsertTriggered();
};
+126 -38
View File
@@ -20,11 +20,14 @@
#include "nodeview.h"
#include <QClipboard>
#include <QMouseEvent>
#include <QGuiApplication>
#include "core.h"
#include "nodeviewundo.h"
#include "node/factory.h"
#include "common/xmlutils.h"
NodeView::NodeView(QWidget *parent) :
QGraphicsView(parent),
@@ -90,16 +93,7 @@ void NodeView::DeleteSelected()
return;
}
QList<QGraphicsItem*> selected = scene_.selectedItems();
QList<Node*> selected_nodes;
foreach (QGraphicsItem* item, selected) {
NodeViewItem* node_item = dynamic_cast<NodeViewItem*>(item);
if (node_item) {
selected_nodes.append(node_item->node());
}
}
QList<Node*> selected_nodes = scene_.GetSelectedNodes();
if (selected_nodes.isEmpty()) {
return;
@@ -110,20 +104,12 @@ void NodeView::DeleteSelected()
void NodeView::SelectAll()
{
QList<QGraphicsItem *> all_items = this->items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(true);
}
scene_.SelectAll();
}
void NodeView::DeselectAll()
{
QList<QGraphicsItem *> selected_items = scene_.selectedItems();
foreach (QGraphicsItem* i, selected_items) {
i->setSelected(false);
}
scene_.DeselectAll();
}
void NodeView::Select(const QList<Node *> &nodes)
@@ -133,9 +119,6 @@ void NodeView::Select(const QList<Node *> &nodes)
foreach (Node* n, nodes) {
NodeViewItem* item = scene_.NodeToUIObject(n);
Q_ASSERT(n);
Q_ASSERT(item);
item->setSelected(true);
}
}
@@ -150,6 +133,125 @@ void NodeView::SelectWithDependencies(QList<Node *> nodes)
Select(nodes);
}
void NodeView::CopySelected(bool cut)
{
if (!graph_) {
return;
}
QList<Node*> selected = scene_.GetSelectedNodes();
if (selected.isEmpty()) {
return;
}
QString copy_str;
QXmlStreamWriter writer(&copy_str);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement(QStringLiteral("olive"));
foreach (Node* n, selected) {
n->Save(&writer);
}
writer.writeEndElement(); // clipboard
writer.writeEndDocument();
if (cut) {
DeleteSelected();
}
QGuiApplication::clipboard()->setText(copy_str);
}
void NodeView::Paste()
{
if (!graph_) {
return;
}
QString clipboard = QGuiApplication::clipboard()->text();
if (clipboard.isEmpty()) {
return;
}
QXmlStreamReader reader(clipboard);
QList<Node*> pasted_nodes;
QHash<quintptr, NodeOutput*> output_ptrs;
QList<NodeParam::SerializedConnection> desired_connections;
QList<NodeInput::FootageConnection> footage_connections;
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("olive")) {
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("node")) {
Node* node = XMLLoadNode(&reader);
if (node) {
node->Load(&reader, output_ptrs, desired_connections, footage_connections, nullptr);
graph_->AddNode(node);
pasted_nodes.append(node);
}
} else {
reader.skipCurrentElement();
}
}
} else {
reader.skipCurrentElement();
}
}
// Make connections
if (!desired_connections.isEmpty()) {
XMLConnectNodes(output_ptrs, desired_connections);
}
// Connect footage to existing footage if it exists
if (!footage_connections.isEmpty()) {
// Get list of all footage from project
// FIXME: Assumes sequence
QList<ItemPtr> footage = static_cast<Sequence*>(graph_)->project()->get_items_of_type(Item::kFootage);
if (!footage.isEmpty()) {
foreach (const NodeInput::FootageConnection& con, footage_connections) {
if (con.footage) {
// Assume this is a pointer to a Stream*
Stream* loaded_stream = reinterpret_cast<Stream*>(con.footage);
bool found = false;
foreach (ItemPtr item, footage) {
const QList<StreamPtr>& streams = std::static_pointer_cast<Footage>(item)->streams();
foreach (StreamPtr s, streams) {
if (s.get() == loaded_stream) {
con.input->set_standard_value(QVariant::fromValue(s));
found = true;
break;
}
}
if (found) {
break;
}
}
}
}
}
}
if (!pasted_nodes.isEmpty()) {
// FIXME: Attach to cursor so user can drop in place
}
}
void NodeView::ItemsChanged()
{
QHash<NodeEdge *, NodeViewEdge *>::const_iterator i;
@@ -263,21 +365,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
void NodeView::SceneSelectionChangedSlot()
{
// Get the scene's selected items and convert it into a list of selected nodes
QList<QGraphicsItem*> selected_items = scene_.selectedItems();
QList<Node*> selected_nodes;
for (int i=0;i<selected_items.size();i++) {
// Try to dynamically cast to a widget holding a Node
NodeViewItem* item = dynamic_cast<NodeViewItem*>(selected_items.at(i));
if (item != nullptr) {
selected_nodes.append(item->node());
}
}
emit SelectionChanged(selected_nodes);
emit SelectionChanged(scene_.GetSelectedNodes());
}
void NodeView::ShowContextMenu(const QPoint &pos)
+3
View File
@@ -57,6 +57,9 @@ public:
void Select(const QList<Node*>& nodes);
void SelectWithDependencies(QList<Node *> nodes);
void CopySelected(bool cut);
void Paste();
signals:
/**
* @brief Signal emitted when the selected nodes have changed
+55
View File
@@ -10,6 +10,15 @@ NodeViewScene::NodeViewScene(QObject *parent) :
void NodeViewScene::clear()
{
// Deselect everything (prevents signals that a selection has changed after deleting an object)
DeselectAll();
// HACK: QGraphicsScene contains some sort of internal hashing of the selected items which doesn't update unless
// we call a function like this. That means even though we deselect all items above, QGraphicsScene will
// continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get
// deleted. Calling this function appears to update the internal cache and prevent this.
selectedItems();
{
QHash<Node*, NodeViewItem*>::const_iterator i;
for (i=item_map_.begin();i!=item_map_.end();i++) {
@@ -27,6 +36,24 @@ void NodeViewScene::clear()
}
}
void NodeViewScene::SelectAll()
{
QList<QGraphicsItem *> all_items = this->items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(true);
}
}
void NodeViewScene::DeselectAll()
{
QList<QGraphicsItem *> selected_items = this->selectedItems();
foreach (QGraphicsItem* i, selected_items) {
i->setSelected(false);
}
}
NodeViewItem *NodeViewScene::NodeToUIObject(Node *n)
{
return item_map_.value(n);
@@ -42,6 +69,34 @@ void NodeViewScene::SetGraph(NodeGraph *graph)
graph_ = graph;
}
QList<Node *> NodeViewScene::GetSelectedNodes() const
{
QHash<Node*, NodeViewItem*>::const_iterator iterator;
QList<Node *> selected;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
if (iterator.value()->isSelected()) {
selected.append(iterator.key());
}
}
return selected;
}
QList<NodeViewItem *> NodeViewScene::GetSelectedItems() const
{
QHash<Node*, NodeViewItem*>::const_iterator iterator;
QList<NodeViewItem *> selected;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
if (iterator.value()->isSelected()) {
selected.append(iterator.value());
}
}
return selected;
}
const QHash<Node *, NodeViewItem *> &NodeViewScene::item_map() const
{
return item_map_;
+6
View File
@@ -16,6 +16,9 @@ public:
void clear();
void SelectAll();
void DeselectAll();
/**
* @brief Retrieve the graphical widget corresponding to a specific Node
*
@@ -38,6 +41,9 @@ public:
void SetGraph(NodeGraph* graph);
QList<Node*> GetSelectedNodes() const;
QList<NodeViewItem*> GetSelectedItems() const;
const QHash<Node*, NodeViewItem*>& item_map() const;
const QHash<NodeEdge*, NodeViewEdge*>& edge_map() const;
+22
View File
@@ -118,6 +118,28 @@ public:
virtual void DecreaseTrackHeight(){}
virtual void SetIn(){}
virtual void SetOut(){}
virtual void ResetIn(){}
virtual void ResetOut(){}
virtual void ClearInOut(){}
virtual void SetMarker(){}
virtual void ToggleLinks(){}
virtual void CutSelected(){}
virtual void CopySelected(){}
virtual void Paste(){}
virtual void PasteInsert(){}
protected:
/**
* @brief paintEvent
@@ -24,7 +24,7 @@
#include <QEvent>
#include <QHBoxLayout>
#include "common/timecodefunctions.h"
#include "core.h"
#include "config/config.h"
#include "ui/icons/icons.h"
@@ -120,6 +120,8 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
UpdateIcons();
SetTimebase(0);
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &PlaybackControls::TimecodeChanged);
}
void PlaybackControls::SetTimecodeEnabled(bool enabled)
@@ -147,9 +149,11 @@ void PlaybackControls::SetEndTime(const int64_t &r)
return;
}
end_tc_lbl_->setText(Timecode::timestamp_to_timecode(r,
end_time_ = r;
end_tc_lbl_->setText(Timecode::timestamp_to_timecode(end_time_,
time_base_,
Timecode::CurrentDisplay()));
Core::instance()->GetTimecodeDisplay()));
}
void PlaybackControls::ShowPauseButton()
@@ -181,3 +185,9 @@ void PlaybackControls::UpdateIcons()
next_frame_btn_->setIcon(icon::NextFrame);
go_to_end_btn_->setIcon(icon::GoToEnd);
}
void PlaybackControls::TimecodeChanged()
{
// Update end time
SetEndTime(end_time_);
}
@@ -101,6 +101,8 @@ private:
TimeSlider* cur_tc_lbl_;
QLabel* end_tc_lbl_;
int64_t end_time_;
rational time_base_;
QPushButton* go_to_start_btn_;
@@ -113,7 +115,7 @@ private:
QStackedWidget* playpause_stack_;
private slots:
void TimecodeChanged();
};
@@ -39,11 +39,8 @@ void ResizableScrollBar::mouseMoveEvent(QMouseEvent *event)
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 (mouse_dragging_) {
int new_drag_pos = GetActiveMousePos(event);
int mouse_movement = new_drag_pos - mouse_drag_start_;
mouse_drag_start_ = new_drag_pos;
@@ -52,21 +49,29 @@ void ResizableScrollBar::mouseMoveEvent(QMouseEvent *event)
mouse_movement = -mouse_movement;
}
double scale_multiplier = static_cast<double>(sr.width()) / static_cast<double>(sr.width() + mouse_movement);
emit RequestScale(scale_multiplier);
double width_adjustment = static_cast<double>(sr.width() + mouse_movement);
if (mouse_handle_state_ == kInTopHandle) {
int slider_min = gr.x();
int slider_max = gr.right() - (sr.width() + mouse_movement);
int val = QStyle::sliderValueFromPosition(minimum(),
maximum(),
event->pos().x() - slider_min,
slider_max - slider_min,
opt.upsideDown);
// Prevent dividing by zero or emitting a negative scale
if (width_adjustment > 0) {
double scale_multiplier = static_cast<double>(sr.width()) / width_adjustment;
emit RequestScale(scale_multiplier);
setValue(val);
} else {
setValue(qRound(static_cast<double>(value()) * scale_multiplier));
if (mouse_handle_state_ == kInTopHandle) {
QRect gr = style()->subControlRect(QStyle::CC_ScrollBar, &opt,
QStyle::SC_ScrollBarGroove, this);
int slider_min = gr.x();
int slider_max = gr.right() - (sr.width() + mouse_movement);
int val = QStyle::sliderValueFromPosition(minimum(),
maximum(),
event->pos().x() - slider_min,
slider_max - slider_min,
opt.upsideDown);
setValue(val);
} else {
setValue(qRound(static_cast<double>(value()) * scale_multiplier));
}
}
} else {
+10 -2
View File
@@ -1,11 +1,14 @@
#include "timeslider.h"
#include "common/timecodefunctions.h"
#include "core.h"
TimeSlider::TimeSlider(QWidget *parent) :
IntegerSlider(parent)
{
SetMinimum(0);
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &TimeSlider::TimecodeDisplayChanged);
}
void TimeSlider::SetTimebase(const rational &timebase)
@@ -25,10 +28,15 @@ QString TimeSlider::ValueToString(const QVariant &v)
return Timecode::timestamp_to_timecode(v.toLongLong(),
timebase_,
Timecode::CurrentDisplay());
Core::instance()->GetTimecodeDisplay());
}
QVariant TimeSlider::StringToValue(const QString &s, bool *ok)
{
return QVariant::fromValue(Timecode::timecode_to_timestamp(s, timebase_, Timecode::CurrentDisplay(), ok));
return QVariant::fromValue(Timecode::timecode_to_timestamp(s, timebase_, Core::instance()->GetTimecodeDisplay(), ok));
}
void TimeSlider::TimecodeDisplayChanged()
{
UpdateLabel(Value());
}
+4
View File
@@ -6,6 +6,7 @@
class TimeSlider : public IntegerSlider
{
Q_OBJECT
public:
TimeSlider(QWidget* parent = nullptr);
@@ -19,6 +20,9 @@ protected:
private:
rational timebase_;
private slots:
void TimecodeDisplayChanged();
};
#endif // TIMESLIDER_H
+156 -5
View File
@@ -1,11 +1,19 @@
#include "timebased.h"
#include <QInputDialog>
#include <QUndoCommand>
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "core.h"
#include "project/item/sequence/sequence.h"
#include "widget/timelinewidget/undo/undo.h"
TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_status_visible, QWidget *parent) :
QWidget(parent),
TimelineScaledWidget(parent),
viewer_node_(nullptr),
auto_max_scrollbar_(false)
auto_max_scrollbar_(false),
points_(nullptr)
{
ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this);
connect(ruler_, &TimeRuler::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal);
@@ -44,6 +52,9 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
DisconnectNodeInternal(viewer_node_);
disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
points_ = nullptr;
ruler()->ConnectTimelinePoints(nullptr);
}
viewer_node_ = node;
@@ -51,9 +62,13 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
ConnectedNodeChanged(viewer_node_);
if (viewer_node_) {
ConnectNodeInternal(viewer_node_);
connect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
if ((points_ = ConnectTimelinePoints())) {
ruler()->ConnectTimelinePoints(points_);
}
ConnectNodeInternal(viewer_node_);
}
}
@@ -68,7 +83,25 @@ void TimeBasedWidget::UpdateMaximumScroll()
void TimeBasedWidget::ScrollBarResized(const double &multiplier)
{
SetScale(GetScale() * multiplier);
QScrollBar* bar = static_cast<QScrollBar*>(sender());
// Our extension area (represented by a TimelineViewEndItem) is NOT scaled, but the ResizableScrollBar doesn't know
// this. Here we re-calculate the requested scale knowing that the end item is not affected by scale.
int current_max = bar->maximum();
double proposed_max = static_cast<double>(current_max) * multiplier;
proposed_max = proposed_max - (bar->width() * 0.5 / multiplier) + (bar->width() * 0.5);
double corrected_scale;
if (current_max == 0) {
corrected_scale = multiplier;
} else {
corrected_scale = (proposed_max / static_cast<double>(current_max));
}
SetScale(GetScale() * corrected_scale);
}
TimeRuler *TimeBasedWidget::ruler() const
@@ -114,6 +147,16 @@ void TimeBasedWidget::resizeEvent(QResizeEvent *event)
UpdateMaximumScroll();
}
TimelinePoints *TimeBasedWidget::ConnectTimelinePoints()
{
return static_cast<Sequence*>(viewer_node_->parent());
}
TimelinePoints *TimeBasedWidget::GetConnectedTimelinePoints() const
{
return points_;
}
void TimeBasedWidget::SetTime(int64_t timestamp)
{
ruler_->SetTime(timestamp);
@@ -128,6 +171,7 @@ void TimeBasedWidget::SetTimebase(const rational &timebase)
void TimeBasedWidget::SetScale(const double &scale)
{
// Simple QObject slot wrapper around TimelineScaledObject::SetScale()
TimelineScaledObject::SetScale(scale);
}
@@ -242,3 +286,110 @@ void TimeBasedWidget::CenterScrollOnPlayhead()
{
scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2);
}
void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time)
{
if (!points_) {
return;
}
QUndoCommand* command = new QUndoCommand();
// Enable workarea if it isn't already enabled
if (!points_->workarea()->enabled()) {
new WorkareaSetEnabledCommand(points_, true, command);
}
// Determine our new range
rational in_point, out_point;
if (m == Timeline::kTrimIn) {
in_point = time;
if (!points_->workarea()->enabled() || points_->workarea()->out() < in_point) {
out_point = TimelineWorkArea::kResetOut;
} else {
out_point = points_->workarea()->out();
}
} else {
out_point = time;
if (!points_->workarea()->enabled() || points_->workarea()->in() > out_point) {
in_point = TimelineWorkArea::kResetIn;
} else {
in_point = points_->workarea()->in();
}
}
// Set workarea
new WorkareaSetRangeCommand(points_, TimeRange(in_point, out_point), command);
Core::instance()->undo_stack()->push(command);
}
void TimeBasedWidget::ResetPoint(Timeline::MovementMode m)
{
if (!points_ || !points_->workarea()->enabled()) {
return;
}
TimeRange r = points_->workarea()->range();
if (m == Timeline::kTrimIn) {
r.set_in(TimelineWorkArea::kResetIn);
} else {
r.set_out(TimelineWorkArea::kResetOut);
}
Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points_, r));
}
void TimeBasedWidget::SetInAtPlayhead()
{
SetPoint(Timeline::kTrimIn, GetTime());
}
void TimeBasedWidget::SetOutAtPlayhead()
{
SetPoint(Timeline::kTrimOut, GetTime());
}
void TimeBasedWidget::ResetIn()
{
ResetPoint(Timeline::kTrimIn);
}
void TimeBasedWidget::ResetOut()
{
ResetPoint(Timeline::kTrimOut);
}
void TimeBasedWidget::ClearInOutPoints()
{
if (!points_) {
return;
}
Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(points_, false));
}
void TimeBasedWidget::SetMarker()
{
if (!points_) {
return;
}
bool ok;
QString marker_name;
if (Config::Current()["SetNameWithMarker"].toBool()) {
marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, QString(), &ok);
} else {
ok = true;
}
if (ok) {
points_->markers()->AddMarker(TimeRange(GetTime(), GetTime()), marker_name);
}
}
+40 -1
View File
@@ -3,12 +3,13 @@
#include <QWidget>
#include "common/timelinecommon.h"
#include "node/output/viewer/viewer.h"
#include "widget/resizablescrollbar/resizablescrollbar.h"
#include "widget/timelinewidget/timelinescaledobject.h"
#include "widget/timeruler/timeruler.h"
class TimeBasedWidget : public QWidget, public TimelineScaledObject
class TimeBasedWidget : public TimelineScaledWidget
{
Q_OBJECT
public:
@@ -48,6 +49,18 @@ public slots:
void GoToNextCut();
void SetInAtPlayhead();
void SetOutAtPlayhead();
void ResetIn();
void ResetOut();
void ClearInOutPoints();
void SetMarker();
TimeRuler* ruler() const;
protected slots:
@@ -72,6 +85,10 @@ protected:
virtual void resizeEvent(QResizeEvent *event) override;
virtual TimelinePoints* ConnectTimelinePoints();
TimelinePoints* GetConnectedTimelinePoints() const;
protected slots:
/**
* @brief Slot to center the horizontal scroll bar on the playhead's current position
@@ -84,6 +101,26 @@ signals:
void TimebaseChanged(const rational&);
private:
/**
* @brief Set either in or out point to the current playhead
*
* @param m
*
* Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
*/
void SetPoint(Timeline::MovementMode m, const rational &time);
/**
* @brief Reset either the in or out point
*
* Sets either the in point to 0 or the out point to `RATIONAL_MAX`.
*
* @param m
*
* Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
*/
void ResetPoint(Timeline::MovementMode m);
ViewerOutput* viewer_node_;
TimeRuler* ruler_;
@@ -92,6 +129,8 @@ private:
bool auto_max_scrollbar_;
TimelinePoints* points_;
private slots:
void UpdateMaximumScroll();
@@ -3,8 +3,11 @@
#include <cfloat>
#include <QtMath>
#include "common/clamp.h"
TimelineScaledObject::TimelineScaledObject() :
scale_(1.0),
min_scale_(0),
max_scale_(DBL_MAX)
{
@@ -64,6 +67,15 @@ void TimelineScaledObject::SetMaximumScale(const double &max)
}
}
void TimelineScaledObject::SetMinimumScale(const double &min)
{
min_scale_ = min;
if (GetScale() < min_scale_) {
SetScale(min_scale_);
}
}
const double& TimelineScaledObject::GetScale() const
{
return scale_;
@@ -71,7 +83,14 @@ const double& TimelineScaledObject::GetScale() const
void TimelineScaledObject::SetScale(const double& scale)
{
scale_ = scale;
Q_ASSERT(scale > 0);
scale_ = clamp(scale, min_scale_, max_scale_);
ScaleChangedEvent(scale_);
}
TimelineScaledWidget::TimelineScaledWidget(QWidget *parent) :
QWidget(parent)
{
}
@@ -1,6 +1,8 @@
#ifndef TIMELINESCALEDOBJECT_H
#define TIMELINESCALEDOBJECT_H
#include <QWidget>
#include "common/rational.h"
class TimelineScaledObject
@@ -30,6 +32,8 @@ protected:
void SetMaximumScale(const double& max);
void SetMinimumScale(const double& min);
private:
rational timebase_;
@@ -37,8 +41,17 @@ private:
double scale_;
double min_scale_;
double max_scale_;
};
class TimelineScaledWidget : public QWidget, public TimelineScaledObject
{
Q_OBJECT
public:
TimelineScaledWidget(QWidget* parent = nullptr);
};
#endif // TIMELINESCALEDOBJECT_H
+29 -2
View File
@@ -335,7 +335,7 @@ void TimelineWidget::SplitAtPlayhead()
}
}
void TimelineWidget::DeleteSelectedInternal(QList<Block *> blocks,
void TimelineWidget::DeleteSelectedInternal(const QList<Block *> &blocks,
bool transition_aware,
bool remove_from_graph,
QUndoCommand *command)
@@ -380,6 +380,8 @@ void TimelineWidget::DeleteSelectedInternal(QList<Block *> blocks,
}
if (remove_from_graph) {
new BlockUnlinkAllCommand(b, command);
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(b->parent()), b, command);
}
}
@@ -426,7 +428,7 @@ void TimelineWidget::DeleteSelected(bool ripple)
range_list.InsertTimeRange(TimeRange(b->in(), b->out()));
}
new TimelineRippleDeleteGapsAtRegions(GetConnectedNode(), range_list, command);
new TimelineRippleDeleteGapsAtRegionsCommand(GetConnectedNode(), range_list, command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
@@ -470,6 +472,30 @@ void TimelineWidget::OverwriteFootageAtPlayhead(const QList<Footage *> &footage)
import_tool_->PlaceAt(footage, GetTime(), false);
}
void TimelineWidget::ToggleLinksOnSelected()
{
QList<TimelineViewBlockItem*> sel = GetSelectedBlocks();
// Prioritize unlinking
QList<Block*> blocks;
bool link = true;
foreach (TimelineViewBlockItem* item, sel) {
if (link && item->block()->HasLinks()) {
link = false;
}
blocks.append(item->block());
}
if (link) {
Core::instance()->undo_stack()->push(new BlockLinkManyCommand(blocks, true));
} else {
Core::instance()->undo_stack()->push(new BlockLinkManyCommand(blocks, false));
}
}
QList<TimelineViewBlockItem *> TimelineWidget::GetSelectedBlocks()
{
QList<TimelineViewBlockItem *> list;
@@ -685,6 +711,7 @@ void TimelineWidget::AddBlock(Block *block, TrackReference track)
views_.at(track.type())->view()->scene()->addItem(item);
connect(block, &Block::Refreshed, this, &TimelineWidget::BlockChanged);
connect(block, &Block::LinksChanged, this, &TimelineWidget::PreviewUpdated);
if (block->type() == Block::kClip) {
connect(static_cast<ClipBlock*>(block), &ClipBlock::PreviewUpdated, this, &TimelineWidget::PreviewUpdated);
+3 -1
View File
@@ -57,6 +57,8 @@ public:
void OverwriteFootageAtPlayhead(const QList<Footage *> &footage);
void ToggleLinksOnSelected();
QList<TimelineViewBlockItem*> GetSelectedBlocks();
signals:
@@ -349,7 +351,7 @@ private:
bool dual_transition_;
};
void DeleteSelectedInternal(QList<Block *> blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
void DeleteSelectedInternal(const QList<Block *>& blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
void SetBlockLinksSelected(Block *block, bool selected);
+1 -1
View File
@@ -143,7 +143,7 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
Timecode::CurrentDisplay());
Core::instance()->GetTimecodeDisplay());
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
// of the cursor)
+1 -1
View File
@@ -404,7 +404,7 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
int64_t earliest_timestamp = Timecode::time_to_timestamp(time_movement, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
Timecode::CurrentDisplay(),
Core::instance()->GetTimecodeDisplay(),
true);
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
+1 -1
View File
@@ -54,7 +54,7 @@ void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
int64_t earliest_timestamp = Timecode::time_to_timestamp(time_movement, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
Timecode::CurrentDisplay(),
Core::instance()->GetTimecodeDisplay(),
true);
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
// of the cursor)
+103 -3
View File
@@ -623,14 +623,14 @@ void BlockSetSpeedCommand::undo_internal()
block_->set_speed(old_speed_);
}
TimelineRippleDeleteGapsAtRegions::TimelineRippleDeleteGapsAtRegions(ViewerOutput *vo, const TimeRangeList &regions, QUndoCommand *parent) :
TimelineRippleDeleteGapsAtRegionsCommand::TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput *vo, const TimeRangeList &regions, QUndoCommand *parent) :
UndoCommand(parent),
timeline_(vo),
regions_(regions)
{
}
void TimelineRippleDeleteGapsAtRegions::redo_internal()
void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal()
{
foreach (const TimeRange& range, regions_) {
rational max_ripple_length = range.length();
@@ -671,7 +671,7 @@ void TimelineRippleDeleteGapsAtRegions::redo_internal()
}
}
void TimelineRippleDeleteGapsAtRegions::undo_internal()
void TimelineRippleDeleteGapsAtRegionsCommand::undo_internal()
{
for (int i=commands_.size()-1;i>=0;i--) {
commands_.at(i)->undo();
@@ -679,3 +679,103 @@ void TimelineRippleDeleteGapsAtRegions::undo_internal()
}
commands_.empty();
}
WorkareaSetEnabledCommand::WorkareaSetEnabledCommand(TimelinePoints *points, bool enabled, QUndoCommand *parent) :
UndoCommand(parent),
points_(points),
old_enabled_(points_->workarea()->enabled()),
new_enabled_(enabled)
{
}
void WorkareaSetEnabledCommand::redo_internal()
{
points_->workarea()->set_enabled(new_enabled_);
}
void WorkareaSetEnabledCommand::undo_internal()
{
points_->workarea()->set_enabled(old_enabled_);
}
WorkareaSetRangeCommand::WorkareaSetRangeCommand(TimelinePoints *points, const TimeRange &range, QUndoCommand *parent) :
UndoCommand(parent),
points_(points),
old_range_(points_->workarea()->range()),
new_range_(range)
{
}
void WorkareaSetRangeCommand::redo_internal()
{
points_->workarea()->set_range(new_range_);
}
void WorkareaSetRangeCommand::undo_internal()
{
points_->workarea()->set_range(old_range_);
}
BlockLinkCommand::BlockLinkCommand(Block *a, Block *b, bool link, QUndoCommand *parent) :
UndoCommand(parent),
a_(a),
b_(b),
link_(link)
{
}
void BlockLinkCommand::redo_internal()
{
if (link_) {
done_ = Block::Link(a_, b_);
} else {
done_ = Block::Unlink(a_, b_);
}
}
void BlockLinkCommand::undo_internal()
{
if (done_) {
if (link_) {
Block::Unlink(a_, b_);
} else {
Block::Link(a_, b_);
}
}
}
BlockUnlinkAllCommand::BlockUnlinkAllCommand(Block *block, QUndoCommand *parent) :
UndoCommand(parent),
block_(block)
{
}
void BlockUnlinkAllCommand::redo_internal()
{
unlinked_ = block_->linked_clips();
foreach (Block* link, unlinked_) {
Block::Unlink(block_, link);
}
}
void BlockUnlinkAllCommand::undo_internal()
{
foreach (Block* link, unlinked_) {
Block::Link(block_, link);
}
unlinked_.clear();
}
BlockLinkManyCommand::BlockLinkManyCommand(const QList<Block *> blocks, bool link, QUndoCommand *parent) :
UndoCommand(parent)
{
foreach (Block* a, blocks) {
foreach (Block* b, blocks) {
if (a != b) {
new BlockLinkCommand(a, b, link, this);
}
}
}
}
+76 -2
View File
@@ -27,6 +27,7 @@
#include "node/block/gap/gap.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "timeline/timelinepoints.h"
#include "undo/undocommand.h"
class BlockResizeCommand : public UndoCommand {
@@ -280,9 +281,9 @@ private:
};
class TimelineRippleDeleteGapsAtRegions : public UndoCommand {
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
public:
TimelineRippleDeleteGapsAtRegions(ViewerOutput* vo, const TimeRangeList& regions, QUndoCommand* parent = nullptr);
TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput* vo, const TimeRangeList& regions, QUndoCommand* parent = nullptr);
protected:
virtual void redo_internal() override;
@@ -296,4 +297,77 @@ private:
};
class WorkareaSetEnabledCommand : public UndoCommand {
public:
WorkareaSetEnabledCommand(TimelinePoints* points, bool enabled, QUndoCommand* parent = nullptr);
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
TimelinePoints* points_;
bool old_enabled_;
bool new_enabled_;
};
class WorkareaSetRangeCommand : public UndoCommand {
public:
WorkareaSetRangeCommand(TimelinePoints* points, const TimeRange& range, QUndoCommand* parent = nullptr);
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
TimelinePoints* points_;
TimeRange old_range_;
TimeRange new_range_;
};
class BlockLinkManyCommand : public UndoCommand {
public:
BlockLinkManyCommand(const QList<Block*> blocks, bool link, QUndoCommand* parent = nullptr);
};
class BlockLinkCommand : public UndoCommand {
public:
BlockLinkCommand(Block* a, Block* b, bool link, QUndoCommand* parent = nullptr);
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
Block* a_;
Block* b_;
bool link_;
bool done_;
};
class BlockUnlinkAllCommand : public UndoCommand {
public:
BlockUnlinkAllCommand(Block* block, QUndoCommand* parent = nullptr);
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
Block* block_;
QVector<Block*> unlinked_;
};
#endif // TIMELINEUNDOABLE_H
@@ -22,12 +22,12 @@
#include <QPainter>
const QColor &TimelinePlayhead::PlayheadColor() const
const QColor &TimelinePlayhead::GetPlayheadColor() const
{
return playhead_color_;
}
const QColor &TimelinePlayhead::PlayheadHighlightColor() const
const QColor &TimelinePlayhead::GetPlayheadHighlightColor() const
{
return playhead_highlight_color_;
}
@@ -45,10 +45,10 @@ void TimelinePlayhead::SetPlayheadHighlightColor(QColor c)
void TimelinePlayhead::Draw(QPainter* painter, const QRectF& playhead_rect) const
{
painter->setPen(Qt::NoPen);
painter->setBrush(PlayheadHighlightColor());
painter->setBrush(GetPlayheadHighlightColor());
painter->drawRect(playhead_rect);
painter->setPen(PlayheadColor());
painter->setPen(GetPlayheadColor());
painter->setBrush(Qt::NoBrush);
painter->drawLine(QLineF(playhead_rect.topLeft(), playhead_rect.bottomLeft()));
}
@@ -31,13 +31,13 @@
class TimelinePlayhead : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor playheadColor READ PlayheadColor WRITE SetPlayheadColor DESIGNABLE true)
Q_PROPERTY(QColor playheadHighlightColor READ PlayheadHighlightColor WRITE SetPlayheadHighlightColor DESIGNABLE true)
Q_PROPERTY(QColor playheadColor READ GetPlayheadColor WRITE SetPlayheadColor DESIGNABLE true)
Q_PROPERTY(QColor playheadHighlightColor READ GetPlayheadHighlightColor WRITE SetPlayheadHighlightColor DESIGNABLE true)
public:
TimelinePlayhead() = default;
const QColor& PlayheadColor() const;
const QColor& PlayheadHighlightColor() const;
const QColor& GetPlayheadColor() const;
const QColor& GetPlayheadHighlightColor() const;
void SetPlayheadColor(QColor c);
void SetPlayheadHighlightColor(QColor c);
@@ -29,11 +29,10 @@
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include "audio/sumsamples.h"
#include "common/clamp.h"
#include "common/qtutils.h"
#include "config/config.h"
#include "node/block/transition/transition.h"
#include "widget/viewer/audiowaveformview.h"
TimelineViewBlockItem::TimelineViewBlockItem(Block *block, QGraphicsItem* parent) :
TimelineViewRect(parent),
@@ -94,45 +93,12 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
QByteArray w = wave_file.readAll();
// FIXME: Hardcoded channel count
int channels = 2;
const SampleSummer::Sum* samples = reinterpret_cast<const SampleSummer::Sum*>(w.constData());
int nb_samples = w.size() / sizeof(SampleSummer::Sum);
int sample_index, next_sample_index = 0;
QVector<SampleSummer::Sum> summary;
int summary_index = -1;
int channel_height = rect().height() / channels;
int channel_half_height = channel_height / 2;
for (int i=0;i<rect().width();i++) {
sample_index = next_sample_index;
if (sample_index == nb_samples) {
break;
}
next_sample_index = qMin(nb_samples,
qFloor(static_cast<double>(SampleSummer::kSumSampleRate) * static_cast<double>(i+1) / this->GetScale()) * channels);
if (summary_index != sample_index) {
summary = SampleSummer::ReSumSamples(&samples[sample_index],
qMax(channels, next_sample_index - sample_index),
channels);
summary_index = sample_index;
}
for (int j=0;j<summary.size();j++) {
int channel_mid = channel_height * j + channel_half_height;
painter->drawLine(i,
channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height),
i,
channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height));
}
}
AudioWaveformView::DrawWaveform(painter,
rect().toRect(),
this->GetScale(),
reinterpret_cast<const SampleSummer::Sum*>(w.constData()),
w.size() / sizeof(SampleSummer::Sum),
2);
wave_file.close();
}
+2
View File
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timeruler/seekablewidget.h
widget/timeruler/seekablewidget.cpp
widget/timeruler/timeruler.h
widget/timeruler/timeruler.cpp
PARENT_SCOPE
+201
View File
@@ -0,0 +1,201 @@
#include "seekablewidget.h"
#include <QMouseEvent>
#include <QPainter>
#include <QtMath>
#include "common/qtutils.h"
SeekableWidget::SeekableWidget(QWidget* parent) :
TimelineScaledWidget(parent),
time_(0),
timeline_points_(nullptr),
scroll_(0)
{
QFontMetrics fm = fontMetrics();
text_height_ = fm.height();
// Set width of playhead marker
playhead_width_ = QFontMetricsWidth(fm, "H");
}
void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points)
{
if (timeline_points_) {
disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
}
timeline_points_ = points;
if (timeline_points_) {
connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
}
update();
}
const int64_t &SeekableWidget::GetTime() const
{
return time_;
}
const int &SeekableWidget::GetScroll() const
{
return scroll_;
}
void SeekableWidget::mousePressEvent(QMouseEvent *event)
{
SeekToScreenPoint(event->pos().x());
}
void SeekableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
SeekToScreenPoint(event->pos().x());
}
}
void SeekableWidget::ScaleChangedEvent(const double &)
{
update();
}
TimelinePoints *SeekableWidget::timeline_points() const
{
return timeline_points_;
}
void SeekableWidget::SetTime(const int64_t &r)
{
time_ = r;
update();
}
void SeekableWidget::SetScroll(int s)
{
scroll_ = s;
update();
}
double SeekableWidget::ScreenToUnitFloat(int screen)
{
return (screen + scroll_) / GetScale() / timebase_dbl();
}
int64_t SeekableWidget::ScreenToUnit(int screen)
{
return qFloor(ScreenToUnitFloat(screen));
}
int64_t SeekableWidget::ScreenToUnitRounded(int screen)
{
return qRound64(ScreenToUnitFloat(screen));
}
int SeekableWidget::UnitToScreen(int64_t unit)
{
return qFloor(static_cast<double>(unit) * GetScale() * timebase_dbl()) - scroll_;
}
int SeekableWidget::TimeToScreen(const rational &time)
{
return qFloor(time.toDouble() * GetScale()) - scroll_;
}
void SeekableWidget::SeekToScreenPoint(int screen)
{
int64_t timestamp = qMax(static_cast<int64_t>(0), ScreenToUnitRounded(screen));
SetTime(timestamp);
emit TimeChanged(timestamp);
}
void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom)
{
if (!timeline_points()) {
return;
}
// Draw in/out workarea
if (timeline_points()->workarea()->enabled()) {
int workarea_left = qMax(0, TimeToScreen(timeline_points()->workarea()->in()));
int workarea_right;
if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) {
workarea_right = width();
} else {
workarea_right = qMin(width(), TimeToScreen(timeline_points()->workarea()->out()));
}
p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight());
}
// Draw markers
if (marker_bottom > 0 && !timeline_points()->markers()->list().isEmpty()) {
int marker_top = marker_bottom - text_height_;
// FIXME: Hardcoded marker colors
p->setPen(Qt::black);
p->setBrush(Qt::green);
foreach (TimelineMarker* marker, timeline_points()->markers()->list()) {
int marker_left = TimeToScreen(marker->time().in());
int marker_right = TimeToScreen(marker->time().out());
if (marker_left >= width() || marker_right < 0) {
continue;
}
if (marker->time().length() == 0) {
// Single point in time marker
DrawPlayhead(p, marker_left, marker_bottom);
} else {
// Marker range
int rect_left = qMax(0, marker_left);
int rect_right = qMin(width(), marker_right);
QRect marker_rect(rect_left, marker_top, rect_right - rect_left, marker_bottom - marker_top);
p->drawRect(marker_rect);
if (!marker->name().isEmpty()) {
p->drawText(marker_rect, marker->name());
}
}
}
}
}
void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y)
{
int half_width = playhead_width_ / 2;
if (x + half_width < 0 || x - half_width > width()) {
return;
}
p->setRenderHint(QPainter::Antialiasing);
int half_text_height = text_height() / 3;
QPoint points[] = {
QPoint(x, y),
QPoint(x - half_width, y - half_text_height),
QPoint(x - half_width, y - text_height()),
QPoint(x + 1 + half_width, y - text_height()),
QPoint(x + 1 + half_width, y - half_text_height),
QPoint(x + 1, y),
};
p->drawPolygon(points, 6);
p->setRenderHint(QPainter::Antialiasing, false);
}
+88
View File
@@ -0,0 +1,88 @@
#ifndef SEEKABLEWIDGET_H
#define SEEKABLEWIDGET_H
#include "common/rational.h"
#include "timeline/timelinepoints.h"
#include "widget/timelinewidget/view/timelineplayhead.h"
#include "widget/timelinewidget/timelinescaledobject.h"
class SeekableWidget : public TimelineScaledWidget
{
Q_OBJECT
public:
SeekableWidget(QWidget *parent = nullptr);
const int64_t& GetTime() const;
const int& GetScroll() const;
void ConnectTimelinePoints(TimelinePoints* points);
public slots:
void SetTime(const int64_t &r);
void SetScroll(int s);
protected:
void SeekToScreenPoint(int screen);
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void ScaleChangedEvent(const double&) override;
void DrawTimelinePoints(QPainter *p, int marker_bottom = 0);
TimelinePoints* timeline_points() const;
double ScreenToUnitFloat(int screen);
int64_t ScreenToUnit(int screen);
int64_t ScreenToUnitRounded(int screen);
int UnitToScreen(int64_t unit);
int TimeToScreen(const rational& time);
void DrawPlayhead(QPainter* p, int x, int y);
inline const int& text_height() const {
return text_height_;
}
inline const int& playhead_width() const {
return playhead_width_;
}
inline const QColor& GetPlayheadColor() const
{
return style_.GetPlayheadColor();
}
inline const QColor& GetPlayheadHighlightColor() const
{
return style_.GetPlayheadHighlightColor();
}
signals:
/**
* @brief Signal emitted whenever the time changes on this ruler, either by user or programatically
*/
void TimeChanged(int64_t);
private:
int64_t time_;
TimelinePlayhead style_;
TimelinePoints* timeline_points_;
int scroll_;
int text_height_;
int playhead_width_;
};
#endif // SEEKABLEWIDGET_H
+40 -123
View File
@@ -21,9 +21,7 @@
#include "timeruler.h"
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QtMath>
#include "common/timecodefunctions.h"
#include "common/qtutils.h"
@@ -31,12 +29,9 @@
#include "core.h"
TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* parent) :
QWidget(parent),
scroll_(0),
SeekableWidget(parent),
text_visible_(text_visible),
centered_text_(true),
scale_(1.0),
time_(0),
show_cache_status_(cache_status_visible)
{
QFontMetrics fm = fontMetrics();
@@ -44,46 +39,17 @@ TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* pare
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
// Text height is used to calculate widget height
text_height_ = fm.height();
cache_status_height_ = text_height_ / 4;
cache_status_height_ = text_height() / 4;
// Get the "minimum" space allowed between two line markers on the ruler (in screen pixels)
// Mediocre but reliable way of scaling UI objects by font/DPI size
minimum_gap_between_lines_ = QFontMetricsWidth(fm, "H");
// Set width of playhead marker
playhead_width_ = minimum_gap_between_lines_;
// Text visibility affects height, so we set that here
UpdateHeight();
}
const double &TimeRuler::GetScale()
{
return scale_;
}
void TimeRuler::SetScale(const double &d)
{
scale_ = d;
update();
}
void TimeRuler::SetTimebase(const rational &r)
{
timebase_ = r;
timebase_dbl_ = timebase_.toDouble();
timebase_flipped_dbl_ = timebase_.flipped().toDouble();
update();
}
const int64_t &TimeRuler::GetTime()
{
return time_;
// Force update if the default timecode display mode changes
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, static_cast<void (TimeRuler::*)()>(&TimeRuler::update));
}
void TimeRuler::SetCacheStatusLength(const rational &length)
@@ -97,20 +63,6 @@ void TimeRuler::SetCacheStatusLength(const rational &length)
}
}
void TimeRuler::SetTime(const int64_t &r)
{
time_ = r;
update();
}
void TimeRuler::SetScroll(int s)
{
scroll_ = s;
update();
}
void TimeRuler::CacheInvalidatedRange(const TimeRange& range)
{
if (show_cache_status_) {
@@ -123,7 +75,7 @@ void TimeRuler::CacheInvalidatedRange(const TimeRange& range)
void TimeRuler::CacheTimeReady(const rational &time)
{
if (show_cache_status_) {
dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase_));
dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase()));
update();
}
@@ -132,18 +84,33 @@ void TimeRuler::CacheTimeReady(const rational &time)
void TimeRuler::paintEvent(QPaintEvent *)
{
// Nothing to paint if the timebase is invalid
if (timebase_.isNull()) {
if (timebase().isNull()) {
return;
}
QPainter p(this);
double width_of_frame = timebase_dbl_ * scale_;
// Draw timeline points if connected
if (timeline_points()) {
int marker_bottom = height() - text_height();
if (show_cache_status_) {
marker_bottom -= cache_status_height_;
}
if (text_visible_) {
marker_bottom -= cache_status_height_;
}
DrawTimelinePoints(&p, marker_bottom);
}
double width_of_frame = timebase_dbl() * GetScale();
double width_of_second = 0;
do {
width_of_second += timebase_dbl_;
width_of_second += timebase_dbl();
} while (width_of_second < 1.0);
width_of_second *= scale_;
width_of_second *= GetScale();
double width_of_minute = width_of_second * 60;
double width_of_hour = width_of_minute * 60;
double width_of_day = width_of_hour * 24;
@@ -229,7 +196,7 @@ void TimeRuler::paintEvent(QPaintEvent *)
const int kAverageTextWidth = 200;
for (int i=-kAverageTextWidth;i<width()+kAverageTextWidth;i++) {
double screen_pt = static_cast<double>(i + scroll_);
double screen_pt = static_cast<double>(i + GetScroll());
if (long_interval > -1) {
int this_long_unit = qFloor(screen_pt/long_interval);
@@ -239,7 +206,7 @@ void TimeRuler::paintEvent(QPaintEvent *)
if (text_visible_) {
QRect text_rect;
Qt::Alignment text_align;
QString timecode_str = Timecode::timestamp_to_timecode(ScreenToUnit(i), timebase_, Timecode::CurrentDisplay());
QString timecode_str = Timecode::timestamp_to_timecode(ScreenToUnit(i), timebase(), Core::instance()->GetTimecodeDisplay());
int timecode_width = QFontMetricsWidth(fm, timecode_str);
int timecode_left;
@@ -308,43 +275,17 @@ void TimeRuler::paintEvent(QPaintEvent *)
}
// Draw the playhead if it's on screen at the moment
int playhead_pos = UnitToScreen(time_);
if (playhead_pos + playhead_width_ >= 0 && playhead_pos - playhead_width_ < width()) {
p.setPen(Qt::NoPen);
p.setBrush(style_.PlayheadColor());
DrawPlayhead(&p, playhead_pos, line_bottom);
}
int playhead_pos = UnitToScreen(GetTime());
p.setPen(Qt::NoPen);
p.setBrush(GetPlayheadColor());
DrawPlayhead(&p, playhead_pos, line_bottom);
}
void TimeRuler::mousePressEvent(QMouseEvent *event)
void TimeRuler::TimebaseChangedEvent(const rational &tb)
{
SeekToScreenPoint(event->pos().x());
}
timebase_flipped_dbl_ = tb.flipped().toDouble();
void TimeRuler::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
SeekToScreenPoint(event->pos().x());
}
}
void TimeRuler::DrawPlayhead(QPainter *p, int x, int y)
{
p->setRenderHint(QPainter::Antialiasing);
int half_text_height = text_height_ / 3;
int half_width = playhead_width_ / 2;
QPoint points[] = {
QPoint(x, y),
QPoint(x - half_width, y - half_text_height),
QPoint(x - half_width, y - text_height_),
QPoint(x + 1 + half_width, y - text_height_),
QPoint(x + 1 + half_width, y - half_text_height),
QPoint(x + 1, y),
};
p->drawPolygon(points, 6);
update();
}
int TimeRuler::CacheStatusHeight() const
@@ -352,46 +293,22 @@ int TimeRuler::CacheStatusHeight() const
return fontMetrics().height() / 4;
}
double TimeRuler::ScreenToUnitFloat(int screen)
{
return (screen + scroll_) / scale_ / timebase_dbl_;
}
int64_t TimeRuler::ScreenToUnit(int screen)
{
return qFloor(ScreenToUnitFloat(screen));
}
int TimeRuler::UnitToScreen(int64_t unit)
{
return qFloor(static_cast<double>(unit) * scale_ * timebase_dbl_) - scroll_;
}
int TimeRuler::TimeToScreen(const rational &time)
{
return qFloor(time.toDouble() * scale_) - scroll_;
}
void TimeRuler::SeekToScreenPoint(int screen)
{
int64_t timestamp = qMax(0, qRound(ScreenToUnitFloat(screen)));
SetTime(timestamp);
emit TimeChanged(timestamp);
}
void TimeRuler::UpdateHeight()
{
int height = text_height_;
int height = text_height();
// Add text height
if (text_visible_) {
height += text_height_;
height += text_height();
}
// Add cache status height
if (show_cache_status_) {
height += cache_status_height_;
}
// Add marker height
height += text_height();
setFixedHeight(height);
}
+3 -50
View File
@@ -24,30 +24,18 @@
#include <QTimer>
#include <QWidget>
#include "common/rational.h"
#include "common/timerange.h"
#include "widget/timelinewidget/view/timelineplayhead.h"
#include "seekablewidget.h"
class TimeRuler : public QWidget
class TimeRuler : public SeekableWidget
{
Q_OBJECT
public:
TimeRuler(bool text_visible = true, bool cache_status_visible = false, QWidget* parent = nullptr);
const double& GetScale();
void SetScale(const double& d);
void SetTimebase(const rational& r);
void SetCenteredText(bool c);
const int64_t& GetTime();
public slots:
void SetTime(const int64_t &r);
void SetScroll(int s);
void CacheInvalidatedRange(const TimeRange &range);
void CacheTimeReady(const rational& time);
@@ -57,58 +45,23 @@ public slots:
protected:
virtual void paintEvent(QPaintEvent* e) override;
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
signals:
/**
* @brief Signal emitted whenever the time changes on this ruler, either by user or programmatically
*/
void TimeChanged(int64_t);
virtual void TimebaseChangedEvent(const rational& tb) override;
private:
void UpdateHeight();
void DrawPlayhead(QPainter* p, int x, int y);
int CacheStatusHeight() const;
double ScreenToUnitFloat(int screen);
int64_t ScreenToUnit(int screen);
int UnitToScreen(int64_t unit);
int TimeToScreen(const rational& time);
void SeekToScreenPoint(int screen);
int text_height_;
int cache_status_height_;
int minimum_gap_between_lines_;
int playhead_width_;
int scroll_;
bool text_visible_;
bool centered_text_;
double scale_;
rational timebase_;
double timebase_dbl_;
double timebase_flipped_dbl_;
int64_t time_;
TimelinePlayhead style_;
bool show_cache_status_;
rational cache_length_;
+2
View File
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/viewer/audiowaveformview.h
widget/viewer/audiowaveformview.cpp
widget/viewer/footageviewer.h
widget/viewer/footageviewer.cpp
widget/viewer/viewer.h
+189
View File
@@ -0,0 +1,189 @@
#include "audiowaveformview.h"
#include <QFile>
#include <QPainter>
#include <QtMath>
#include "common/clamp.h"
#include "config/config.h"
AudioWaveformView::AudioWaveformView(QWidget *parent) :
SeekableWidget(parent),
backend_(nullptr)
{
setAutoFillBackground(true);
setBackgroundRole(QPalette::Base);
}
void AudioWaveformView::SetBackend(AudioRenderBackend *backend)
{
if (backend_) {
disconnect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast<void (AudioWaveformView::*)()>(&AudioWaveformView::update));
disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
SetTimebase(0);
}
backend_ = backend;
if (backend_) {
connect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast<void (AudioWaveformView::*)()>(&AudioWaveformView::update));
connect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
SetTimebase(backend_->params().time_base());
}
update();
}
void AudioWaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels)
{
int sample_index, next_sample_index = 0;
QVector<SampleSummer::Sum> summary;
int summary_index = -1;
int channel_height = rect.height() / channels;
int channel_half_height = channel_height / 2;
for (int i=0;i<rect.width();i++) {
sample_index = next_sample_index;
if (sample_index == nb_samples) {
break;
}
next_sample_index = qMin(nb_samples,
qFloor(static_cast<double>(SampleSummer::kSumSampleRate) * static_cast<double>(i+1) / scale) * channels);
if (summary_index != sample_index) {
summary = SampleSummer::ReSumSamples(&samples[sample_index],
qMax(channels, next_sample_index - sample_index),
channels);
summary_index = sample_index;
}
int line_x = i + rect.x();
for (int j=0;j<summary.size();j++) {
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
int channel_bottom = rect.y() + channel_height * (j + 1);
int diff = qRound((summary.at(j).max - summary.at(j).min) * channel_half_height);
painter->drawLine(line_x,
channel_bottom - diff,
line_x,
channel_bottom);
} else{
int channel_mid = rect.y() + channel_height * j + channel_half_height;
painter->drawLine(line_x,
channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height),
line_x,
channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height));
}
}
}
}
void AudioWaveformView::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) {
return;
}
const AudioRenderingParams& params = backend_->params();
if (cached_size_ != size()
|| cached_scale_ != GetScale()
|| cached_scroll_ != GetScroll()) {
cached_waveform_ = QPixmap(size());
cached_waveform_.fill(Qt::transparent);
QFile fs(backend_->CachePathName());
if (fs.open(QFile::ReadOnly)) {
QPainter wave_painter(&cached_waveform_);
// FIXME: Hardcoded color
wave_painter.setPen(Qt::green);
int channel_height = height() / params.channel_count();
int channel_half_height = channel_height / 2;
int drew = 0;
fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
for (int x=0; x<width() && !fs.atEnd(); x++) {
int samples_len = ScreenToUnitRounded(x+1) - ScreenToUnitRounded(x);
int max_read_size = params.samples_to_bytes(samples_len);
QByteArray read_buffer = fs.read(max_read_size);
// Detect whether we've reached EOF and recalculate sample count if so
if (read_buffer.size() < max_read_size) {
samples_len = params.bytes_to_samples(read_buffer.size());
}
QVector<SampleSummer::Sum> samples = SampleSummer::SumSamples(reinterpret_cast<const float*>(read_buffer.constData()),
samples_len,
params.channel_count());
for (int i=0;i<params.channel_count();i++) {
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
int channel_bottom = channel_height * (i + 1);
int diff = qRound((samples.at(i).max - samples.at(i).min) * channel_half_height);
wave_painter.drawLine(x,
channel_bottom - diff,
x,
channel_bottom);
} else {
int channel_mid = channel_height * i + channel_half_height;
wave_painter.drawLine(x,
channel_mid + samples.at(i).min * static_cast<float>(channel_half_height),
x,
channel_mid + samples.at(i).max * static_cast<float>(channel_half_height));
}
drew++;
}
}
cached_size_ = size();
cached_scale_ = GetScale();
cached_scroll_ = GetScroll();
fs.close();
}
}
QPainter p(this);
// Draw in/out points
DrawTimelinePoints(&p);
// Draw cached waveform pixmap
p.drawPixmap(0, 0, cached_waveform_);
// Draw playhead
p.setPen(GetPlayheadColor());
int playhead_x = UnitToScreen(GetTime());
p.drawLine(playhead_x, 0, playhead_x, height());
}
void AudioWaveformView::BackendParamsChanged()
{
SetTimebase(backend_->params().time_base());
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef WAVEFORMVIEW_H
#define WAVEFORMVIEW_H
#include <QWidget>
#include "audio/sumsamples.h"
#include "render/audioparams.h"
#include "render/backend/audiorenderbackend.h"
#include "widget/timeruler/seekablewidget.h"
class AudioWaveformView : public SeekableWidget
{
Q_OBJECT
public:
AudioWaveformView(QWidget* parent = nullptr);
//void SetData(const QString& file, const AudioRenderingParams& params);
void SetBackend(AudioRenderBackend* backend);
static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const SampleSummer::Sum *samples, int nb_samples, int channels);
protected:
virtual void paintEvent(QPaintEvent* event) override;
private:
AudioRenderBackend* backend_;
QPixmap cached_waveform_;
QSize cached_size_;
double cached_scale_;
int cached_scroll_;
private slots:
void BackendParamsChanged();
};
#endif // WAVEFORMVIEW_H
+10 -5
View File
@@ -13,10 +13,6 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) :
audio_node_ = new AudioInput();
viewer_node_ = new ViewerOutput();
NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input());
NodeParam::ConnectEdge(audio_node_->output(), viewer_node_->samples_input());
NodeParam::ConnectEdge(video_node_->output(), viewer_node_->length_input());
connect(gl_widget_, &ViewerGLWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag);
}
@@ -29,6 +25,9 @@ void FootageViewerWidget::SetFootage(Footage *footage)
{
if (footage_) {
ConnectViewerNode(nullptr);
NodeParam::DisconnectEdge(video_node_->output(), viewer_node_->texture_input());
NodeParam::DisconnectEdge(audio_node_->output(), viewer_node_->samples_input());
}
footage_ = footage;
@@ -55,11 +54,13 @@ void FootageViewerWidget::SetFootage(Footage *footage)
if (video_stream) {
video_node_->SetFootage(video_stream);
viewer_node_->set_video_params(VideoParams(video_stream->width(), video_stream->height(), video_stream->frame_rate().flipped()));
NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input());
}
if (audio_stream) {
audio_node_->SetFootage(audio_stream);
viewer_node_->set_audio_params(AudioParams(audio_stream->sample_rate(), audio_stream->channel_layout()));
NodeParam::ConnectEdge(audio_node_->output(), viewer_node_->samples_input());
}
ConnectViewerNode(viewer_node_, footage->project()->color_manager());
@@ -68,13 +69,17 @@ void FootageViewerWidget::SetFootage(Footage *footage)
}
}
TimelinePoints *FootageViewerWidget::ConnectTimelinePoints()
{
return footage_ ? footage_ : nullptr;
}
void FootageViewerWidget::StartFootageDrag()
{
if (!GetFootage()) {
return;
}
qDebug() << "Drag start!";
QDrag* drag = new QDrag(this);
QMimeData* mimedata = new QMimeData();
+3
View File
@@ -15,6 +15,9 @@ public:
Footage* GetFootage() const;
void SetFootage(Footage* footage);
protected:
virtual TimelinePoints* ConnectTimelinePoints() override;
private:
Footage* footage_;
+94 -12
View File
@@ -47,20 +47,30 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(0);
// Create main OpenGL-based view
// Set up stacked widget to allow switching away from the viewer widget
stack_ = new QStackedWidget();
layout->addWidget(stack_);
// Create main OpenGL-based view and sizer
sizer_ = new ViewerSizer();
layout->addWidget(sizer_);
stack_->addWidget(sizer_);
gl_widget_ = new ViewerGLWidget();
connect(gl_widget_, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
connect(sizer_, &ViewerSizer::RequestMatrix, gl_widget_, &ViewerGLWidget::SetMatrix);
sizer_->SetWidget(gl_widget_);
// Create waveform view when audio is connected and video isn't
waveform_view_ = new AudioWaveformView();
stack_->addWidget(waveform_view_);
// Create time ruler
layout->addWidget(ruler());
// Create scrollbar
layout->addWidget(scrollbar());
connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &AudioWaveformView::SetScroll);
// Create lower controls
controls_ = new PlaybackControls();
@@ -85,6 +95,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
connect(video_renderer_, &VideoRenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange);
audio_renderer_ = new AudioBackend(this);
waveform_view_->SetBackend(audio_renderer_);
connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
SetAutoMaxScrollBar(true);
@@ -97,6 +110,7 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
}
controls_->SetTime(i);
waveform_view_->SetTime(i);
if (GetConnectedNode() && last_time_ != i) {
rational time_set = Timecode::timestamp_to_time(i, timebase());
@@ -111,13 +125,21 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
{
SetTimebase(n->video_params().time_base());
if (!n->video_params().time_base().isNull()) {
SetTimebase(n->video_params().time_base());
} else if (n->audio_params().sample_rate() > 0) {
SetTimebase(n->audio_params().time_base());
} else {
SetTimebase(rational());
}
connect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
connect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
connect(n, &ViewerOutput::VideoGraphChanged, this, &ViewerWidget::UpdateStack);
connect(n, &ViewerOutput::AudioGraphChanged, this, &ViewerWidget::UpdateStack);
SizeChangedSlot(n->video_params().width(), n->video_params().height());
LengthChangedSlot(n->Length());
@@ -133,24 +155,34 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
divider_ = CalculateDivider();
UpdateRendererParameters();
UpdateStack();
if (GetConnectedTimelinePoints()) {
waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints());
}
}
void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
{
Pause();
SetTimebase(0);
SetTimebase(rational());
disconnect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
disconnect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
disconnect(n, &ViewerOutput::VideoGraphChanged, this, &ViewerWidget::UpdateStack);
disconnect(n, &ViewerOutput::AudioGraphChanged, this, &ViewerWidget::UpdateStack);
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
gl_widget_->DisconnectColorManager();
waveform_view_->ConnectTimelinePoints(nullptr);
}
void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
@@ -159,6 +191,13 @@ void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
audio_renderer_->SetViewerNode(n);
}
void ViewerWidget::ScaleChangedEvent(const double &s)
{
TimeBasedWidget::ScaleChangedEvent(s);
waveform_view_->SetScale(s);
}
void ViewerWidget::resizeEvent(QResizeEvent *event)
{
TimeBasedWidget::resizeEvent(event);
@@ -169,6 +208,8 @@ void ViewerWidget::resizeEvent(QResizeEvent *event)
UpdateRendererParameters();
}
UpdateMinimumScale();
}
void ViewerWidget::TogglePlayPause()
@@ -251,7 +292,11 @@ void ViewerWidget::PlayInternal(int speed)
controls_->ShowPauseButton();
connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
if (stack_->currentWidget() == sizer_) {
connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
} else {
connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
}
}
void ViewerWidget::PushScrubbedAudio()
@@ -261,8 +306,8 @@ void ViewerWidget::PushScrubbedAudio()
QIODevice* audio_src = audio_renderer_->GetAudioPullDevice();
if (audio_src && audio_src->open(QFile::ReadOnly)) {
// Try to get one "frame" of audio
int size_of_sample = audio_renderer_->params().time_to_bytes(timebase());
// FIXME: Hardcoded scrubbing interval (20ms)
int size_of_sample = audio_renderer_->params().time_to_bytes(rational(20, 1000));
// Push audio
audio_src->seek(audio_renderer_->params().time_to_bytes(GetTime()));
@@ -287,6 +332,24 @@ int ViewerWidget::CalculateDivider()
return divider_;
}
void ViewerWidget::UpdateMinimumScale()
{
if (!GetConnectedNode()) {
return;
}
SetMinimumScale(static_cast<double>(ruler()->width()) / GetConnectedNode()->Length().toDouble());
}
void ViewerWidget::UpdateStack()
{
if (!GetConnectedNode() || GetConnectedNode()->texture_input()->IsConnected()) {
stack_->setCurrentWidget(sizer_);
} else {
stack_->setCurrentWidget(waveform_view_);
}
}
void ViewerWidget::UpdateRendererParameters()
{
if (!GetConnectedNode()) {
@@ -359,12 +422,21 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
// Playback resolution
QMenu* playback_resolution_menu = menu.addMenu(tr("Resolution"));
playback_resolution_menu->addAction(tr("Full"))->setData(1);
playback_resolution_menu->addAction(tr("1/2"))->setData(2);
playback_resolution_menu->addAction(tr("1/4"))->setData(4);
playback_resolution_menu->addAction(tr("1/8"))->setData(8);
playback_resolution_menu->addAction(tr("1/16"))->setData(16);
int dividers[] = {2, 4, 8, 16};
for (int i=0;i<4;i++) {
playback_resolution_menu->addAction(tr("1/%1").arg(dividers[i]))->setData(dividers[i]);
}
connect(playback_resolution_menu, &QMenu::triggered, this, &ViewerWidget::SetDividerFromMenu);
// Viewer Zoom Level
QMenu* zoom_menu = menu.addMenu(tr("Zoom"));
int zoom_levels[] = {10, 25, 50, 75, 100, 150, 200, 400};
zoom_menu->addAction(tr("Fit"))->setData(0);
for (int i=0;i<8;i++) {
zoom_menu->addAction(tr("%1%").arg(zoom_levels[i]))->setData(zoom_levels[i]);
}
connect(zoom_menu, &QMenu::triggered, this, &ViewerWidget::SetZoomFromMenu);
foreach (QAction* a, playback_resolution_menu->actions()) {
a->setCheckable(true);
if (a->data() == divider_) {
@@ -387,7 +459,11 @@ void ViewerWidget::Pause()
playback_speed_ = 0;
controls_->ShowPlayButton();
disconnect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
if (stack_->currentWidget() == sizer_) {
disconnect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
} else {
disconnect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
}
}
}
@@ -495,6 +571,7 @@ void ViewerWidget::LengthChangedSlot(const rational &length)
{
controls_->SetEndTime(Timecode::time_to_timestamp(length, timebase()));
ruler()->SetCacheStatusLength(length);
UpdateMinimumScale();
}
void ViewerWidget::ColorDisplayChanged(QAction* action)
@@ -526,6 +603,11 @@ void ViewerWidget::SetDividerFromMenu(QAction *action)
UpdateRendererParameters();
}
void ViewerWidget::SetZoomFromMenu(QAction *action)
{
sizer_->SetZoom(action->data().toInt());
}
void ViewerWidget::InvalidateVisible()
{
video_renderer_->InvalidateCache(TimeRange(GetTime(), GetTime()));
+13
View File
@@ -28,6 +28,7 @@
#include <QTimer>
#include <QWidget>
#include "audiowaveformview.h"
#include "common/rational.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/opengl/openglbackend.h"
@@ -113,6 +114,8 @@ protected:
virtual void DisconnectNodeInternal(ViewerOutput *) override;
virtual void ConnectedNodeChanged(ViewerOutput*n) override;
virtual void ScaleChangedEvent(const double& s) override;
virtual void resizeEvent(QResizeEvent *event) override;
OpenGLBackend* video_renderer_;
@@ -131,6 +134,10 @@ private:
int CalculateDivider();
void UpdateMinimumScale();
QStackedWidget* stack_;
ViewerSizer* sizer_;
PlaybackControls* controls_;
@@ -152,6 +159,8 @@ private:
bool time_changed_from_timer_;
AudioWaveformView* waveform_view_;
private slots:
void PlaybackTimerUpdate();
@@ -182,8 +191,12 @@ private slots:
void SetDividerFromMenu(QAction* action);
void SetZoomFromMenu(QAction* action);
void InvalidateVisible();
void UpdateStack();
};
#endif // VIEWER_WIDGET_H
+8
View File
@@ -129,6 +129,14 @@ void ViewerGLWidget::SetImage(const QString &fn)
void ViewerGLWidget::SetOCIODisplay(const QString &display)
{
ocio_display_ = display;
// Determine if the selected view is available in this display
if (color_manager_
&& !color_manager_->ListAvailableViews(ocio_display_).contains(ocio_view_)) {
// If not, set to the default view for this display
ocio_view_ = color_manager_->GetDefaultView(ocio_display_);
}
SetupColorProcessor();
update();
}
+12 -12
View File
@@ -68,18 +68,17 @@ public:
*/
void DisconnectColorManager();
/**
* @brief Set the transformation matrix to draw with
*
* Set this if you want the drawing to pass through some sort of transform (most of the time you won't want this).
*/
void SetMatrix(const QMatrix4x4& mat);
/**
* @brief Set an image to load and display on screen
*/
void SetImage(const QString& fn);
ColorManager* color_manager() const;
const QString& ocio_display() const;
const QString& ocio_view() const;
const QString& ocio_look() const;
public slots:
/**
* @brief Set the texture to draw and draw it
@@ -113,11 +112,12 @@ public slots:
*/
void SetOCIOLook(const QString& look);
ColorManager* color_manager() const;
const QString& ocio_display() const;
const QString& ocio_view() const;
const QString& ocio_look() const;
/**
* @brief Set the transformation matrix to draw with
*
* Set this if you want the drawing to pass through some sort of transform (most of the time you won't want this).
*/
void SetMatrix(const QMatrix4x4& mat);
signals:
void DragStarted();
+57 -14
View File
@@ -1,9 +1,12 @@
#include "viewersizer.h"
#include <QMatrix4x4>
ViewerSizer::ViewerSizer(QWidget *parent) :
QWidget(parent),
widget_(nullptr),
aspect_ratio_(0)
aspect_ratio_(0),
zoom_(0)
{
}
@@ -23,15 +26,25 @@ void ViewerSizer::SetWidget(QWidget *widget)
void ViewerSizer::SetChildSize(int width, int height)
{
if (height == 0) {
width_ = width;
height_ = height;
if (!width_ || !height_) {
aspect_ratio_ = 0;
} else {
aspect_ratio_ = static_cast<double>(width) / static_cast<double>(height);
aspect_ratio_ = static_cast<double>(width_) / static_cast<double>(height_);
}
UpdateSize();
}
void ViewerSizer::SetZoom(int percent)
{
zoom_ = percent;
UpdateSize();
}
void ViewerSizer::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
@@ -53,21 +66,51 @@ void ViewerSizer::UpdateSize()
widget_->setVisible(true);
double our_aspect_ratio = static_cast<double>(width()) / static_cast<double>(height());
QSize child_size;
QMatrix4x4 child_matrix;
QPoint child_pos;
QSize child_size = size();
if (zoom_ <= 0) {
// If zoom is 0, we auto-fit
double our_aspect_ratio = static_cast<double>(width()) / static_cast<double>(height());
child_size = size();
if (our_aspect_ratio > aspect_ratio_) {
// This container is wider than the image, scale by height
child_size = QSize(qRound(child_size.height() * aspect_ratio_), height());
} else {
// This container is taller than the image, scale by width
child_size = QSize(width(), qRound(child_size.width() / aspect_ratio_));
}
if (our_aspect_ratio > aspect_ratio_) {
// This container is wider than the image, scale by height
child_size.setWidth(qRound(child_size.height() * aspect_ratio_));
child_pos.setX(width() / 2 - child_size.width() / 2);
} else {
// This container is taller than the image, scale by width
child_size.setHeight(qRound(child_size.width() / aspect_ratio_));
child_pos.setY(height() / 2 - child_size.height() / 2);
float x_scale = 1.0f;
float y_scale = 1.0f;
int zoomed_width = qRound(width_ * static_cast<double>(zoom_) * 0.01);
int zoomed_height = qRound(height_ * static_cast<double>(zoom_) * 0.01);
if (zoomed_width > width()) {
x_scale = static_cast<double>(zoomed_width) / static_cast<double>(width());
zoomed_width = width();
}
if (zoomed_height > height()) {
y_scale = static_cast<double>(zoomed_height) / static_cast<double>(height());
zoomed_height = height();
}
// Rather than make a huge surface, we still crop at our width/height and then signal a matrix
child_matrix.scale(x_scale, y_scale, 1.0F);
child_size = QSize(zoomed_width, zoomed_height);
}
widget_->resize(child_size);
widget_->move(child_pos);
widget_->move(width() / 2 - child_size.width() / 2, height() / 2 - child_size.height() / 2);
emit RequestMatrix(child_matrix);
}
+21
View File
@@ -52,6 +52,16 @@ public:
*/
void SetChildSize(int width, int height);
/**
* @brief Set the zoom value of the child widget
*
* The number is an integer percentage (100 = 100%). Set to 0 to auto-fit.
*/
void SetZoom(int percent);
signals:
void RequestMatrix(const QMatrix4x4& matrix);
protected:
/**
* @brief Listen for resize events to ensure the child widget remains correctly sized
@@ -71,11 +81,22 @@ private:
*/
QWidget* widget_;
/**
* @brief Internal resolution values
*/
int width_;
int height_;
/**
* @brief Aspect ratio calculated from the size provided by SetChildSize()
*/
double aspect_ratio_;
/**
* @brief Internal zoom value
*/
int zoom_;
};
#endif // VIEWERSIZER_H