Merge branch 'master' into typos
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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(©_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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -623,14 +623,14 @@ void BlockSetSpeedCommand::undo_internal()
|
||||
block_->set_speed(old_speed_);
|
||||
}
|
||||
|
||||
TimelineRippleDeleteGapsAtRegions::TimelineRippleDeleteGapsAtRegions(ViewerOutput *vo, const TimeRangeList ®ions, QUndoCommand *parent) :
|
||||
TimelineRippleDeleteGapsAtRegionsCommand::TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput *vo, const TimeRangeList ®ions, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ public:
|
||||
Footage* GetFootage() const;
|
||||
void SetFootage(Footage* footage);
|
||||
|
||||
protected:
|
||||
virtual TimelinePoints* ConnectTimelinePoints() override;
|
||||
|
||||
private:
|
||||
Footage* footage_;
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user