Merge branch 'master' into pr/1717
This commit is contained in:
@@ -41,7 +41,6 @@ ColorLabelMenuItem::ColorLabelMenuItem(QWidget* parent) :
|
||||
layout->addWidget(box_);
|
||||
|
||||
label_ = new QLabel();
|
||||
StyleManager::UseOSNativeStyling(label_);
|
||||
layout->addWidget(label_);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,9 +106,6 @@ void Menu::Init()
|
||||
QPalette p = palette();
|
||||
p.setColor(QPalette::Disabled, QPalette::Light, QColor(0, 0, 0, 0));
|
||||
setPalette(p);
|
||||
|
||||
// If a native palette is available, it should override the one above
|
||||
StyleManager::UseOSNativeStyling(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -137,6 +137,13 @@ void MenuShared::AddItemsForEditMenu(Menu *m, bool for_clips)
|
||||
m->addAction(edit_ripple_delete_item_);
|
||||
m->addAction(edit_split_item_);
|
||||
m->addAction(edit_speedduration_item_);
|
||||
|
||||
m->addSeparator();
|
||||
|
||||
m->addAction(clip_add_default_transition_item_);
|
||||
m->addAction(clip_link_unlink_item_);
|
||||
m->addAction(clip_enable_disable_item_);
|
||||
m->addAction(clip_nest_item_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "common/functiontimer.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -37,9 +38,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
|
||||
super(true, false, parent),
|
||||
last_scroll_val_(0),
|
||||
focused_node_(nullptr),
|
||||
create_checkboxes_(kNoCheckBoxes),
|
||||
time_target_(nullptr),
|
||||
ignore_flags_(false)
|
||||
time_target_(nullptr)
|
||||
{
|
||||
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
@@ -76,6 +75,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
|
||||
for (int i=0; i<context_items_.size(); i++) {
|
||||
NodeParamViewContext *c = new NodeParamViewContext;
|
||||
c->setVisible(false);
|
||||
connect(c, &NodeParamViewContext::AboutToDeleteItem, this, &NodeParamView::ItemAboutToBeRemoved, Qt::DirectConnection);
|
||||
|
||||
NodeParamViewItemTitleBar *title_bar = static_cast<NodeParamViewItemTitleBar*>(c->titleBarWidget());
|
||||
|
||||
@@ -236,62 +236,75 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
}
|
||||
}*/
|
||||
|
||||
void NodeParamView::SetInputChecked(const NodeInput &input, bool e)
|
||||
void NodeParamView::UpdateContexts()
|
||||
{
|
||||
input_checked_.insert(input, e);
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
ctx->SetInputChecked(input, e);
|
||||
//TIME_THIS_FUNCTION;
|
||||
|
||||
bool changes_made = false;
|
||||
|
||||
foreach (Node *ctx, current_contexts_) {
|
||||
if (!contexts_.contains(ctx)) {
|
||||
// Context is being removed
|
||||
RemoveContext(ctx);
|
||||
changes_made = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
if (!current_contexts_.contains(ctx)) {
|
||||
// Context is being added
|
||||
AddContext(ctx);
|
||||
changes_made = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes_made) {
|
||||
current_contexts_ = contexts_;
|
||||
|
||||
if (IsGroupMode()) {
|
||||
// Check inputs that have been passed through
|
||||
NodeGroup *group = static_cast<NodeGroup*>(contexts_.first());
|
||||
for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) {
|
||||
GroupInputPassthroughAdded(group, it->second);
|
||||
}
|
||||
|
||||
connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeParamView::GroupInputPassthroughAdded);
|
||||
connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeParamView::GroupInputPassthroughRemoved);
|
||||
}
|
||||
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
SortItemsInContext(ctx);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
QueueKeyframePositionUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::ItemAboutToBeRemoved(NodeParamViewItem *item)
|
||||
{
|
||||
if (keyframe_view_) {
|
||||
for (auto it=item->GetKeyframeConnections().begin(); it!=item->GetKeyframeConnections().end(); it++) {
|
||||
for (auto jt=it->begin(); jt!=it->end(); jt++) {
|
||||
for (auto kt=jt->begin(); kt!=jt->end(); kt++) {
|
||||
keyframe_view_->RemoveKeyframesOfTrack(*kt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (focused_node_ == item) {
|
||||
focused_node_ = nullptr;
|
||||
emit FocusedNodeChanged(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::SetContexts(const QVector<Node *> &contexts)
|
||||
{
|
||||
//TIME_THIS_FUNCTION;
|
||||
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
ctx->Clear();
|
||||
ctx->setVisible(false);
|
||||
}
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
disconnect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext);
|
||||
}
|
||||
|
||||
// Setting contexts is expensive, so we queue it here to prevent multiple calls in a short timespan
|
||||
contexts_ = contexts;
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
// Queued so that if any further work is done in connecting this node to the context, it'll be
|
||||
// done before our sorting function is called
|
||||
connect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->Clear();
|
||||
}
|
||||
|
||||
if (focused_node_) {
|
||||
focused_node_ = nullptr;
|
||||
emit FocusedNodeChanged(nullptr);
|
||||
}
|
||||
|
||||
foreach (Node *ctx, contexts) {
|
||||
NodeParamViewContext *item = GetContextItemFromContext(ctx);
|
||||
|
||||
item->AddContext(ctx);
|
||||
item->setVisible(true);
|
||||
|
||||
for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) {
|
||||
AddNode(it.key(), item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
SortItemsInContext(ctx);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
QueueKeyframePositionUpdate();
|
||||
}
|
||||
UpdateContexts();
|
||||
}
|
||||
|
||||
void NodeParamView::resizeEvent(QResizeEvent *event)
|
||||
@@ -357,8 +370,28 @@ Node *NodeParamView::GetTimeTarget() const
|
||||
|
||||
void NodeParamView::DeleteSelected()
|
||||
{
|
||||
if (keyframe_view_) {
|
||||
if (keyframe_view_ && keyframe_view_->hasFocus()) {
|
||||
keyframe_view_->DeleteSelected();
|
||||
} else if (focused_node_) {
|
||||
MultiUndoCommand *c = new MultiUndoCommand();
|
||||
Node *n = focused_node_->GetNode();
|
||||
|
||||
// Create command to delete node from context and/or graph
|
||||
NodeViewDeleteCommand *dc = new NodeViewDeleteCommand();
|
||||
dc->AddNode(n, focused_node_->GetContext());
|
||||
c->add_child(dc);
|
||||
|
||||
// Copy any outputs that were connected
|
||||
if (n->GetEffectInput().IsValid()) {
|
||||
if (Node *out = n->GetEffectInput().GetConnectedOutput()) {
|
||||
for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) {
|
||||
c->add_child(new NodeEdgeAddCommand(out, it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Core::instance()->undo_stack()->push(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,27 +417,52 @@ void NodeParamView::QueueKeyframePositionUpdate()
|
||||
QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void NodeParamView::AddNode(Node *n, NodeParamViewContext *context)
|
||||
void NodeParamView::AddContext(Node *ctx)
|
||||
{
|
||||
if ((n->GetFlags() & Node::kDontShowInParamView) && !ignore_flags_) {
|
||||
// Queued so that if any further work is done in connecting this node to the context, it'll be
|
||||
// done before our sorting function is called
|
||||
connect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext, Qt::QueuedConnection);
|
||||
connect(ctx, &Node::NodeRemovedFromContext, this, &NodeParamView::NodeRemovedFromContext, Qt::QueuedConnection);
|
||||
|
||||
NodeParamViewContext *item = GetContextItemFromContext(ctx);
|
||||
|
||||
item->AddContext(ctx);
|
||||
item->setVisible(true);
|
||||
|
||||
for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) {
|
||||
AddNode(it.key(), ctx, item);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::RemoveContext(Node *ctx)
|
||||
{
|
||||
disconnect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext);
|
||||
disconnect(ctx, &Node::NodeRemovedFromContext, this, &NodeParamView::NodeRemovedFromContext);
|
||||
|
||||
foreach (NodeParamViewContext *item, context_items_) {
|
||||
item->RemoveContext(ctx);
|
||||
item->RemoveNodesWithContext(ctx);
|
||||
|
||||
if (item->GetContexts().isEmpty()) {
|
||||
item->setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context)
|
||||
{
|
||||
if ((n->GetFlags() & Node::kDontShowInParamView) && !IsGroupMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context);
|
||||
NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context);
|
||||
|
||||
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
|
||||
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
|
||||
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
|
||||
connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked);
|
||||
|
||||
if (create_checkboxes_) {
|
||||
for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) {
|
||||
if (it.key().node() == n) {
|
||||
item->SetInputChecked(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::InputCheckBoxChanged);
|
||||
|
||||
item->SetContext(ctx);
|
||||
item->SetTimeTarget(GetTimeTarget());
|
||||
item->SetTimebase(timebase());
|
||||
item->SetTime(GetTime());
|
||||
@@ -449,9 +507,11 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item)
|
||||
QVector<QPair<NodeParamViewItem*, int> > distances;
|
||||
|
||||
for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) {
|
||||
NodeParamViewItem *item = *it;
|
||||
|
||||
int distance = -1;
|
||||
foreach (Node *ctx, context_item->GetContexts()) {
|
||||
distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key()));
|
||||
distance = qMax(distance, GetDistanceBetweenNodes(ctx, item->GetNode()));
|
||||
}
|
||||
|
||||
if (distance == -1) {
|
||||
@@ -459,7 +519,7 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item)
|
||||
}
|
||||
|
||||
bool inserted = false;
|
||||
QPair<NodeParamViewItem*, int> dist(it.value(), distance);
|
||||
QPair<NodeParamViewItem*, int> dist(item, distance);
|
||||
|
||||
for (int i=0; i<distances.size(); i++) {
|
||||
if (distances.at(i).second < distance) {
|
||||
@@ -567,25 +627,26 @@ void NodeParamView::KeyframeViewDragged(int x, int y)
|
||||
{
|
||||
Q_UNUSED(y)
|
||||
|
||||
QMetaObject::invokeMethod(this, "CatchUpScrollToPoint", Qt::QueuedConnection,
|
||||
Q_ARG(int, x));
|
||||
QMetaObject::invokeMethod(this, "CatchUpScrollToPoint", Qt::QueuedConnection, Q_ARG(int, x));
|
||||
}
|
||||
|
||||
void NodeParamView::UpdateElementY()
|
||||
{
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) {
|
||||
const KeyframeView::NodeConnections &connections = it.value()->GetKeyframeConnections();
|
||||
NodeParamViewItem *item = *it;
|
||||
Node *node = item->GetNode();
|
||||
const KeyframeView::NodeConnections &connections = item->GetKeyframeConnections();
|
||||
|
||||
if (!connections.isEmpty()) {
|
||||
foreach (const QString& input, it.key()->inputs()) {
|
||||
if (!(it.key()->GetInputFlags(input) & kInputFlagHidden)) {
|
||||
int arr_sz = NodeGroup::ResolveInput(NodeInput(it.key(), input)).GetArraySize();
|
||||
foreach (const QString& input, node->inputs()) {
|
||||
if (!(node->GetInputFlags(input) & kInputFlagHidden)) {
|
||||
int arr_sz = NodeGroup::ResolveInput(NodeInput(node, input)).GetArraySize();
|
||||
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
NodeInput ic = {it.key(), input, i};
|
||||
NodeInput ic = {node, input, i};
|
||||
|
||||
int y = it.value()->GetElementY(ic);
|
||||
int y = item->GetElementY(ic);
|
||||
|
||||
// For some reason Qt's mapToGlobal doesn't seem to handle this, so we offset here
|
||||
y += vertical_scrollbar_->value();
|
||||
@@ -611,9 +672,51 @@ void NodeParamView::NodeAddedToContext(Node *n)
|
||||
Node *ctx = static_cast<Node*>(sender());
|
||||
NodeParamViewContext *item = GetContextItemFromContext(ctx);
|
||||
|
||||
AddNode(n, item);
|
||||
AddNode(n, ctx, item);
|
||||
|
||||
SortItemsInContext(item);
|
||||
|
||||
if (keyframe_view_) {
|
||||
QueueKeyframePositionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::NodeRemovedFromContext(Node *n)
|
||||
{
|
||||
Node *ctx = static_cast<Node*>(sender());
|
||||
|
||||
foreach (NodeParamViewContext *ctx_item, context_items_) {
|
||||
ctx_item->RemoveNode(n, ctx);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
QueueKeyframePositionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::InputCheckBoxChanged(const NodeInput &input, bool e)
|
||||
{
|
||||
NodeGroup *group = static_cast<NodeGroup*>(contexts_.first());
|
||||
|
||||
if (e) {
|
||||
group->AddInputPassthrough(input);
|
||||
} else {
|
||||
group->RemoveInputPassthrough(input);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::GroupInputPassthroughAdded(NodeGroup *group, const NodeInput &input)
|
||||
{
|
||||
foreach (NodeParamViewContext *pvctx, context_items_) {
|
||||
pvctx->SetInputChecked(input, true);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::GroupInputPassthroughRemoved(NodeGroup *group, const NodeInput &input)
|
||||
{
|
||||
foreach (NodeParamViewContext *pvctx, context_items_) {
|
||||
pvctx->SetInputChecked(input, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,16 +45,6 @@ public:
|
||||
|
||||
virtual ~NodeParamView() override;
|
||||
|
||||
void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
|
||||
{
|
||||
create_checkboxes_ = e;
|
||||
}
|
||||
|
||||
bool IsInputChecked(const NodeInput &input) const
|
||||
{
|
||||
return input_checked_.value(input);
|
||||
}
|
||||
|
||||
void CloseContextsBelongingToProject(Project *p);
|
||||
|
||||
Node* GetTimeTarget() const;
|
||||
@@ -71,11 +61,6 @@ public:
|
||||
keyframe_view_->DeselectAll();
|
||||
}
|
||||
|
||||
void SetIgnoreNodeFlags(bool e)
|
||||
{
|
||||
ignore_flags_ = e;
|
||||
}
|
||||
|
||||
void SelectNodes(const QVector<Node*> &nodes);
|
||||
void DeselectNodes(const QVector<Node*> &nodes);
|
||||
|
||||
@@ -85,8 +70,6 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
void SetContexts(const QVector<Node*> &contexts);
|
||||
|
||||
void UpdateElementY();
|
||||
@@ -110,12 +93,21 @@ private:
|
||||
|
||||
void QueueKeyframePositionUpdate();
|
||||
|
||||
void AddNode(Node* n, NodeParamViewContext *context);
|
||||
void AddContext(Node *context);
|
||||
|
||||
void RemoveContext(Node *context);
|
||||
|
||||
void AddNode(Node* n, Node *ctx, NodeParamViewContext *context);
|
||||
|
||||
void SortItemsInContext(NodeParamViewContext *context);
|
||||
|
||||
NodeParamViewContext *GetContextItemFromContext(Node *context);
|
||||
|
||||
bool IsGroupMode() const
|
||||
{
|
||||
return contexts_.size() == 1 && dynamic_cast<NodeGroup*>(contexts_.first());
|
||||
}
|
||||
|
||||
KeyframeView* keyframe_view_;
|
||||
|
||||
QVector<NodeParamViewContext*> context_items_;
|
||||
@@ -136,15 +128,10 @@ private:
|
||||
|
||||
NodeParamViewItem* focused_node_;
|
||||
|
||||
NodeParamViewCheckBoxBehavior create_checkboxes_;
|
||||
|
||||
Node *time_target_;
|
||||
|
||||
QHash<NodeInput, bool> input_checked_;
|
||||
|
||||
bool ignore_flags_;
|
||||
|
||||
QVector<Node*> contexts_;
|
||||
QVector<Node*> current_contexts_;
|
||||
|
||||
private slots:
|
||||
void UpdateGlobalScrollBar();
|
||||
@@ -157,6 +144,18 @@ private slots:
|
||||
|
||||
void NodeAddedToContext(Node *n);
|
||||
|
||||
void NodeRemovedFromContext(Node *n);
|
||||
|
||||
void InputCheckBoxChanged(const NodeInput &input, bool e);
|
||||
|
||||
void GroupInputPassthroughAdded(olive::NodeGroup *group, const olive::NodeInput &input);
|
||||
|
||||
void GroupInputPassthroughRemoved(olive::NodeGroup *group, const olive::NodeInput &input);
|
||||
|
||||
void UpdateContexts();
|
||||
|
||||
void ItemAboutToBeRemoved(NodeParamViewItem *item);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "node/factory.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -45,28 +47,61 @@ NodeParamViewContext::NodeParamViewContext(QWidget *parent) :
|
||||
connect(title_bar(), &NodeParamViewItemTitleBar::AddEffectButtonClicked, this, &NodeParamViewContext::AddEffectButtonClicked);
|
||||
}
|
||||
|
||||
NodeParamViewItem *NodeParamViewContext::GetItem(Node *node, Node *ctx)
|
||||
{
|
||||
for (auto it=items_.begin(); it!=items_.end(); it++) {
|
||||
NodeParamViewItem *item = *it;
|
||||
|
||||
if (item->GetNode() == node && item->GetContext() == ctx) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NodeParamViewContext::AddNode(NodeParamViewItem *item)
|
||||
{
|
||||
items_.insert(item->GetNode(), item);
|
||||
items_.append(item);
|
||||
dock_area_->AddItem(item);
|
||||
}
|
||||
|
||||
void NodeParamViewContext::RemoveNode(Node *node)
|
||||
void NodeParamViewContext::RemoveNode(Node *node, Node *ctx)
|
||||
{
|
||||
for (auto it=items_.begin(); it!=items_.end(); ) {
|
||||
NodeParamViewItem *item = *it;
|
||||
|
||||
if (item->GetNode() == node && item->GetContext() == ctx) {
|
||||
emit AboutToDeleteItem(item);
|
||||
delete item;
|
||||
it = items_.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewContext::Clear()
|
||||
void NodeParamViewContext::RemoveNodesWithContext(Node *ctx)
|
||||
{
|
||||
qDeleteAll(items_);
|
||||
items_.clear();
|
||||
for (auto it=items_.begin(); it!=items_.end(); ) {
|
||||
NodeParamViewItem *item = *it;
|
||||
|
||||
contexts_.clear();
|
||||
if (item->GetContext() == ctx) {
|
||||
emit AboutToDeleteItem(item);
|
||||
delete item;
|
||||
it = items_.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewContext::SetInputChecked(const NodeInput &input, bool e)
|
||||
{
|
||||
if (NodeParamViewItem *item = items_.value(input.node())) {
|
||||
item->SetInputChecked(input, e);
|
||||
foreach (NodeParamViewItem *item, items_) {
|
||||
if (item->GetNode() == input.node()) {
|
||||
item->SetInputChecked(input, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +132,45 @@ void NodeParamViewContext::Retranslate()
|
||||
|
||||
void NodeParamViewContext::AddEffectButtonClicked()
|
||||
{
|
||||
QMessageBox::information(this, tr("STUB"), tr("This feature is coming soon. Thanks for testing development builds of Olive :)"));
|
||||
Menu *m = NodeFactory::CreateMenu(this, false, Node::kCategoryUnknown, Node::kVideoEffect);
|
||||
connect(m, &Menu::triggered, this, &NodeParamViewContext::AddEffectMenuItemTriggered);
|
||||
m->exec(QCursor::pos());
|
||||
delete m;
|
||||
}
|
||||
|
||||
void NodeParamViewContext::AddEffectMenuItemTriggered(QAction *a)
|
||||
{
|
||||
Node *n = NodeFactory::CreateFromMenuAction(a);
|
||||
|
||||
if (n) {
|
||||
NodeInput new_node_input = n->GetEffectInput();
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
QVector<NodeGraph*> graphs_added_to;
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
NodeInput ctx_input = ctx->GetEffectInput();
|
||||
|
||||
if (!graphs_added_to.contains(ctx->parent())) {
|
||||
command->add_child(new NodeAddCommand(ctx->parent(), n));
|
||||
graphs_added_to.append(ctx->parent());
|
||||
}
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(n, ctx, ctx->GetNodePositionInContext(ctx)));
|
||||
command->add_child(new NodeSetPositionCommand(ctx, ctx, ctx->GetNodePositionInContext(ctx) + QPointF(1, 0)));
|
||||
|
||||
if (ctx_input.IsConnected()) {
|
||||
Node *prev_output = ctx_input.GetConnectedOutput();
|
||||
|
||||
command->add_child(new NodeEdgeRemoveCommand(prev_output, ctx_input));
|
||||
command->add_child(new NodeEdgeAddCommand(prev_output, new_node_input));
|
||||
}
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(n, ctx_input));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,16 +43,18 @@ public:
|
||||
return contexts_;
|
||||
}
|
||||
|
||||
const QMap<Node*, NodeParamViewItem*> &GetItems() const
|
||||
const QVector<NodeParamViewItem*> &GetItems() const
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
|
||||
NodeParamViewItem *GetItem(Node *node, Node *ctx);
|
||||
|
||||
void AddNode(NodeParamViewItem *item);
|
||||
|
||||
void RemoveNode(Node *node);
|
||||
void RemoveNode(Node *node, Node *ctx);
|
||||
|
||||
void Clear();
|
||||
void RemoveNodesWithContext(Node *ctx);
|
||||
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
@@ -62,6 +64,9 @@ public:
|
||||
|
||||
void SetTime(const rational &time);
|
||||
|
||||
signals:
|
||||
void AboutToDeleteItem(NodeParamViewItem *item);
|
||||
|
||||
public slots:
|
||||
void AddContext(Node *node)
|
||||
{
|
||||
@@ -81,11 +86,13 @@ private:
|
||||
|
||||
QVector<Node*> contexts_;
|
||||
|
||||
QMap<Node*, NodeParamViewItem*> items_;
|
||||
QVector<NodeParamViewItem*> items_;
|
||||
|
||||
private slots:
|
||||
void AddEffectButtonClicked();
|
||||
|
||||
void AddEffectMenuItemTriggered(QAction *a);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -40,12 +40,14 @@ const int NodeParamViewItemBody::kOptionalCheckBox = 0;
|
||||
const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1;
|
||||
const int NodeParamViewItemBody::kLabelColumn = 2;
|
||||
const int NodeParamViewItemBody::kWidgetStartColumn = 3;
|
||||
const int NodeParamViewItemBody::kMaxWidgetColumn = kKeyControlColumn;
|
||||
|
||||
#define super NodeParamViewItemBase
|
||||
|
||||
NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) :
|
||||
super(parent),
|
||||
node_(node)
|
||||
node_(node),
|
||||
ctx_(nullptr)
|
||||
{
|
||||
node_->Retranslate();
|
||||
|
||||
@@ -61,6 +63,11 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c
|
||||
|
||||
setBackgroundRole(QPalette::Window);
|
||||
|
||||
// Connect title bar enabled checkbox
|
||||
//title_bar()->SetEnabledCheckBoxVisible(true);
|
||||
//title_bar()->SetEnabledCheckBoxChecked(node_->IsEnabled());
|
||||
//connect(title_bar(), &NodeParamViewItemTitleBar::EnabledCheckBoxClicked, node_, &Node::SetEnabled);
|
||||
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
@@ -216,13 +223,6 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
// Place widgets into layout
|
||||
PlaceWidgetsFromBridge(layout, ui_objects.widget_bridge, row);
|
||||
|
||||
// Add widgets for this parameter to the layout
|
||||
for (int i=0; i<ui_objects.widget_bridge->widgets().size(); i++) {
|
||||
QWidget* w = ui_objects.widget_bridge->widgets().at(i);
|
||||
|
||||
layout->addWidget(w, row, i+kWidgetStartColumn);
|
||||
}
|
||||
|
||||
// In case this input is a group, resolve that actual input to use for connected labels
|
||||
NodeInput resolved = NodeGroup::ResolveInput(input_ref);
|
||||
|
||||
@@ -355,7 +355,17 @@ void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodePara
|
||||
for (int i=0; i<bridge->widgets().size(); i++) {
|
||||
QWidget* w = bridge->widgets().at(i);
|
||||
|
||||
layout->addWidget(w, row, i+kWidgetStartColumn);
|
||||
int col = i+kWidgetStartColumn;
|
||||
|
||||
int colspan;
|
||||
if (i == bridge->widgets().size()-1) {
|
||||
// Span this widget among remaining columns
|
||||
colspan = kMaxWidgetColumn - col;
|
||||
} else {
|
||||
colspan = 1;
|
||||
}
|
||||
|
||||
layout->addWidget(w, row, col, 1, colspan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ private:
|
||||
static const int kArrayCollapseBtnColumn;
|
||||
static const int kLabelColumn;
|
||||
static const int kWidgetStartColumn;
|
||||
static const int kMaxWidgetColumn;
|
||||
|
||||
private slots:
|
||||
void EdgeChanged(Node *output, const NodeInput &input);
|
||||
@@ -181,6 +182,16 @@ public:
|
||||
body_->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
Node *GetContext() const
|
||||
{
|
||||
return ctx_;
|
||||
}
|
||||
|
||||
void SetContext(Node *ctx)
|
||||
{
|
||||
ctx_ = ctx;
|
||||
}
|
||||
|
||||
Node* GetNode() const
|
||||
{
|
||||
return node_;
|
||||
@@ -217,6 +228,8 @@ private:
|
||||
|
||||
Node* node_;
|
||||
|
||||
Node *ctx_;
|
||||
|
||||
rational time_;
|
||||
|
||||
KeyframeView::NodeConnections keyframe_connections_;
|
||||
|
||||
@@ -56,6 +56,11 @@ NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
|
||||
pin_btn_->setVisible(false);
|
||||
layout->addWidget(pin_btn_);
|
||||
connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
|
||||
|
||||
enabled_checkbox_ = new QCheckBox();
|
||||
enabled_checkbox_->setVisible(false);
|
||||
layout->addWidget(enabled_checkbox_);
|
||||
connect(enabled_checkbox_, &QCheckBox::clicked, this, &NodeParamViewItemTitleBar::EnabledCheckBoxClicked);
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::SetExpanded(bool e)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#ifndef NODEPARAMVIEWITEMTITLEBAR_H
|
||||
#define NODEPARAMVIEWITEMTITLEBAR_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -59,6 +60,16 @@ public slots:
|
||||
add_fx_btn_->setVisible(e);
|
||||
}
|
||||
|
||||
void SetEnabledCheckBoxVisible(bool e)
|
||||
{
|
||||
enabled_checkbox_->setVisible(e);
|
||||
}
|
||||
|
||||
void SetEnabledCheckBoxChecked(bool e)
|
||||
{
|
||||
enabled_checkbox_->setChecked(e);
|
||||
}
|
||||
|
||||
signals:
|
||||
void ExpandedStateChanged(bool e);
|
||||
|
||||
@@ -66,6 +77,8 @@ signals:
|
||||
|
||||
void AddEffectButtonClicked();
|
||||
|
||||
void EnabledCheckBoxClicked(bool e);
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
@@ -82,6 +95,8 @@ private:
|
||||
|
||||
QPushButton *add_fx_btn_;
|
||||
|
||||
QCheckBox *enabled_checkbox_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
case NodeValue::kGenerateJob:
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kAudioParams:
|
||||
case NodeValue::kDataTypeCount:
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
@@ -198,40 +199,7 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track)
|
||||
|
||||
void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key)
|
||||
{
|
||||
if (GetInnerInput().IsKeyframing()) {
|
||||
rational node_time = GetCurrentTimeAsNodeTime();
|
||||
|
||||
NodeKeyframe* existing_key = GetInnerInput().GetKeyframeAtTimeOnTrack(node_time, track);
|
||||
|
||||
if (existing_key) {
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value));
|
||||
} else {
|
||||
// No existing key, create a new one
|
||||
int nb_tracks = NodeValue::get_number_of_keyframe_tracks(GetInnerInput().node()->GetInputDataType(GetInnerInput().input()));
|
||||
for (int i=0; i<nb_tracks; i++) {
|
||||
QVariant track_value;
|
||||
|
||||
if (i == track) {
|
||||
track_value = value;
|
||||
} else if (!insert_on_all_tracks_if_no_key) {
|
||||
continue;
|
||||
} else {
|
||||
track_value = GetInnerInput().node()->GetSplitValueAtTimeOnTrack(GetInnerInput().input(), node_time, i, GetInnerInput().element());
|
||||
}
|
||||
|
||||
NodeKeyframe* new_key = new NodeKeyframe(node_time,
|
||||
track_value,
|
||||
GetInnerInput().node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(GetInnerInput(), i), node_time),
|
||||
i,
|
||||
GetInnerInput().element(),
|
||||
GetInnerInput().input());
|
||||
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(GetInnerInput().node(), new_key));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(GetInnerInput(), track), value));
|
||||
}
|
||||
Node::SetValueAtTime(GetInnerInput(), GetCurrentTimeAsNodeTime(), value, track, command, insert_on_all_tracks_if_no_key);
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, int slider_track, const QVariant &value)
|
||||
@@ -278,6 +246,7 @@ void NodeParamViewWidgetBridge::WidgetCallback()
|
||||
case NodeValue::kGenerateJob:
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kAudioParams:
|
||||
case NodeValue::kDataTypeCount:
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
@@ -454,6 +423,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
case NodeValue::kGenerateJob:
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kAudioParams:
|
||||
case NodeValue::kDataTypeCount:
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QToolTip>
|
||||
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "node/factory.h"
|
||||
#include "node/group/group.h"
|
||||
#include "node/traverser.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "widget/timebased/timebasedview.h"
|
||||
|
||||
@@ -43,10 +45,12 @@ const double NodeView::kMinimumScale = 0.1;
|
||||
|
||||
NodeView::NodeView(QWidget *parent) :
|
||||
HandMovableView(parent),
|
||||
NodeCopyPasteService(QStringLiteral("nodes")),
|
||||
drop_edge_(nullptr),
|
||||
create_edge_(nullptr),
|
||||
create_edge_output_item_(nullptr),
|
||||
create_edge_input_item_(nullptr),
|
||||
overlay_view_(nullptr),
|
||||
scale_(1.0)
|
||||
{
|
||||
setScene(&scene_);
|
||||
@@ -83,6 +87,10 @@ NodeView::~NodeView()
|
||||
|
||||
void NodeView::SetContexts(const QVector<Node*> &nodes)
|
||||
{
|
||||
if (overlay_view_) {
|
||||
CloseOverlay();
|
||||
}
|
||||
|
||||
// Remove contexts that are no longer in the list
|
||||
foreach (Node *n, contexts_) {
|
||||
if (!nodes.contains(n)) {
|
||||
@@ -213,20 +221,67 @@ void NodeView::Paste()
|
||||
void NodeView::Duplicate()
|
||||
{
|
||||
if (!selected_nodes_.isEmpty()) {
|
||||
Node::PositionMap map;
|
||||
QVector<Node*> selected = selected_nodes_;
|
||||
QVector<Node*> new_nodes;
|
||||
new_nodes.resize(selected_nodes_.size());
|
||||
Node::PositionMap map;
|
||||
|
||||
for (int i=0; i<new_nodes.size(); i++) {
|
||||
Node *og = selected_nodes_.at(i);
|
||||
Node *copy = og->copy();
|
||||
Node::CopyInputs(og, copy, false);
|
||||
map.insert(copy, GetAssumedPositionForSelectedNode(og));
|
||||
new_nodes[i] = copy;
|
||||
new_nodes.resize(selected.size());
|
||||
|
||||
// Create copies of each selected node, checking for groups and adding children if necessary
|
||||
for (int i=0; i<selected.size(); i++) {
|
||||
new_nodes[i] = selected.at(i)->copy();
|
||||
|
||||
if (NodeGroup *g = dynamic_cast<NodeGroup*>(selected.at(i))) {
|
||||
for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) {
|
||||
if (!selected.contains(it.key())) {
|
||||
// This should automatically recurse if this is a group inside a group
|
||||
selected.append(it.key());
|
||||
}
|
||||
}
|
||||
new_nodes.resize(selected.size());
|
||||
}
|
||||
}
|
||||
|
||||
Node::CopyDependencyGraph(selected_nodes_, new_nodes, nullptr);
|
||||
// Get positions in contexts, add input passthroughs, and copy input values/keyframes
|
||||
for (int i=0; i<new_nodes.size(); i++) {
|
||||
Node *og = selected.at(i);
|
||||
Node *copy = new_nodes.at(i);
|
||||
|
||||
Node::Position pos;
|
||||
if (GetAssumedPositionForSelectedNode(og, &pos)) {
|
||||
map.insert(copy, pos);
|
||||
}
|
||||
|
||||
for (auto it=og->GetContextPositions().cbegin(); it!=og->GetContextPositions().cend(); it++) {
|
||||
Node *child_og = it.key();
|
||||
int child_index = selected.indexOf(child_og);
|
||||
|
||||
if (child_index != -1) {
|
||||
Node *child_copy = new_nodes.at(child_index);
|
||||
|
||||
copy->SetNodePositionInContext(child_copy, it.value());
|
||||
}
|
||||
}
|
||||
|
||||
if (NodeGroup *src_group = dynamic_cast<NodeGroup*>(og)) {
|
||||
NodeGroup *dst_group = static_cast<NodeGroup*>(copy);
|
||||
|
||||
for (auto it=src_group->GetInputPassthroughs().cbegin(); it!=src_group->GetInputPassthroughs().cend(); it++) {
|
||||
NodeInput input = it->second;
|
||||
input.set_node(new_nodes.at(selected.indexOf(input.node())));
|
||||
dst_group->AddInputPassthrough(input, it->first);
|
||||
}
|
||||
|
||||
dst_group->SetOutputPassthrough(new_nodes.at(selected.indexOf(src_group->GetOutputPassthrough())));
|
||||
}
|
||||
|
||||
Node::CopyInputs(selected.at(i), new_nodes.at(i), false);
|
||||
}
|
||||
|
||||
// Copy connections
|
||||
Node::CopyDependencyGraph(selected, new_nodes, nullptr);
|
||||
|
||||
// Set root level context positions and attach to
|
||||
PostPaste(new_nodes, map);
|
||||
}
|
||||
}
|
||||
@@ -303,6 +358,8 @@ void NodeView::keyPressEvent(QKeyEvent *event)
|
||||
break;
|
||||
}
|
||||
|
||||
emit EscPressed();
|
||||
|
||||
/* fall through */
|
||||
default:
|
||||
super::keyPressEvent(event);
|
||||
@@ -394,8 +451,10 @@ void NodeView::mousePressEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
// Default QGraphicsView functionality (selecting, dragging, etc.)
|
||||
super::mousePressEvent(event);
|
||||
if (attached_items_.isEmpty()) {
|
||||
// Default QGraphicsView functionality (selecting, dragging, etc.)
|
||||
super::mousePressEvent(event);
|
||||
}
|
||||
|
||||
// For any selected item, store its position in case the user is dragging it somewhere else
|
||||
auto selected_items = scene_.GetSelectedItems();
|
||||
@@ -416,7 +475,9 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
super::mouseMoveEvent(event);
|
||||
if (attached_items_.isEmpty()) {
|
||||
super::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
// See if there are any items attached
|
||||
if (!attached_items_.isEmpty()) {
|
||||
@@ -443,26 +504,26 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
|
||||
if (new_drop_edge) {
|
||||
drop_input_.Reset();
|
||||
|
||||
NodeValue::Type drop_edge_data_type = NodeValue::kNone;
|
||||
NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType();
|
||||
|
||||
// Run the Node and determine what type is being used
|
||||
NodeTraverser traverser;
|
||||
NodeValue drop_edge_value = traverser.GenerateRow(new_drop_edge->output(), TimeRange(0, 0))[new_drop_edge->input().input()];
|
||||
drop_edge_data_type = drop_edge_value.type();
|
||||
// Determine best input to connect to our new node
|
||||
if (attached_node->GetEffectInput().IsValid()) {
|
||||
// If node specifies an effect input, use that immediately
|
||||
drop_input_ = attached_node->GetEffectInput();
|
||||
} else {
|
||||
// Otherwise, we may have to iterate to find a valid one
|
||||
for (const QString& input : attached_node->inputs()) {
|
||||
NodeInput i(attached_node, input);
|
||||
|
||||
// Iterate through the inputs of our dragging node and see if our node has any acceptable
|
||||
// inputs to connect to for this type
|
||||
for (const QString& input : attached_node->inputs()) {
|
||||
NodeInput i(attached_node, input);
|
||||
|
||||
if (attached_node->IsInputConnectable(input)) {
|
||||
if (attached_node->GetInputDataType(input) == drop_edge_data_type) {
|
||||
// Found exactly the type we're looking for, set and break this loop
|
||||
drop_input_ = i;
|
||||
break;
|
||||
} else if (!drop_input_.IsValid()) {
|
||||
// Default to first connectable input
|
||||
drop_input_ = i;
|
||||
if (attached_node->IsInputConnectable(input)) {
|
||||
if (attached_node->GetInputDataType(input) == drop_edge_data_type) {
|
||||
// Found exactly the type we're looking for, set and break this loop
|
||||
drop_input_ = i;
|
||||
break;
|
||||
} else if (!drop_input_.IsValid()) {
|
||||
// Default to first connectable input
|
||||
drop_input_ = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,6 +564,8 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
|
||||
Node *select_context = nullptr;
|
||||
QVector<Node*> select_nodes;
|
||||
|
||||
bool had_attached_items = !attached_items_.isEmpty();
|
||||
|
||||
if (!attached_items_.isEmpty()) {
|
||||
select_context = nullptr;
|
||||
|
||||
@@ -586,7 +649,9 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
super::mouseReleaseEvent(event);
|
||||
if (!had_attached_items) {
|
||||
super::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
if (select_context) {
|
||||
scene_.context_map().value(select_context)->Select(select_nodes);
|
||||
@@ -610,6 +675,10 @@ void NodeView::resizeEvent(QResizeEvent *event)
|
||||
super::resizeEvent(event);
|
||||
|
||||
RepositionMiniMap();
|
||||
|
||||
if (overlay_view_) {
|
||||
ResizeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::UpdateSelectionCache()
|
||||
@@ -1020,12 +1089,13 @@ NodeViewItem *NodeView::GetAssumedItemForSelectedNode(Node *node)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node::Position NodeView::GetAssumedPositionForSelectedNode(Node *node)
|
||||
bool NodeView::GetAssumedPositionForSelectedNode(Node *node, Node::Position *pos)
|
||||
{
|
||||
if (NodeViewItem *item = GetAssumedItemForSelectedNode(node)) {
|
||||
return item->GetNodePositionData();
|
||||
*pos = item->GetNodePositionData();
|
||||
return true;
|
||||
} else {
|
||||
return Node::Position();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1174,6 +1244,7 @@ void NodeView::GroupNodes()
|
||||
|
||||
if (!output_passthrough) {
|
||||
// Default to the first node we find that doesn't output to a node inside the group
|
||||
output_passthrough = nodes_to_group.first();
|
||||
foreach (Node *potential_in, nodes_to_group) {
|
||||
if (potential_in != n && !n->OutputsTo(potential_in, false)) {
|
||||
output_passthrough = n;
|
||||
@@ -1236,7 +1307,37 @@ void NodeView::ShowNodeProperties()
|
||||
Node *first_node = selected_nodes_.first();
|
||||
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(first_node)) {
|
||||
emit NodeGroupOpenRequested(group);
|
||||
if (!overlay_view_) {
|
||||
overlay_view_ = new NodeView(this);
|
||||
overlay_view_->show();
|
||||
|
||||
QPushButton *overlay_close_btn = new QPushButton(overlay_view_);
|
||||
overlay_close_btn->setIcon(icon::Error);
|
||||
int offset = overlay_close_btn->sizeHint().width()/2;
|
||||
overlay_close_btn->move(offset, offset);
|
||||
overlay_close_btn->show();
|
||||
|
||||
connect(overlay_view_, &NodeView::NodesSelected, this, &NodeView::NodesSelected);
|
||||
connect(overlay_view_, &NodeView::NodesDeselected, this, &NodeView::NodesDeselected);
|
||||
connect(overlay_view_, &NodeView::NodeGroupOpened, this, &NodeView::NodeGroupOpened);
|
||||
connect(overlay_view_, &NodeView::NodeGroupClosed, this, &NodeView::NodeGroupClosed);
|
||||
connect(overlay_view_, &NodeView::EscPressed, this, &NodeView::CloseOverlay);
|
||||
connect(overlay_close_btn, &QPushButton::clicked, this, &NodeView::CloseOverlay);
|
||||
|
||||
const QColor &bgcol = overlay_view_->palette().base().color();
|
||||
overlay_view_->setStyleSheet(QStringLiteral("QGraphicsView { background: rgba(%1, %2, %3, 0.8); }").arg(QString::number(bgcol.red()), QString::number(bgcol.green()), QString::number(bgcol.blue())));
|
||||
|
||||
overlay_close_btn->setStyleSheet(QStringLiteral("background: transparent; border: none;"));
|
||||
}
|
||||
overlay_view_->SetContexts({group});
|
||||
ResizeOverlay();
|
||||
QMetaObject::invokeMethod(overlay_view_, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection);
|
||||
overlay_view_->setFocus();
|
||||
|
||||
emit NodesDeselected(selected_nodes_);
|
||||
overlay_view_->SelectAll();
|
||||
|
||||
emit NodeGroupOpened(group);
|
||||
} else {
|
||||
LabelSelectedNodes();
|
||||
}
|
||||
@@ -1272,6 +1373,17 @@ void NodeView::ItemAboutToBeDeleted(NodeViewItem *item)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::CloseOverlay()
|
||||
{
|
||||
if (overlay_view_->overlay_view_) {
|
||||
overlay_view_->CloseOverlay();
|
||||
}
|
||||
|
||||
overlay_view_->deleteLater();
|
||||
overlay_view_ = nullptr;
|
||||
emit NodeGroupClosed();
|
||||
}
|
||||
|
||||
void NodeView::AddContext(Node *n)
|
||||
{
|
||||
NodeViewContext *ctx = scene_.AddContext(n);
|
||||
@@ -1341,8 +1453,8 @@ void NodeView::EndEdgeDrag(bool cancel)
|
||||
create_edge_input_item_->SetHighlighted(false);
|
||||
}
|
||||
|
||||
NodeInput &creating_input = create_edge_input_;
|
||||
if (create_edge_output_item_ && create_edge_input_item_ && !cancel) {
|
||||
NodeInput &creating_input = create_edge_input_;
|
||||
if (creating_input.IsValid()) {
|
||||
// Make connection
|
||||
if (!reconnected_to_itself) {
|
||||
@@ -1353,28 +1465,43 @@ void NodeView::EndEdgeDrag(bool cancel)
|
||||
}
|
||||
|
||||
while (NodeGroup *input_group = dynamic_cast<NodeGroup*>(creating_input.node())) {
|
||||
creating_input = input_group->GetInputPassthroughs().value(creating_input.input());
|
||||
creating_input = input_group->GetInputFromID(creating_input.input());
|
||||
}
|
||||
|
||||
if (creating_input.IsConnected()) {
|
||||
Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input};
|
||||
command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second));
|
||||
|
||||
Node *already_connected_output = creating_input.GetConnectedOutput();
|
||||
NodeViewContext *ctx = GetContextItemFromNodeItem(create_edge_input_item_);
|
||||
if (ctx && !ctx->GetItemFromMap(already_connected_output)) {
|
||||
if (QMessageBox::warning(this, QString(), tr("Input \"%1\" is currently connected to node \"%2\", which is not visible in this context. "
|
||||
"By connecting this, that connection will be removed. Do you wish to continue?").arg(creating_input.name(), already_connected_output->GetLabelAndName()),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancel) {
|
||||
command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second));
|
||||
}
|
||||
}
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(creating_output, creating_input));
|
||||
if (!cancel) {
|
||||
command->add_child(new NodeEdgeAddCommand(creating_output, creating_input));
|
||||
|
||||
// If the output is not in the input's context, add it now. We check the item rather than
|
||||
// the node itself, because sometimes a node may not be in the context but another node
|
||||
// representing it will be (e.g. groups)
|
||||
if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) {
|
||||
command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos())));
|
||||
// If the output is not in the input's context, add it now. We check the item rather than
|
||||
// the node itself, because sometimes a node may not be in the context but another node
|
||||
// representing it will be (e.g. groups)
|
||||
if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) {
|
||||
command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
creating_input.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
creating_input.Reset();
|
||||
create_edge_output_item_ = nullptr;
|
||||
create_edge_input_item_ = nullptr;
|
||||
|
||||
@@ -1429,6 +1556,22 @@ void NodeView::PostPaste(const QVector<Node *> &new_nodes, const Node::PositionM
|
||||
SetAttachedItems(new_attached);
|
||||
}
|
||||
|
||||
void NodeView::ResizeOverlay()
|
||||
{
|
||||
overlay_view_->resize(this->size());
|
||||
}
|
||||
|
||||
NodeViewContext *NodeView::GetContextItemFromNodeItem(NodeViewItem *item)
|
||||
{
|
||||
QGraphicsItem *i = item;
|
||||
while ((i = i->parentItem())) {
|
||||
if (NodeViewContext *nvc = dynamic_cast<NodeViewContext*>(i)) {
|
||||
return nvc;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NodeView::SetAttachedItems(const QVector<AttachedItem> &items)
|
||||
{
|
||||
// Detach anything currently attached
|
||||
|
||||
@@ -54,7 +54,16 @@ public:
|
||||
|
||||
const QVector<Node*> &GetContexts() const
|
||||
{
|
||||
return contexts_;
|
||||
if (overlay_view_) {
|
||||
return overlay_view_->GetContexts();
|
||||
} else {
|
||||
return contexts_;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsGroupOverlay() const
|
||||
{
|
||||
return overlay_view_;
|
||||
}
|
||||
|
||||
void CloseContextsBelongingToProject(Project *project);
|
||||
@@ -109,7 +118,10 @@ signals:
|
||||
|
||||
void NodesDeselected(const QVector<Node*>& nodes);
|
||||
|
||||
void NodeGroupOpenRequested(NodeGroup *group);
|
||||
void NodeGroupOpened(NodeGroup *group);
|
||||
void NodeGroupClosed();
|
||||
|
||||
void EscPressed();
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *event) override;
|
||||
@@ -149,7 +161,7 @@ private:
|
||||
QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const;
|
||||
|
||||
NodeViewItem *GetAssumedItemForSelectedNode(Node *node);
|
||||
Node::Position GetAssumedPositionForSelectedNode(Node *node);
|
||||
bool GetAssumedPositionForSelectedNode(Node *node, Node::Position *pos);
|
||||
|
||||
Menu *CreateAddMenu(Menu *parent);
|
||||
|
||||
@@ -169,8 +181,12 @@ private:
|
||||
|
||||
void PostPaste(const QVector<Node*> &new_nodes, const Node::PositionMap &map);
|
||||
|
||||
void ResizeOverlay();
|
||||
|
||||
NodeViewMiniMap *minimap_;
|
||||
|
||||
NodeViewContext *GetContextItemFromNodeItem(NodeViewItem *item);
|
||||
|
||||
struct AttachedItem {
|
||||
NodeViewItem* item;
|
||||
Node *node;
|
||||
@@ -202,6 +218,8 @@ private:
|
||||
|
||||
QMap<NodeViewItem*, QPointF> dragging_items_;
|
||||
|
||||
NodeView* overlay_view_;
|
||||
|
||||
double scale_;
|
||||
|
||||
static const double kMinimumScale;
|
||||
@@ -252,6 +270,8 @@ private slots:
|
||||
|
||||
void ItemAboutToBeDeleted(NodeViewItem *item);
|
||||
|
||||
void CloseOverlay();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -464,10 +464,16 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
|
||||
QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == ItemPositionHasChanged && node_) {
|
||||
ReadjustAllEdges();
|
||||
if (node_) {
|
||||
if (change == ItemPositionHasChanged) {
|
||||
ReadjustAllEdges();
|
||||
|
||||
UpdateContextRect();
|
||||
UpdateContextRect();
|
||||
} else if (change == ItemSelectedHasChanged) {
|
||||
if (value.toBool()) {
|
||||
qDebug() << "Selected node:" << node_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QGraphicsItem::itemChange(change, value);
|
||||
@@ -786,7 +792,7 @@ NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input)
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node_)) {
|
||||
if (input.node() != group) {
|
||||
// Translate input to group input
|
||||
QString id = NodeGroup::GetGroupInputIDFromInput(input);
|
||||
QString id = group->GetIDOfPassthrough(input);
|
||||
input.set_node(group);
|
||||
input.set_input(id);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
|
||||
|
||||
cur_tc_lbl_ = new RationalSlider();
|
||||
cur_tc_lbl_->SetDisplayType(RationalSlider::kTime);
|
||||
cur_tc_lbl_->SetMinimum(0);
|
||||
connect(cur_tc_lbl_, &RationalSlider::ValueChanged, this, &PlaybackControls::TimeChanged);
|
||||
lower_left_layout->addWidget(cur_tc_lbl_);
|
||||
lower_left_layout->addStretch();
|
||||
|
||||
@@ -57,6 +57,7 @@ SliderBase::SliderBase(QWidget *parent) :
|
||||
void SliderBase::SetAlignment(Qt::Alignment alignment)
|
||||
{
|
||||
label_->setAlignment(alignment);
|
||||
editor_->setAlignment(alignment);
|
||||
}
|
||||
|
||||
bool SliderBase::IsTristate() const
|
||||
@@ -104,7 +105,17 @@ void SliderBase::changeEvent(QEvent *e)
|
||||
|
||||
void SliderBase::UpdateLabel()
|
||||
{
|
||||
label_->setText(tristate_ ? tr("---") : GetFormattedValueToString());
|
||||
QString s;
|
||||
|
||||
if (tristate_) {
|
||||
s = tr("---");
|
||||
} else if (label_substitutions_.contains(GetValueInternal())) {
|
||||
s = label_substitutions_.value(GetValueInternal());
|
||||
} else {
|
||||
s = GetFormattedValueToString();
|
||||
}
|
||||
|
||||
label_->setText(s);
|
||||
}
|
||||
|
||||
QVariant SliderBase::AdjustValue(const QVariant &value) const
|
||||
|
||||
@@ -49,6 +49,12 @@ public:
|
||||
|
||||
QString GetFormattedValueToString(const QVariant& v) const;
|
||||
|
||||
void InsertLabelSubstitution(const QVariant &value, const QString &label)
|
||||
{
|
||||
label_substitutions_.insert(value, label);
|
||||
UpdateLabel();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void ShowEditor();
|
||||
|
||||
@@ -92,6 +98,8 @@ private:
|
||||
|
||||
bool format_plural_;
|
||||
|
||||
QMap<QVariant, QString> label_substitutions_;
|
||||
|
||||
private slots:
|
||||
void LineEditConfirmed();
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "tool/rolling.h"
|
||||
#include "tool/slide.h"
|
||||
#include "tool/slip.h"
|
||||
#include "tool/trackselect.h"
|
||||
#include "tool/transition.h"
|
||||
#include "tool/zoom.h"
|
||||
#include "tool/tool.h"
|
||||
@@ -59,6 +60,7 @@ namespace olive {
|
||||
|
||||
TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
super(true, true, parent),
|
||||
NodeCopyPasteService(QStringLiteral("timeline")),
|
||||
rubberband_(QRubberBand::Rectangle, this),
|
||||
active_tool_(nullptr),
|
||||
use_audio_time_units_(false),
|
||||
@@ -75,6 +77,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
timecode_label_->SetAlignment(Qt::AlignCenter);
|
||||
timecode_label_->SetDisplayType(RationalSlider::kTime);
|
||||
timecode_label_->setVisible(false);
|
||||
timecode_label_->SetMinimum(0);
|
||||
connect(timecode_label_, &RationalSlider::ValueChanged, this, &TimelineWidget::SetTimeAndSignal);
|
||||
ruler_and_time_layout->addWidget(timecode_label_);
|
||||
|
||||
@@ -100,6 +103,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
tools_.fill(nullptr);
|
||||
|
||||
tools_.replace(olive::Tool::kPointer, new PointerTool(this));
|
||||
tools_.replace(olive::Tool::kTrackSelect, new TrackSelectTool(this));
|
||||
tools_.replace(olive::Tool::kEdit, new EditTool(this));
|
||||
tools_.replace(olive::Tool::kRipple, new RippleTool(this));
|
||||
tools_.replace(olive::Tool::kRolling, new RollingTool(this));
|
||||
@@ -183,6 +187,14 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
|
||||
connect(Core::instance(), &Core::ToolChanged, this, &TimelineWidget::ToolChanged);
|
||||
connect(Core::instance(), &Core::AddableObjectChanged, this, &TimelineWidget::AddableObjectChanged);
|
||||
|
||||
signal_block_change_timer_ = new QTimer(this);
|
||||
signal_block_change_timer_->setInterval(1);
|
||||
signal_block_change_timer_->setSingleShot(true);
|
||||
connect(signal_block_change_timer_, &QTimer::timeout, this, [this]{
|
||||
signal_block_change_timer_->stop();
|
||||
emit BlockSelectionChanged(selected_blocks_);
|
||||
});
|
||||
}
|
||||
|
||||
TimelineWidget::~TimelineWidget()
|
||||
@@ -471,7 +483,8 @@ void TimelineWidget::SplitAtPlayhead()
|
||||
void TimelineWidget::ReplaceBlocksWithGaps(const QVector<Block *> &blocks,
|
||||
bool remove_from_graph,
|
||||
MultiUndoCommand *command,
|
||||
bool handle_transitions)
|
||||
bool handle_transitions,
|
||||
bool handle_invalidations)
|
||||
{
|
||||
foreach (Block* b, blocks) {
|
||||
if (dynamic_cast<GapBlock*>(b)) {
|
||||
@@ -482,7 +495,7 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector<Block *> &blocks,
|
||||
|
||||
Track* original_track = b->track();
|
||||
|
||||
command->add_child(new TrackReplaceBlockWithGapCommand(original_track, b, handle_transitions));
|
||||
command->add_child(new TrackReplaceBlockWithGapCommand(original_track, b, handle_transitions, handle_invalidations));
|
||||
|
||||
if (remove_from_graph) {
|
||||
command->add_child(new NodeRemoveWithExclusiveDependenciesAndDisconnect(b));
|
||||
@@ -521,6 +534,9 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
// Remove all selections
|
||||
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections()));
|
||||
|
||||
// For transitions, remove them but extend their attached blocks to fill their place
|
||||
foreach (TransitionBlock* transition, transitions_to_delete) {
|
||||
TransitionRemoveCommand *trc = new TransitionRemoveCommand(transition, true);
|
||||
@@ -532,10 +548,7 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
}
|
||||
|
||||
// Replace clips with gaps (effectively deleting them)
|
||||
ReplaceBlocksWithGaps(clips_to_delete, true, command);
|
||||
|
||||
// Remove all selections
|
||||
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), false));
|
||||
ReplaceBlocksWithGaps(clips_to_delete, true, command, false, !ripple);
|
||||
|
||||
// Insert ripple command now that it's all cleaned up gaps
|
||||
TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr;
|
||||
@@ -1184,7 +1197,8 @@ void TimelineWidget::SetScrollZoomsByDefaultOnAllViews(bool e)
|
||||
|
||||
void TimelineWidget::SignalBlockSelectionChange()
|
||||
{
|
||||
emit BlockSelectionChanged(selected_blocks_);
|
||||
signal_block_change_timer_->stop();
|
||||
signal_block_change_timer_->start();
|
||||
}
|
||||
|
||||
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
|
||||
@@ -1231,7 +1245,7 @@ void TimelineWidget::NudgeInternal(rational amount)
|
||||
// Nudge selections
|
||||
TimelineWidgetSelections new_sel = GetSelections();
|
||||
new_sel.ShiftTime(amount);
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections()));
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
@@ -1283,7 +1297,7 @@ void TimelineWidget::MoveToPlayheadInternal(bool out)
|
||||
it.value().shift(track_adj);
|
||||
}
|
||||
}
|
||||
command->add_child(new SetSelectionsCommand(this, new_sel, GetSelections(), true));
|
||||
command->add_child(new SetSelectionsCommand(this, new_sel, GetSelections()));
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
@@ -1357,7 +1371,7 @@ void TimelineWidget::SignalSelectedBlocks(QVector<Block *> input, bool filter)
|
||||
|
||||
selected_blocks_.append(input);
|
||||
|
||||
emit SignalBlockSelectionChange();
|
||||
SignalBlockSelectionChange();
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalDeselectedBlocks(const QVector<Block *> &deselected_blocks)
|
||||
@@ -1370,7 +1384,7 @@ void TimelineWidget::SignalDeselectedBlocks(const QVector<Block *> &deselected_b
|
||||
selected_blocks_.removeOne(b);
|
||||
}
|
||||
|
||||
emit SignalBlockSelectionChange();
|
||||
SignalBlockSelectionChange();
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalDeselectedAllBlocks()
|
||||
|
||||
@@ -124,7 +124,7 @@ public:
|
||||
|
||||
void RestoreSplitterState(const QByteArray& state);
|
||||
|
||||
static void ReplaceBlocksWithGaps(const QVector<Block *> &blocks, bool remove_from_graph, MultiUndoCommand *command, bool handle_transitions = true);
|
||||
static void ReplaceBlocksWithGaps(const QVector<Block *> &blocks, bool remove_from_graph, MultiUndoCommand *command, bool handle_transitions = true, bool handle_invalidations = true);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the QGraphicsItem at a particular scene position
|
||||
@@ -237,7 +237,7 @@ public:
|
||||
|
||||
class SetSelectionsCommand : public UndoCommand {
|
||||
public:
|
||||
SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, bool process_block_changes) :
|
||||
SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, bool process_block_changes = true) :
|
||||
timeline_(timeline),
|
||||
old_(old),
|
||||
now_(now),
|
||||
@@ -329,6 +329,8 @@ private:
|
||||
|
||||
MultiUndoCommand *subtitle_show_command_;
|
||||
|
||||
QTimer *signal_block_change_timer_;
|
||||
|
||||
class SetSplitterSizesCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -38,6 +38,8 @@ set(OLIVE_SOURCES
|
||||
widget/timelinewidget/tool/slide.h
|
||||
widget/timelinewidget/tool/slip.cpp
|
||||
widget/timelinewidget/tool/slip.h
|
||||
widget/timelinewidget/tool/trackselect.cpp
|
||||
widget/timelinewidget/tool/trackselect.h
|
||||
widget/timelinewidget/tool/transition.cpp
|
||||
widget/timelinewidget/tool/transition.h
|
||||
widget/timelinewidget/tool/tool.cpp
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "node/factory.h"
|
||||
#include "node/generator/shape/shapenode.h"
|
||||
#include "node/generator/solid/solid.h"
|
||||
#include "node/generator/text/text.h"
|
||||
#include "node/generator/text/textv3.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
#include "widget/timelinewidget/undo/timelineundopointer.h"
|
||||
|
||||
@@ -138,7 +138,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
break;
|
||||
case olive::Tool::kAddableTitle:
|
||||
{
|
||||
node_to_add = new TextGenerator();
|
||||
node_to_add = new TextGeneratorV3();
|
||||
break;
|
||||
}
|
||||
case olive::Tool::kAddableBars:
|
||||
|
||||
@@ -44,6 +44,7 @@ PointerTool::PointerTool(TimelineWidget *parent) :
|
||||
trimming_allowed_(true),
|
||||
track_movement_allowed_(true),
|
||||
gap_trimming_allowed_(false),
|
||||
can_rubberband_select_(false),
|
||||
rubberband_selecting_(false)
|
||||
{
|
||||
}
|
||||
@@ -173,7 +174,7 @@ void PointerTool::MouseMove(TimelineViewMouseEvent *event)
|
||||
|
||||
// If we're performing an action, we can initiate ghosts
|
||||
if (drag_movement_mode_ != Timeline::kNone) {
|
||||
InitiateDrag(clicked_item_, drag_movement_mode_);
|
||||
InitiateDrag(clicked_item_, drag_movement_mode_, event->GetModifiers());
|
||||
}
|
||||
|
||||
// Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed
|
||||
@@ -246,6 +247,7 @@ void SetGhostToSlideMode(TimelineViewGhostItem* g)
|
||||
|
||||
void PointerTool::InitiateDragInternal(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
bool dont_roll_trims,
|
||||
bool allow_nongap_rolling,
|
||||
bool slide_instead_of_moving)
|
||||
@@ -409,30 +411,41 @@ void PointerTool::InitiateDragInternal(Block *clicked_item,
|
||||
}
|
||||
|
||||
Timeline::MovementMode flipped_mode = FlipTrimMode(trim_mode);
|
||||
TimelineViewGhostItem* adjacent_ghost;
|
||||
QVector<TimelineViewGhostItem*> adjacent_ghosts;
|
||||
|
||||
if (adjacent) {
|
||||
adjacent_ghost = AddGhostFromBlock(adjacent, flipped_mode);
|
||||
adjacent_ghosts.append(AddGhostFromBlock(adjacent, flipped_mode));
|
||||
|
||||
// Select adjacent's links if applicable
|
||||
// FIXME: The check for `clips.size() == 1` may not be necessary, but I don't know yet.
|
||||
// I'm only including it to prevent any potentially unintended behavior.
|
||||
if (clips.size() == 1 && !(modifiers & Qt::AltModifier)) {
|
||||
if (ClipBlock *adjacent_clip = dynamic_cast<ClipBlock*>(adjacent)) {
|
||||
foreach (Block *adjacent_link, adjacent_clip->block_links()) {
|
||||
adjacent_ghosts.append(AddGhostFromBlock(adjacent_link, flipped_mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (trim_mode == Timeline::kTrimIn || block->next()) {
|
||||
rational null_ghost_pos = (trim_mode == Timeline::kTrimIn) ? block->in() : block->out();
|
||||
|
||||
adjacent_ghost = AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->track()->ToReference(), flipped_mode);
|
||||
} else {
|
||||
adjacent_ghost = nullptr;
|
||||
adjacent_ghosts.append(AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->track()->ToReference(), flipped_mode));
|
||||
}
|
||||
|
||||
// If we have an adjacent block (for any reason), this is a roll edit and the adjacent is
|
||||
// expected to fill the remaining space (no gap needs to be created)
|
||||
ghost->SetData(TimelineViewGhostItem::kTrimIsARollEdit, static_cast<bool>(adjacent));
|
||||
|
||||
if (adjacent_ghost) {
|
||||
if (treat_trim_as_slide) {
|
||||
// We're sliding a transition rather than a pure trim/roll
|
||||
SetGhostToSlideMode(adjacent_ghost);
|
||||
} else if (dynamic_cast<GapBlock*>(block)) {
|
||||
ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
} else {
|
||||
adjacent_ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
foreach (TimelineViewGhostItem *adjacent_ghost, adjacent_ghosts) {
|
||||
if (adjacent_ghost) {
|
||||
if (treat_trim_as_slide) {
|
||||
// We're sliding a transition rather than a pure trim/roll
|
||||
SetGhostToSlideMode(adjacent_ghost);
|
||||
} else if (dynamic_cast<GapBlock*>(block)) {
|
||||
ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
} else {
|
||||
adjacent_ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,6 +573,10 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
ghost->GetAdjustedLength(),
|
||||
ghost->GetMode());
|
||||
|
||||
if (event->GetModifiers() & Qt::ControlModifier) {
|
||||
|
||||
}
|
||||
|
||||
c->SetTrimIsARollEdit(ghost->GetData(TimelineViewGhostItem::kTrimIsARollEdit).toBool());
|
||||
|
||||
command->add_child(c);
|
||||
@@ -575,7 +592,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
} else {
|
||||
new_sel.TrimOut(reference_ghost->GetOutAdjustment());
|
||||
}
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +641,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
TimelineWidgetSelections new_sel = parent()->GetSelections();
|
||||
new_sel.ShiftTime(blocks_moving.first().ghost->GetInAdjustment());
|
||||
new_sel.ShiftTracks(drag_track_type_, blocks_moving.first().ghost->GetTrackAdjustment());
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections()));
|
||||
}
|
||||
|
||||
if (!blocks_sliding.isEmpty()) {
|
||||
@@ -685,7 +702,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
// Adjust selections
|
||||
TimelineWidgetSelections new_sel = parent()->GetSelections();
|
||||
new_sel.ShiftTime(movement);
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,10 +731,9 @@ Timeline::MovementMode PointerTool::IsCursorInTrimHandle(Block *block, qreal cur
|
||||
}
|
||||
}
|
||||
|
||||
void PointerTool::InitiateDrag(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode)
|
||||
void PointerTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, false, false, false);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, false, false, false);
|
||||
}
|
||||
|
||||
//#define HIDE_GAP_GHOSTS
|
||||
|
||||
@@ -39,8 +39,7 @@ public:
|
||||
protected:
|
||||
virtual void FinishDrag(TimelineViewMouseEvent *event);
|
||||
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode);
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers);
|
||||
|
||||
TimelineViewGhostItem* AddGhostFromBlock(Block *block, Timeline::MovementMode mode, bool check_if_exists = false);
|
||||
|
||||
@@ -66,13 +65,12 @@ protected:
|
||||
|
||||
void InitiateDragInternal(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
bool dont_roll_trims,
|
||||
bool allow_nongap_rolling, bool slide_instead_of_moving);
|
||||
|
||||
const Timeline::MovementMode& drag_movement_mode() const
|
||||
{
|
||||
return drag_movement_mode_;
|
||||
}
|
||||
const Timeline::MovementMode& drag_movement_mode() const { return drag_movement_mode_; }
|
||||
void set_drag_movement_mode(const Timeline::MovementMode &d) { drag_movement_mode_ = d; }
|
||||
|
||||
void SetMovementAllowed(bool e)
|
||||
{
|
||||
@@ -94,6 +92,11 @@ protected:
|
||||
gap_trimming_allowed_ = e;
|
||||
}
|
||||
|
||||
void SetClickedItem(Block *b)
|
||||
{
|
||||
clicked_item_ = b;
|
||||
}
|
||||
|
||||
private:
|
||||
Timeline::MovementMode IsCursorInTrimHandle(Block* block, qreal cursor_x);
|
||||
|
||||
|
||||
@@ -34,10 +34,9 @@ RippleTool::RippleTool(TimelineWidget* parent) :
|
||||
SetGapTrimmingAllowed(true);
|
||||
}
|
||||
|
||||
void RippleTool::InitiateDrag(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode)
|
||||
void RippleTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, true, true, false);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, true, true, false);
|
||||
|
||||
if (!parent()->HasGhosts()) {
|
||||
return;
|
||||
|
||||
@@ -32,8 +32,7 @@ public:
|
||||
protected:
|
||||
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
|
||||
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode) override;
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -33,10 +33,9 @@ RollingTool::RollingTool(TimelineWidget* parent) :
|
||||
SetGapTrimmingAllowed(true);
|
||||
}
|
||||
|
||||
void RollingTool::InitiateDrag(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode)
|
||||
void RollingTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, false, true, false);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, false, true, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ public:
|
||||
RollingTool(TimelineWidget* parent);
|
||||
|
||||
protected:
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode) override;
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ SlideTool::SlideTool(TimelineWidget* parent) :
|
||||
SetGapTrimmingAllowed(true);
|
||||
}
|
||||
|
||||
void SlideTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode)
|
||||
void SlideTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, false, true, true);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, false, true, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ public:
|
||||
SlideTool(TimelineWidget* parent);
|
||||
|
||||
protected:
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode) override;
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "trackselect.h"
|
||||
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "node/output/track/track.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TrackSelectTool::TrackSelectTool(TimelineWidget *parent) :
|
||||
PointerTool(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void TrackSelectTool::MousePress(TimelineViewMouseEvent *event)
|
||||
{
|
||||
QVector<Block*> blocks;
|
||||
bool forward = !(event->GetModifiers() & Qt::ControlModifier);
|
||||
|
||||
parent()->DeselectAll();
|
||||
|
||||
if (event->GetModifiers() & Qt::ShiftModifier) {
|
||||
// Track only
|
||||
Track *track = parent()->GetTrackFromReference(event->GetTrack());
|
||||
if (track) {
|
||||
SelectBlocksOnTrack(track, event, &blocks, forward);
|
||||
}
|
||||
} else {
|
||||
// All tracks
|
||||
foreach (Track *track, parent()->sequence()->GetTracks()) {
|
||||
SelectBlocksOnTrack(track, event, &blocks, forward);
|
||||
}
|
||||
}
|
||||
|
||||
if (!blocks.isEmpty()) {
|
||||
parent()->SignalSelectedBlocks(blocks);
|
||||
set_drag_movement_mode(Timeline::kMove);
|
||||
SetClickedItem(blocks.first());
|
||||
drag_start_ = event->GetCoordinates();
|
||||
} else {
|
||||
set_drag_movement_mode(Timeline::kNone);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackSelectTool::SelectBlocksOnTrack(Track *track, TimelineViewMouseEvent *event, QVector<Block*> *blocks, bool forward)
|
||||
{
|
||||
Block *b = track->NearestBlockBeforeOrAt(event->GetFrame());
|
||||
|
||||
if (!b && !track->Blocks().isEmpty() && !forward) {
|
||||
// Fallback to first or last block in track
|
||||
b = track->Blocks().last();
|
||||
}
|
||||
|
||||
while (b) {
|
||||
if (!dynamic_cast<GapBlock*>(b)) {
|
||||
if (!blocks->contains(b)) {
|
||||
parent()->AddSelection(b);
|
||||
blocks->append(b);
|
||||
}
|
||||
|
||||
if (!(event->GetModifiers() & Qt::AltModifier)) {
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(b)) {
|
||||
foreach (Block *link, clip->block_links()) {
|
||||
if (!blocks->contains(link)) {
|
||||
parent()->AddSelection(link);
|
||||
blocks->append(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b = forward ? b->next() : b->previous();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TRACKSELECTTOOL_H
|
||||
#define TRACKSELECTTOOL_H
|
||||
|
||||
#include "pointer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TrackSelectTool : public PointerTool
|
||||
{
|
||||
public:
|
||||
TrackSelectTool(TimelineWidget *parent);
|
||||
|
||||
virtual void MousePress(TimelineViewMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void SelectBlocksOnTrack(Track *track, TimelineViewMouseEvent *event, QVector<Block *> *blocks, bool forward);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TRACKSELECTTOOL_H
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
@@ -56,15 +57,21 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
connect(line_edit_, &FocusableLineEdit::Cancelled, this, &TrackViewItem::LineEditCancelled);
|
||||
stack_->addWidget(line_edit_);
|
||||
|
||||
mute_button_ = CreateMSLButton(tr("M"), Qt::red);
|
||||
mute_button_ = CreateMSLButton(Qt::red);
|
||||
mute_button_->setChecked(track->IsMuted());
|
||||
UpdateMuteButton(track->IsMuted());
|
||||
connect(mute_button_, &QPushButton::toggled, track_, &Track::SetMuted);
|
||||
connect(mute_button_, &QPushButton::toggled, this, &TrackViewItem::UpdateMuteButton);
|
||||
layout->addWidget(mute_button_);
|
||||
|
||||
/*solo_button_ = CreateMSLButton(tr("S"), Qt::yellow);
|
||||
layout->addWidget(solo_button_);*/
|
||||
|
||||
lock_button_ = CreateMSLButton(tr("L"), Qt::gray);
|
||||
lock_button_ = CreateMSLButton(Qt::gray);
|
||||
lock_button_->setChecked(track->IsLocked());
|
||||
UpdateLockButton(track->IsLocked());
|
||||
connect(lock_button_, &QPushButton::toggled, track_, &Track::SetLocked);
|
||||
connect(lock_button_, &QPushButton::toggled, this, &TrackViewItem::UpdateLockButton);
|
||||
layout->addWidget(lock_button_);
|
||||
|
||||
setMinimumHeight(mute_button_->height());
|
||||
@@ -74,9 +81,9 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
connect(this, &QWidget::customContextMenuRequested, this, &TrackViewItem::ShowContextMenu);
|
||||
}
|
||||
|
||||
QPushButton *TrackViewItem::CreateMSLButton(const QString& text, const QColor& checked_color) const
|
||||
QPushButton *TrackViewItem::CreateMSLButton(const QColor& checked_color) const
|
||||
{
|
||||
QPushButton* button = new QPushButton(text);
|
||||
QPushButton* button = new QPushButton();
|
||||
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet(QStringLiteral("QPushButton::checked { background: %1; }").arg(checked_color.name()));
|
||||
@@ -169,4 +176,14 @@ void TrackViewItem::DeleteAllEmptyTracks()
|
||||
}
|
||||
}
|
||||
|
||||
void TrackViewItem::UpdateMuteButton(bool e)
|
||||
{
|
||||
mute_button_->setIcon(e ? icon::EyeClosed : icon::EyeOpened);
|
||||
}
|
||||
|
||||
void TrackViewItem::UpdateLockButton(bool e)
|
||||
{
|
||||
lock_button_->setIcon(e ? icon::LockClosed : icon::LockOpened);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QPushButton* CreateMSLButton(const QString &text, const QColor &checked_color) const;
|
||||
QPushButton* CreateMSLButton(const QColor &checked_color) const;
|
||||
|
||||
QStackedWidget* stack_;
|
||||
|
||||
@@ -67,6 +67,10 @@ private slots:
|
||||
|
||||
void DeleteAllEmptyTracks();
|
||||
|
||||
void UpdateMuteButton(bool e);
|
||||
|
||||
void UpdateLockButton(bool e);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -490,7 +490,9 @@ void TrackReplaceBlockWithGapCommand::redo()
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
if (handle_invalidations_) {
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Block is at the end of the track, simply remove it
|
||||
@@ -547,7 +549,9 @@ void TrackReplaceBlockWithGapCommand::undo()
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
|
||||
if (handle_invalidations_) {
|
||||
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
|
||||
}
|
||||
} else {
|
||||
|
||||
// Our gap and existing gap were both null, our block must have been at the end and thus
|
||||
|
||||
@@ -233,13 +233,14 @@ private:
|
||||
|
||||
class TrackReplaceBlockWithGapCommand : public UndoCommand {
|
||||
public:
|
||||
TrackReplaceBlockWithGapCommand(Track* track, Block* block, bool handle_transitions = true) :
|
||||
TrackReplaceBlockWithGapCommand(Track* track, Block* block, bool handle_transitions = true, bool handle_invalidations = true) :
|
||||
track_(track),
|
||||
block_(block),
|
||||
existing_gap_(nullptr),
|
||||
existing_merged_gap_(nullptr),
|
||||
our_gap_(nullptr),
|
||||
handle_transitions_(handle_transitions)
|
||||
handle_transitions_(handle_transitions),
|
||||
handle_invalidations_(handle_invalidations)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -265,6 +266,7 @@ private:
|
||||
GapBlock* our_gap_;
|
||||
|
||||
bool handle_transitions_;
|
||||
bool handle_invalidations_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
@@ -360,6 +362,62 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class NodeBeginOperationCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeBeginOperationCommand(Node *node) :
|
||||
node_(node)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
node_->BeginOperation();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
node_->EndOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
Node *node_;
|
||||
|
||||
};
|
||||
|
||||
class NodeEndOperationCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeEndOperationCommand(Node *node) :
|
||||
node_(node)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
node_->EndOperation();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
node_->BeginOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
Node *node_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEUNDOGENERAL_H
|
||||
|
||||
@@ -488,26 +488,36 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
}
|
||||
|
||||
max_gaps = qMax(max_gaps, gaps_on_track.size());
|
||||
} else {
|
||||
qWarning() << "Failed to find corresponding gap to region";
|
||||
}
|
||||
}
|
||||
|
||||
// For each gap on each track, find a corresponding gap on every other track (which may include
|
||||
// a requested gap) to ripple in order to keep everything synchronized
|
||||
QHash<GapBlock*, rational> gap_lengths;
|
||||
for (int gap_index=0; gap_index<max_gaps; gap_index++) {
|
||||
rational earliest_point = RATIONAL_MAX;
|
||||
rational ripple_length = RATIONAL_MAX;
|
||||
rational latest_point = RATIONAL_MIN;
|
||||
|
||||
foreach (const QVector<RemovalRequest> &gaps_on_track, requested_gaps) {
|
||||
if (gap_index < gaps_on_track.size()) {
|
||||
const RemovalRequest &gap = gaps_on_track.at(gap_index);
|
||||
earliest_point = qMin(earliest_point, gap.range.in());
|
||||
ripple_length = qMin(ripple_length, gap.range.length());
|
||||
latest_point = qMax(latest_point, gap.range.out());
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which gaps will be involved in this operation
|
||||
QVector<GapBlock*> gaps;
|
||||
|
||||
bool all_tracks_unlocked = true;
|
||||
|
||||
foreach (Track* track, timeline_->GetTracks()) {
|
||||
if (track->IsLocked()) {
|
||||
all_tracks_unlocked = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -548,7 +558,12 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
|
||||
if (gap) {
|
||||
gaps.append(gap);
|
||||
ripple_length = qMin(ripple_length, gap->length());
|
||||
|
||||
if (!gap_lengths.contains(gap)) {
|
||||
gap_lengths.insert(gap, gap->length());
|
||||
}
|
||||
|
||||
ripple_length = qMin(ripple_length, gap_lengths.value(gap));
|
||||
}
|
||||
|
||||
if (ripple_length == 0) {
|
||||
@@ -558,11 +573,24 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
|
||||
if (ripple_length > 0) {
|
||||
foreach (GapBlock *gap, gaps) {
|
||||
if (gap->length() == ripple_length) {
|
||||
if (all_tracks_unlocked) {
|
||||
commands_.append(new NodeBeginOperationCommand(gap->track()));
|
||||
}
|
||||
|
||||
if (gap_lengths.value(gap) == ripple_length) {
|
||||
commands_.append(new TrackRippleRemoveBlockCommand(gap->track(), gap));
|
||||
} else {
|
||||
commands_.append(new BlockResizeCommand(gap, gap->length() - ripple_length));
|
||||
gap_lengths[gap] -= ripple_length;
|
||||
commands_.append(new BlockResizeCommand(gap, gap_lengths.value(gap)));
|
||||
}
|
||||
|
||||
if (all_tracks_unlocked) {
|
||||
commands_.append(new NodeEndOperationCommand(gap->track()));
|
||||
}
|
||||
}
|
||||
|
||||
if (all_tracks_unlocked) {
|
||||
commands_.append(new TimelineShiftCacheCommand(timeline_, latest_point, latest_point - ripple_length));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,4 +610,14 @@ void TimelineRippleDeleteGapsAtRegionsCommand::undo()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineShiftCacheCommand::redo()
|
||||
{
|
||||
timeline_->ShiftCache(from_, to_);
|
||||
}
|
||||
|
||||
void TimelineShiftCacheCommand::undo()
|
||||
{
|
||||
timeline_->ShiftCache(to_, from_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace olive {
|
||||
* By default, nothing takes this area meaning all subsequent clips are pushed backward, however you can specify
|
||||
* a block to insert at the `in` point. No checking is done to ensure `insert` is the same length as `in` to `out`.
|
||||
*/
|
||||
class TrackRippleRemoveAreaCommand : public UndoCommand {
|
||||
class TrackRippleRemoveAreaCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TrackRippleRemoveAreaCommand(Track* track, const TimeRange& range);
|
||||
|
||||
@@ -98,7 +99,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TrackListRippleRemoveAreaCommand : public UndoCommand {
|
||||
class TrackListRippleRemoveAreaCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TrackListRippleRemoveAreaCommand(TrackList* list, rational in, rational out) :
|
||||
list_(list),
|
||||
@@ -134,7 +136,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineRippleRemoveAreaCommand : public MultiUndoCommand {
|
||||
class TimelineRippleRemoveAreaCommand : public MultiUndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineRippleRemoveAreaCommand(Sequence* timeline, rational in, rational out);
|
||||
|
||||
@@ -148,7 +151,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TrackListRippleToolCommand : public UndoCommand {
|
||||
class TrackListRippleToolCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
struct RippleInfo {
|
||||
Block* block;
|
||||
@@ -200,7 +204,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
|
||||
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const QVector<QPair<Track*, TimeRange> >& regions) :
|
||||
timeline_(vo),
|
||||
@@ -243,6 +248,34 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineShiftCacheCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineShiftCacheCommand(Sequence* timeline, const rational &from, const rational &to) :
|
||||
timeline_(timeline),
|
||||
from_(from),
|
||||
to_(to)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return timeline_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Sequence* timeline_;
|
||||
|
||||
rational from_;
|
||||
|
||||
rational to_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEUNDORIPPLE_H
|
||||
|
||||
@@ -38,14 +38,15 @@ void BlockSplitCommand::redo()
|
||||
if (!reconnect_tree_command_) {
|
||||
reconnect_tree_command_ = new MultiUndoCommand();
|
||||
new_block_ = static_cast<Block*>(Node::CopyNodeInGraph(block_, reconnect_tree_command_));
|
||||
if (ClipBlock *new_clip = dynamic_cast<ClipBlock*>(new_block_)) {
|
||||
ClipBlock *old_clip = static_cast<ClipBlock*>(block_);
|
||||
new_clip->waveform() = old_clip->waveform();
|
||||
}
|
||||
}
|
||||
|
||||
reconnect_tree_command_->redo_now();
|
||||
|
||||
if (ClipBlock *new_clip = dynamic_cast<ClipBlock*>(new_block_)) {
|
||||
ClipBlock *old_clip = static_cast<ClipBlock*>(block_);
|
||||
new_clip->waveform() = old_clip->waveform();
|
||||
}
|
||||
|
||||
// Determine our new lengths
|
||||
rational new_length = point_ - block_->in();
|
||||
rational new_part_length = block_->out() - point_;
|
||||
|
||||
@@ -360,6 +360,9 @@ void TimelineView::ToolChangedEvent(Tool::Item tool)
|
||||
case Tool::kZoom:
|
||||
setCursor(Qt::CrossCursor);
|
||||
break;
|
||||
case Tool::kTrackSelect:
|
||||
setCursor(Qt::SizeHorCursor); // FIXME: Not the ideal cursor
|
||||
break;
|
||||
default:
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ Toolbar::Toolbar(QWidget *parent) :
|
||||
|
||||
// Create standard tool buttons
|
||||
btn_pointer_tool_ = CreateToolButton(Tool::kPointer);
|
||||
btn_trackselect_tool_ = CreateToolButton(Tool::kTrackSelect);
|
||||
btn_edit_tool_ = CreateToolButton(Tool::kEdit);
|
||||
btn_ripple_tool_ = CreateToolButton(Tool::kRipple);
|
||||
btn_rolling_tool_ = CreateToolButton(Tool::kRolling);
|
||||
@@ -107,6 +108,7 @@ void Toolbar::resizeEvent(QResizeEvent *e)
|
||||
void Toolbar::Retranslate()
|
||||
{
|
||||
btn_pointer_tool_->setToolTip(tr("Pointer Tool"));
|
||||
btn_trackselect_tool_->setToolTip(tr("Track Select Tool"));
|
||||
btn_edit_tool_->setToolTip(tr("Edit Tool"));
|
||||
btn_ripple_tool_->setToolTip(tr("Ripple Tool"));
|
||||
btn_rolling_tool_->setToolTip(tr("Rolling Tool"));
|
||||
@@ -124,6 +126,7 @@ void Toolbar::Retranslate()
|
||||
void Toolbar::UpdateIcons()
|
||||
{
|
||||
btn_pointer_tool_->setIcon(icon::ToolPointer);
|
||||
btn_trackselect_tool_->setIcon(icon::ToolTrackSelect);
|
||||
btn_edit_tool_->setIcon(icon::ToolEdit);
|
||||
btn_ripple_tool_->setIcon(icon::ToolRipple);
|
||||
btn_rolling_tool_->setIcon(icon::ToolRolling);
|
||||
|
||||
@@ -173,6 +173,7 @@ private:
|
||||
QList<ToolbarButton*> toolbar_btns_;
|
||||
|
||||
ToolbarButton* btn_pointer_tool_;
|
||||
ToolbarButton* btn_trackselect_tool_;
|
||||
ToolbarButton* btn_edit_tool_;
|
||||
ToolbarButton* btn_ripple_tool_;
|
||||
ToolbarButton* btn_rolling_tool_;
|
||||
|
||||
@@ -32,6 +32,8 @@ set(OLIVE_SOURCES
|
||||
widget/viewer/viewersafemargininfo.h
|
||||
widget/viewer/viewersizer.cpp
|
||||
widget/viewer/viewersizer.h
|
||||
widget/viewer/viewertexteditor.cpp
|
||||
widget/viewer/viewertexteditor.h
|
||||
widget/viewer/viewerwindow.cpp
|
||||
widget/viewer/viewerwindow.h
|
||||
PARENT_SCOPE
|
||||
|
||||
@@ -256,6 +256,8 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
|
||||
|
||||
// Queue an UpdateStack so that when it runs, the viewer node will be fully disconnected
|
||||
QMetaObject::invokeMethod(this, &ViewerWidget::UpdateStack, Qt::QueuedConnection);
|
||||
|
||||
SetGizmos(nullptr);
|
||||
}
|
||||
|
||||
void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
|
||||
@@ -466,11 +468,8 @@ void ViewerWidget::ReceivedAudioBufferForPlayback()
|
||||
|
||||
// If the tempo must be adjusted, adjust now
|
||||
if (tempo_processor_.IsOpen()) {
|
||||
tempo_processor_.Push(pack.data(), pack.size());
|
||||
int actual = tempo_processor_.Pull(pack.data(), pack.size());
|
||||
if (actual != pack.size()) {
|
||||
pack.resize(actual);
|
||||
}
|
||||
tempo_processor_.Push(pack);
|
||||
pack = tempo_processor_.Pull();
|
||||
}
|
||||
|
||||
// TempoProcessor may have emptied the array
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "viewerdisplay.h"
|
||||
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
@@ -28,12 +30,23 @@
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QScreen>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "common/functiontimer.h"
|
||||
#include "common/html.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "node/gizmo/path.h"
|
||||
#include "node/gizmo/point.h"
|
||||
#include "node/gizmo/polygon.h"
|
||||
#include "node/gizmo/screen.h"
|
||||
#include "node/gizmo/text.h"
|
||||
#include "node/traverser.h"
|
||||
#include "viewertexteditor.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -44,7 +57,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
|
||||
deinterlace_texture_(nullptr),
|
||||
signal_cursor_color_(false),
|
||||
gizmos_(nullptr),
|
||||
gizmo_click_(false),
|
||||
current_gizmo_(nullptr),
|
||||
gizmo_drag_started_(false),
|
||||
hand_dragging_(false),
|
||||
deinterlace_(false),
|
||||
show_fps_(false),
|
||||
@@ -175,7 +189,7 @@ void ViewerDisplayWidget::SetTime(const rational &time)
|
||||
}
|
||||
}
|
||||
|
||||
QPoint ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(QPoint pos)
|
||||
QPointF ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(const QPointF &pos)
|
||||
{
|
||||
/*
|
||||
* Inversion will only fail if the viewer has been scaled by 0 in any direction
|
||||
@@ -204,12 +218,12 @@ void ViewerDisplayWidget::IncrementSkippedFrames()
|
||||
void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton && gizmos_
|
||||
&& gizmos_->GizmoPress(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime()), TransformViewerSpaceToBufferSpace(event->pos()))) {
|
||||
&& (current_gizmo_ = TryGizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos())))) {
|
||||
|
||||
// Handle gizmo click
|
||||
gizmo_click_ = true;
|
||||
gizmo_drag_time_ = GetGizmoTime();
|
||||
gizmo_start_drag_ = event->pos();
|
||||
gizmo_last_drag_ = gizmo_start_drag_;
|
||||
current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime()));
|
||||
|
||||
} else if (IsHandDrag(event)) {
|
||||
|
||||
@@ -242,14 +256,38 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
hand_last_drag_pos_ = event->pos();
|
||||
|
||||
} else if (gizmo_click_) {
|
||||
} else if (current_gizmo_) {
|
||||
|
||||
// Signal movement
|
||||
gizmos_->GizmoMove(TransformViewerSpaceToBufferSpace(event->pos()),
|
||||
gizmo_drag_time_,
|
||||
event->modifiers());
|
||||
gizmo_start_drag_ = event->pos();
|
||||
update();
|
||||
if (DraggableGizmo *draggable = dynamic_cast<DraggableGizmo*>(current_gizmo_)) {
|
||||
if (!gizmo_drag_started_) {
|
||||
QPointF start = TransformViewerSpaceToBufferSpace(gizmo_start_drag_);
|
||||
|
||||
rational gizmo_time = GetGizmoTime();
|
||||
NodeTraverser t;
|
||||
t.SetCacheVideoParams(gizmo_params_);
|
||||
NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base()));
|
||||
|
||||
draggable->DragStart(row, start.x(), start.y(), gizmo_time);
|
||||
gizmo_drag_started_ = true;
|
||||
}
|
||||
|
||||
QPointF v = TransformViewerSpaceToBufferSpace(event->pos());
|
||||
switch (draggable->GetDragValueBehavior()) {
|
||||
case DraggableGizmo::kAbsolute:
|
||||
// Above value is correct
|
||||
break;
|
||||
case DraggableGizmo::kDeltaFromPrevious:
|
||||
v -= TransformViewerSpaceToBufferSpace(gizmo_last_drag_);
|
||||
gizmo_last_drag_ = event->pos();
|
||||
break;
|
||||
case DraggableGizmo::kDeltaFromStart:
|
||||
v -= TransformViewerSpaceToBufferSpace(gizmo_start_drag_);
|
||||
break;
|
||||
}
|
||||
|
||||
draggable->DragMove(v.x(), v.y(), event->modifiers());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -268,13 +306,18 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
hand_dragging_ = false;
|
||||
UpdateCursor();
|
||||
|
||||
} else if (gizmo_click_) {
|
||||
} else if (current_gizmo_) {
|
||||
|
||||
// Handle gizmo
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
gizmos_->GizmoRelease(command);
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
gizmo_click_ = false;
|
||||
if (gizmo_drag_started_) {
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
if (DraggableGizmo *draggable = dynamic_cast<DraggableGizmo*>(current_gizmo_)) {
|
||||
draggable->DragEnd(command);
|
||||
}
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
gizmo_drag_started_ = false;
|
||||
}
|
||||
current_gizmo_ = nullptr;
|
||||
|
||||
} else {
|
||||
|
||||
@@ -284,6 +327,69 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton && gizmos_) {
|
||||
QPointF ptr = TransformViewerSpaceToBufferSpace(event->pos());
|
||||
foreach (NodeGizmo *g, gizmos_->GetGizmos()) {
|
||||
if (TextGizmo *text = dynamic_cast<TextGizmo*>(g)) {
|
||||
if (text->GetRect().contains(ptr)) {
|
||||
QTransform gizmo_transform = GenerateGizmoTransform();
|
||||
|
||||
ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this);
|
||||
Html::HtmlToDoc(text_edit->document(), text->GetHtml());
|
||||
text_edit->setProperty("gizmo", reinterpret_cast<quintptr>(text));
|
||||
|
||||
QRectF transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect();
|
||||
text_edit->setGeometry(transformed_geom.toRect());
|
||||
|
||||
ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this);
|
||||
|
||||
QPoint pos = mapToGlobal(QPoint(transformed_geom.x(), transformed_geom.y() - toolbar->height()));
|
||||
for (QScreen *screen : qApp->screens()) {
|
||||
if (screen->geometry().contains(pos)) {
|
||||
if (pos.x() + toolbar->width() > screen->geometry().right()) {
|
||||
pos.setX(screen->geometry().right() - toolbar->width());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
toolbar->move(pos);
|
||||
toolbar->show();
|
||||
|
||||
text_edit->show();
|
||||
|
||||
connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged);
|
||||
|
||||
text_edit->ConnectToolBar(toolbar);
|
||||
|
||||
QPoint text_edit_pos = text_edit->mapFrom(this, event->pos());
|
||||
|
||||
// Ensure text edit is actually focused rather than the toolbar
|
||||
connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [this, text_edit, text_edit_pos]{
|
||||
// Grab focus back from the toolbar
|
||||
this->raise();
|
||||
this->activateWindow();
|
||||
text_edit->setFocus();
|
||||
|
||||
// Start text cursor where the user clicked
|
||||
text_edit->setTextCursor(text_edit->cursorForPosition(text_edit_pos));
|
||||
|
||||
// HACK: On macOS, for some reason the QDockWidget receives focus before the
|
||||
// ViewerTextEditor, causing the editor to close prematurely. However this only
|
||||
// happens the first time the editor receives focus and not subsequent times, so
|
||||
// if we get it to only listen after the first one, this solves the problem.
|
||||
text_edit->SetListenToFocusEvents(true);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
emit DragEntered(event);
|
||||
@@ -396,7 +502,11 @@ void ViewerDisplayWidget::OnPaint()
|
||||
|
||||
QPainter p(inner_widget());
|
||||
p.setWorldTransform(GenerateGizmoTransform());
|
||||
gizmos_->DrawGizmos(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range), &p);
|
||||
|
||||
gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range));
|
||||
foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) {
|
||||
gizmo->Draw(&p);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw action/title safe areas
|
||||
@@ -404,7 +514,7 @@ void ViewerDisplayWidget::OnPaint()
|
||||
QPainter p(inner_widget());
|
||||
p.setWorldTransform(GenerateWorldTransform());
|
||||
|
||||
p.setPen(Qt::lightGray);
|
||||
p.setPen(QPen(Qt::lightGray, 0));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
|
||||
int x = 0, y = 0, w = width(), h = height();
|
||||
@@ -563,6 +673,31 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform()
|
||||
return gizmo_transform;
|
||||
}
|
||||
|
||||
NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPointF &p)
|
||||
{
|
||||
for (auto it=gizmos_->GetGizmos().crbegin(); it!=gizmos_->GetGizmos().crend(); it++) {
|
||||
NodeGizmo *gizmo = *it;
|
||||
if (PointGizmo *point = dynamic_cast<PointGizmo*>(gizmo)) {
|
||||
if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) {
|
||||
return point;
|
||||
}
|
||||
} else if (PolygonGizmo *poly = dynamic_cast<PolygonGizmo*>(gizmo)) {
|
||||
if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) {
|
||||
return poly;
|
||||
}
|
||||
} else if (PathGizmo *path = dynamic_cast<PathGizmo*>(gizmo)) {
|
||||
if (path->GetPath().contains(p)) {
|
||||
return path;
|
||||
}
|
||||
} else if (ScreenGizmo *screen = dynamic_cast<ScreenGizmo*>(gizmo)) {
|
||||
// NOTE: Perhaps this should limit to the actual visible screen space? We'll see.
|
||||
return screen;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e)
|
||||
{
|
||||
// Do this no matter what, emits signal to any pixel samplers
|
||||
@@ -657,4 +792,14 @@ void ViewerDisplayWidget::UpdateFromQueue()
|
||||
update();
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::TextEditChanged()
|
||||
{
|
||||
ViewerTextEditor *editor = static_cast<ViewerTextEditor *>(sender());
|
||||
|
||||
TextGizmo *gizmo = reinterpret_cast<TextGizmo*>(editor->property("gizmo").value<quintptr>());
|
||||
|
||||
QString html = Html::DocToHtml(editor->document());
|
||||
gizmo->UpdateInputHtml(html, GetGizmoTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
* @brief Transform a point from viewer space to the buffer space.
|
||||
* Multiplies by the inverted transform matrix to undo the scaling and translation.
|
||||
*/
|
||||
QPoint TransformViewerSpaceToBufferSpace(QPoint pos);
|
||||
QPointF TransformViewerSpaceToBufferSpace(const QPointF &pos);
|
||||
|
||||
bool IsDeinterlacing() const
|
||||
{
|
||||
@@ -216,6 +216,8 @@ protected:
|
||||
*/
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
||||
virtual void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
|
||||
virtual void dragLeaveEvent(QDragLeaveEvent* event) override;
|
||||
@@ -255,6 +257,8 @@ private:
|
||||
return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base());
|
||||
}
|
||||
|
||||
NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p);
|
||||
|
||||
/**
|
||||
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
||||
*/
|
||||
@@ -302,10 +306,11 @@ private:
|
||||
|
||||
Node* gizmos_;
|
||||
NodeValueRow gizmo_db_;
|
||||
rational gizmo_drag_time_;
|
||||
VideoParams gizmo_params_;
|
||||
QPoint gizmo_start_drag_;
|
||||
bool gizmo_click_;
|
||||
QPoint gizmo_last_drag_;
|
||||
NodeGizmo *current_gizmo_;
|
||||
bool gizmo_drag_started_;
|
||||
|
||||
rational time_;
|
||||
|
||||
@@ -358,6 +363,8 @@ private slots:
|
||||
|
||||
void UpdateFromQueue();
|
||||
|
||||
void TextEditChanged();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,529 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "viewertexteditor.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QColorDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/colorbutton/colorbutton.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QTextEdit
|
||||
|
||||
ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) :
|
||||
super(parent),
|
||||
transparent_clone_(nullptr),
|
||||
block_update_toolbar_signal_(false),
|
||||
listen_to_focus_events_(false)
|
||||
{
|
||||
// Ensure default text color is white
|
||||
QPalette p = palette();
|
||||
p.setColor(QPalette::Text, Qt::white);
|
||||
setPalette(p);
|
||||
|
||||
document()->setDefaultStyleSheet(QStringLiteral("body { color: white; }"));
|
||||
|
||||
viewport()->setAutoFillBackground(false);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
connect(horizontalScrollBar(), &QScrollBar::rangeChanged, this, &ViewerTextEditor::LockScrollBarMaximumToZero);
|
||||
connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &ViewerTextEditor::LockScrollBarMaximumToZero);
|
||||
|
||||
// Force DPI to the same one that we're using in the actual render
|
||||
dpi_force_ = QImage(1, 1, QImage::Format_RGBA8888_Premultiplied);
|
||||
|
||||
const int dpm = 3780 * scale;
|
||||
dpi_force_.setDotsPerMeterX(dpm);
|
||||
dpi_force_.setDotsPerMeterY(dpm);
|
||||
document()->documentLayout()->setPaintDevice(&dpi_force_);
|
||||
|
||||
connect(qApp, &QApplication::focusChanged, this, &ViewerTextEditor::FocusChanged);
|
||||
connect(this, &QTextEdit::currentCharFormatChanged, this, &ViewerTextEditor::FormatChanged);
|
||||
connect(document(), &QTextDocument::contentsChanged, this, &ViewerTextEditor::DocumentChanged, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::ConnectToolBar(ViewerTextEditorToolBar *toolbar)
|
||||
{
|
||||
connect(this, &ViewerTextEditor::destroyed, toolbar, &ViewerTextEditorToolBar::deleteLater);
|
||||
|
||||
connect(toolbar, &ViewerTextEditorToolBar::FamilyChanged, this, &ViewerTextEditor::SetFamily);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::SizeChanged, this, &ViewerTextEditor::setFontPointSize);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::StyleChanged, this, &ViewerTextEditor::SetStyle);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::UnderlineChanged, this, &ViewerTextEditor::setFontUnderline);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::StrikethroughChanged, this, &ViewerTextEditor::SetFontStrikethrough);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::ColorChanged, this, &ViewerTextEditor::setTextColor);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::SmallCapsChanged, this, &ViewerTextEditor::SetSmallCaps);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::StretchChanged, this, &ViewerTextEditor::SetFontStretch);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::KerningChanged, this, &ViewerTextEditor::SetFontKerning);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::LineHeightChanged, this, &ViewerTextEditor::SetLineHeight);
|
||||
connect(toolbar, &ViewerTextEditorToolBar::AlignmentChanged, this, [this](Qt::Alignment a){
|
||||
this->setAlignment(a);
|
||||
|
||||
// Ensure no buttons are checked that shouldn't be
|
||||
static_cast<ViewerTextEditorToolBar*>(sender())->SetAlignment(a);
|
||||
});
|
||||
|
||||
UpdateToolBar(toolbar, this->currentCharFormat(), this->textCursor().blockFormat(), this->alignment());
|
||||
|
||||
toolbars_.append(toolbar);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
super::keyPressEvent(event);
|
||||
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerTextEditor::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPainter p(this->viewport());
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
|
||||
QRect r = e->rect();
|
||||
if (r.isValid())
|
||||
p.setClipRect(r, Qt::IntersectClip);
|
||||
ctx.clip = r;
|
||||
|
||||
ctx.cursorPosition = this->textCursor().position();
|
||||
|
||||
if (this->textCursor().hasSelection()) {
|
||||
QAbstractTextDocumentLayout::Selection selection;
|
||||
selection.cursor = this->textCursor();
|
||||
|
||||
QPalette::ColorGroup cg = this->hasFocus() ? QPalette::Active : QPalette::Inactive;
|
||||
QBrush b = ctx.palette.brush(cg, QPalette::Highlight);
|
||||
QColor bc = b.color();
|
||||
bc.setAlpha(128);
|
||||
b.setColor(bc);
|
||||
selection.format.setBackground(b);
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
if (this->style()->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, this)) {
|
||||
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
||||
}
|
||||
ctx.selections.append(selection);
|
||||
}
|
||||
|
||||
transparent_clone_->documentLayout()->draw(&p, ctx);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment)
|
||||
{
|
||||
QFontDatabase fd;
|
||||
|
||||
QString family = f.fontFamily();
|
||||
if (family.isEmpty()) {
|
||||
family = qApp->font().family();
|
||||
}
|
||||
|
||||
QString style = f.fontStyleName().toString();
|
||||
QStringList styles = fd.styles(family);
|
||||
if (!styles.isEmpty() && (style.isEmpty() || !styles.contains(style))) {
|
||||
// There seems to be no better way to find the "regular" style outside of this heuristic.
|
||||
// Feel free to add more if a font isn't working right.
|
||||
style = QStringLiteral("Regular");
|
||||
if (!styles.contains(style)) {
|
||||
style = QStringLiteral("Normal");
|
||||
}
|
||||
if (!styles.contains(style)) {
|
||||
style = QStringLiteral("R");
|
||||
}
|
||||
if (!styles.contains(style)) {
|
||||
style = styles.first();
|
||||
}
|
||||
}
|
||||
|
||||
toolbar->SetFontFamily(family);
|
||||
toolbar->SetFontSize(f.fontPointSize());
|
||||
toolbar->SetStyle(style);
|
||||
toolbar->SetUnderline(f.fontUnderline());
|
||||
toolbar->SetStrikethrough(f.fontStrikeOut());
|
||||
toolbar->SetAlignment(alignment);
|
||||
toolbar->SetColor(f.foreground().color());
|
||||
toolbar->SetSmallCaps(f.fontCapitalization() == QFont::SmallCaps);
|
||||
toolbar->SetStretch(f.fontStretch() == 0 ? 100 : f.fontStretch());
|
||||
toolbar->SetKerning(f.fontLetterSpacing() == 0.0 ? 100 : f.fontLetterSpacing());
|
||||
toolbar->SetLineHeight(b.lineHeight() == 0.0 ? 100 : b.lineHeight());
|
||||
}
|
||||
|
||||
void ViewerTextEditor::FocusChanged(QWidget *old, QWidget *now)
|
||||
{
|
||||
if (!listen_to_focus_events_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget *test = now;
|
||||
|
||||
if (!test) {
|
||||
// Ignore null focuses because that could be one of the toolbar widgets simply losing focus
|
||||
// and that would be undesirable to close the text editor from
|
||||
return;
|
||||
}
|
||||
|
||||
while (test) {
|
||||
if (test == this
|
||||
|| dynamic_cast<ViewerTextEditorToolBar*>(test)
|
||||
|| dynamic_cast<SliderLadder*>(test)) {
|
||||
return;
|
||||
}
|
||||
|
||||
test = test->parentWidget();
|
||||
}
|
||||
|
||||
// If we didn't return in the loop, the user must have focused on something else
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ViewerTextEditor::FormatChanged(const QTextCharFormat &f)
|
||||
{
|
||||
if (!block_update_toolbar_signal_) {
|
||||
foreach (ViewerTextEditorToolBar *toolbar, toolbars_) {
|
||||
UpdateToolBar(toolbar, f, textCursor().blockFormat(), this->alignment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetFamily(const QString &s)
|
||||
{
|
||||
ViewerTextEditorToolBar *toolbar = static_cast<ViewerTextEditorToolBar *>(sender());
|
||||
|
||||
QTextCharFormat f;
|
||||
f.setFontFamily(s);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
|
||||
f.setFontFamilies({s});
|
||||
#endif
|
||||
|
||||
ApplyStyle(&f, s, toolbar->GetFontStyleName());
|
||||
|
||||
MergeCharFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetStyle(const QString &s)
|
||||
{
|
||||
ViewerTextEditorToolBar *toolbar = static_cast<ViewerTextEditorToolBar *>(sender());
|
||||
|
||||
QTextCharFormat f;
|
||||
|
||||
ApplyStyle(&f, toolbar->GetFontFamily(), s);
|
||||
|
||||
MergeCharFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetFontStrikethrough(bool e)
|
||||
{
|
||||
QTextCharFormat f;
|
||||
f.setFontStrikeOut(e);
|
||||
MergeCharFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetSmallCaps(bool e)
|
||||
{
|
||||
QTextCharFormat f;
|
||||
f.setFontCapitalization(e ? QFont::SmallCaps : QFont::MixedCase);
|
||||
MergeCharFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetFontStretch(int i)
|
||||
{
|
||||
QTextCharFormat f;
|
||||
f.setFontStretch(i);
|
||||
MergeCharFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetFontKerning(qreal i)
|
||||
{
|
||||
QTextCharFormat f;
|
||||
f.setFontLetterSpacing(i);
|
||||
MergeCharFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::MergeCharFormat(const QTextCharFormat &fmt)
|
||||
{
|
||||
// mergeCurrentCharFormat throws a currentCharFormatChanged signal that updates the toolbar,
|
||||
// this can be undesirable if the user is currently typing a font
|
||||
block_update_toolbar_signal_ = true;
|
||||
mergeCurrentCharFormat(fmt);
|
||||
block_update_toolbar_signal_ = false;
|
||||
}
|
||||
|
||||
void ViewerTextEditor::ApplyStyle(QTextCharFormat *format, const QString &family, const QString &style)
|
||||
{
|
||||
// NOTE: Windows appears to require setting weight and italic manually, while macOS and Linux are
|
||||
// perfectly fine with just the style name
|
||||
QFontDatabase fd;
|
||||
format->setFontWeight(fd.weight(family, style));
|
||||
format->setFontItalic(fd.italic(family, style));
|
||||
|
||||
format->setFontStyleName(style);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::SetLineHeight(qreal i)
|
||||
{
|
||||
QTextBlockFormat f = this->textCursor().blockFormat();
|
||||
f.setLineHeight(i, QTextBlockFormat::ProportionalHeight);
|
||||
this->textCursor().setBlockFormat(f);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::LockScrollBarMaximumToZero()
|
||||
{
|
||||
static_cast<QScrollBar*>(sender())->setMaximum(0);
|
||||
}
|
||||
|
||||
void ViewerTextEditor::DocumentChanged()
|
||||
{
|
||||
// HACK: We want to show the text cursor and selections without necessarily rendering the text,
|
||||
// because the text is already being rendered underneath the gizmo (and rendering twice will
|
||||
// alter the overall look of the text while editing). This is something that Qt does not
|
||||
// support by default, and while it could be solved by subclassing QAbstractTextDocumentLayout,
|
||||
// this seems like overkill since 99% of QTextDocumentLayout's (the subclass used by default)
|
||||
// functionality and would need to be fully implemented ourselves. So instead, we clone the
|
||||
// document, set all of its colors to rgba(0,0,0,0) to make them transparent, and draw that
|
||||
// document instead. The result is cursor and selections being rendered without the text.
|
||||
// While this isn't the fastest code, nor is it the cleanest solution, it definitely works.
|
||||
delete transparent_clone_;
|
||||
transparent_clone_ = document()->clone(this);
|
||||
transparent_clone_->documentLayout()->setPaintDevice(&dpi_force_);
|
||||
|
||||
QTextCursor cursor(transparent_clone_);
|
||||
cursor.select(QTextCursor::Document);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setForeground(QColor(0, 0, 0, 0));
|
||||
cursor.mergeCharFormat(fmt);
|
||||
}
|
||||
|
||||
ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) :
|
||||
QWidget(parent, Qt::Tool | Qt::FramelessWindowHint),
|
||||
painted_(false)
|
||||
{
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||
outer_layout->setSpacing(0);
|
||||
|
||||
QHBoxLayout *basic_layout = new QHBoxLayout();
|
||||
outer_layout->addLayout(basic_layout);
|
||||
|
||||
int advanced_slider_width = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("9999.9%"));
|
||||
|
||||
font_combo_ = new QFontComboBox();
|
||||
connect(font_combo_, &QFontComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::UpdateFontStyleListAndEmitFamilyChanged);
|
||||
basic_layout->addWidget(font_combo_);
|
||||
|
||||
font_sz_slider_ = new FloatSlider();
|
||||
font_sz_slider_->SetMinimum(0.1);
|
||||
font_sz_slider_->SetMaximum(9999.9);
|
||||
font_sz_slider_->SetDecimalPlaces(1);
|
||||
font_sz_slider_->SetAlignment(Qt::AlignCenter);
|
||||
font_sz_slider_->setFixedWidth(advanced_slider_width);
|
||||
connect(font_sz_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::SizeChanged);
|
||||
font_sz_slider_->SetLadderElementCount(2);
|
||||
basic_layout->addWidget(font_sz_slider_);
|
||||
|
||||
style_combo_ = new QComboBox();
|
||||
connect(style_combo_, &QComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::StyleChanged);
|
||||
basic_layout->addWidget(style_combo_);
|
||||
|
||||
underline_btn_ = new QPushButton();
|
||||
connect(underline_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::UnderlineChanged);
|
||||
underline_btn_->setCheckable(true);
|
||||
underline_btn_->setIcon(icon::TextUnderline);
|
||||
basic_layout->addWidget(underline_btn_);
|
||||
|
||||
strikethrough_btn_ = new QPushButton();
|
||||
connect(strikethrough_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::StrikethroughChanged);
|
||||
strikethrough_btn_->setCheckable(true);
|
||||
strikethrough_btn_->setIcon(icon::TextStrikethrough);
|
||||
basic_layout->addWidget(strikethrough_btn_);
|
||||
|
||||
basic_layout->addWidget(QtUtils::CreateVerticalLine());
|
||||
|
||||
align_left_btn_ = new QPushButton();
|
||||
align_left_btn_->setCheckable(true);
|
||||
align_left_btn_->setIcon(icon::TextAlignLeft);
|
||||
connect(align_left_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignLeft);});
|
||||
basic_layout->addWidget(align_left_btn_);
|
||||
|
||||
align_center_btn_ = new QPushButton();
|
||||
align_center_btn_->setCheckable(true);
|
||||
align_center_btn_->setIcon(icon::TextAlignCenter);
|
||||
connect(align_center_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignCenter);});
|
||||
basic_layout->addWidget(align_center_btn_);
|
||||
|
||||
align_right_btn_ = new QPushButton();
|
||||
align_right_btn_->setCheckable(true);
|
||||
align_right_btn_->setIcon(icon::TextAlignRight);
|
||||
connect(align_right_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignRight);});
|
||||
basic_layout->addWidget(align_right_btn_);
|
||||
|
||||
align_justify_btn_ = new QPushButton();
|
||||
align_justify_btn_->setCheckable(true);
|
||||
align_justify_btn_->setIcon(icon::TextAlignJustify);
|
||||
connect(align_justify_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignJustify);});
|
||||
basic_layout->addWidget(align_justify_btn_);
|
||||
|
||||
basic_layout->addWidget(QtUtils::CreateVerticalLine());
|
||||
|
||||
color_btn_ = new QPushButton();
|
||||
color_btn_->setAutoFillBackground(true);
|
||||
connect(color_btn_, &QPushButton::clicked, this, [this]{
|
||||
QColor c = color_btn_->property("color").value<QColor>();
|
||||
|
||||
QColorDialog cd(c, this);
|
||||
if (cd.exec() == QDialog::Accepted) {
|
||||
c = cd.selectedColor();
|
||||
SetColor(c);
|
||||
emit ColorChanged(c);
|
||||
}
|
||||
});
|
||||
basic_layout->addWidget(color_btn_);
|
||||
|
||||
basic_layout->addStretch();
|
||||
|
||||
QHBoxLayout *advanced_layout = new QHBoxLayout();
|
||||
outer_layout->addLayout(advanced_layout);
|
||||
|
||||
advanced_layout->addWidget(new QLabel(tr("Stretch: "))); // FIXME: Procure icon
|
||||
|
||||
stretch_slider_ = new IntegerSlider();
|
||||
stretch_slider_->SetMinimum(0);
|
||||
stretch_slider_->SetDefaultValue(100);
|
||||
stretch_slider_->setFixedWidth(advanced_slider_width);
|
||||
stretch_slider_->SetFormat(tr("%1%"));
|
||||
connect(stretch_slider_, &IntegerSlider::ValueChanged, this, &ViewerTextEditorToolBar::StretchChanged);
|
||||
advanced_layout->addWidget(stretch_slider_);
|
||||
|
||||
advanced_layout->addWidget(new QLabel(tr("Kerning: "))); // FIXME: Procure icon
|
||||
|
||||
kerning_slider_ = new FloatSlider();
|
||||
kerning_slider_->SetMinimum(0);
|
||||
kerning_slider_->SetDefaultValue(100);
|
||||
kerning_slider_->SetDecimalPlaces(1);
|
||||
kerning_slider_->setFixedWidth(advanced_slider_width);
|
||||
kerning_slider_->SetFormat(tr("%1%"));
|
||||
connect(kerning_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::KerningChanged);
|
||||
advanced_layout->addWidget(kerning_slider_);
|
||||
|
||||
advanced_layout->addWidget(new QLabel(tr("Line Height: "))); // FIXME: Procure icon
|
||||
|
||||
line_height_slider_ = new FloatSlider();
|
||||
line_height_slider_->SetMinimum(0);
|
||||
line_height_slider_->SetDefaultValue(100);
|
||||
line_height_slider_->SetDecimalPlaces(1);
|
||||
line_height_slider_->setFixedWidth(advanced_slider_width);
|
||||
line_height_slider_->SetFormat(tr("%1%"));
|
||||
connect(line_height_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::LineHeightChanged);
|
||||
advanced_layout->addWidget(line_height_slider_);
|
||||
|
||||
small_caps_btn_ = new QPushButton();
|
||||
small_caps_btn_->setIcon(icon::TextSmallCaps);
|
||||
small_caps_btn_->setCheckable(true);
|
||||
connect(small_caps_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::SmallCapsChanged);
|
||||
advanced_layout->addWidget(small_caps_btn_);
|
||||
|
||||
advanced_layout->addStretch();
|
||||
|
||||
setAutoFillBackground(true);
|
||||
|
||||
resize(sizeHint());
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::SetAlignment(Qt::Alignment a)
|
||||
{
|
||||
align_left_btn_->setChecked(a == Qt::AlignLeft);
|
||||
align_center_btn_->setChecked(a == Qt::AlignCenter);
|
||||
align_right_btn_->setChecked(a == Qt::AlignRight);
|
||||
align_justify_btn_->setChecked(a == Qt::AlignJustify);
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::SetColor(const QColor &c)
|
||||
{
|
||||
color_btn_->setProperty("color", c);
|
||||
color_btn_->setStyleSheet(QStringLiteral("QPushButton { background: %1; }").arg(c.name()));
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
if (!painted_) {
|
||||
emit FirstPaint();
|
||||
painted_ = true;
|
||||
}
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::UpdateFontStyleList(const QString &family)
|
||||
{
|
||||
QString temp = style_combo_->currentText();
|
||||
|
||||
style_combo_->blockSignals(true);
|
||||
style_combo_->clear();
|
||||
QStringList l = QFontDatabase().styles(family);
|
||||
foreach (const QString &style, l) {
|
||||
style_combo_->addItem(style);
|
||||
}
|
||||
style_combo_->setCurrentText(temp);
|
||||
style_combo_->blockSignals(false);
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::UpdateFontStyleListAndEmitFamilyChanged(const QString &family)
|
||||
{
|
||||
// Ensures correct ordering of commands
|
||||
UpdateFontStyleList(family);
|
||||
emit FamilyChanged(family);
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mousePressEvent(event);
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
drag_anchor_ = event->pos();
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mouseMoveEvent(event);
|
||||
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
this->move(mapToParent(QPoint(event->pos() - drag_anchor_)));
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerTextEditorToolBar::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef VIEWERTEXTEDITOR_H
|
||||
#define VIEWERTEXTEDITOR_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QFontComboBox>
|
||||
#include <QTextEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "widget/slider/floatslider.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ViewerTextEditorToolBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ViewerTextEditorToolBar(QWidget *parent = nullptr);
|
||||
|
||||
QString GetFontFamily() const
|
||||
{
|
||||
return font_combo_->currentText();
|
||||
}
|
||||
|
||||
QString GetFontStyleName() const
|
||||
{
|
||||
return style_combo_->currentText();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void SetFontFamily(QString s)
|
||||
{
|
||||
font_combo_->blockSignals(true);
|
||||
font_combo_->setCurrentFont(s);
|
||||
UpdateFontStyleList(s);
|
||||
font_combo_->blockSignals(false);
|
||||
}
|
||||
|
||||
void SetStyle(QString style)
|
||||
{
|
||||
style_combo_->blockSignals(true);
|
||||
style_combo_->setCurrentText(style);
|
||||
style_combo_->blockSignals(false);
|
||||
}
|
||||
|
||||
void SetFontSize(double d) { font_sz_slider_->SetValue(d); }
|
||||
void SetUnderline(bool e) { underline_btn_->setChecked(e); }
|
||||
void SetStrikethrough(bool e) { strikethrough_btn_->setChecked(e); }
|
||||
void SetAlignment(Qt::Alignment a);
|
||||
void SetColor(const QColor &c);
|
||||
void SetSmallCaps(bool e) { small_caps_btn_->setChecked(e); }
|
||||
void SetStretch(int i) { stretch_slider_->SetValue(i); }
|
||||
void SetKerning(qreal i) { kerning_slider_->SetValue(i); }
|
||||
void SetLineHeight(qreal i) { line_height_slider_->SetValue(i); }
|
||||
|
||||
signals:
|
||||
void FamilyChanged(const QString &s);
|
||||
void SizeChanged(double d);
|
||||
void StyleChanged(const QString &s);
|
||||
void UnderlineChanged(bool e);
|
||||
void StrikethroughChanged(bool e);
|
||||
void AlignmentChanged(Qt::Alignment alignment);
|
||||
void ColorChanged(const QColor &c);
|
||||
void SmallCapsChanged(bool e);
|
||||
void StretchChanged(int i);
|
||||
void KerningChanged(qreal i);
|
||||
void LineHeightChanged(qreal i);
|
||||
|
||||
void FirstPaint();
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
virtual void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QPoint drag_anchor_;
|
||||
|
||||
QFontComboBox *font_combo_;
|
||||
|
||||
FloatSlider *font_sz_slider_;
|
||||
|
||||
QComboBox *style_combo_;
|
||||
|
||||
QPushButton *underline_btn_;
|
||||
QPushButton *strikethrough_btn_;
|
||||
|
||||
QPushButton *align_left_btn_;
|
||||
QPushButton *align_center_btn_;
|
||||
QPushButton *align_right_btn_;
|
||||
QPushButton *align_justify_btn_;
|
||||
|
||||
IntegerSlider *stretch_slider_;
|
||||
FloatSlider *kerning_slider_;
|
||||
FloatSlider *line_height_slider_;
|
||||
QPushButton *small_caps_btn_;
|
||||
|
||||
QPushButton *color_btn_;
|
||||
|
||||
bool painted_;
|
||||
|
||||
private slots:
|
||||
void UpdateFontStyleList(const QString &family);
|
||||
|
||||
void UpdateFontStyleListAndEmitFamilyChanged(const QString &family);
|
||||
|
||||
};
|
||||
|
||||
class ViewerTextEditor : public QTextEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ViewerTextEditor(double scale, QWidget *parent = nullptr);
|
||||
|
||||
void ConnectToolBar(ViewerTextEditorToolBar *toolbar);
|
||||
|
||||
void SetListenToFocusEvents(bool e) { listen_to_focus_events_ = e; }
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
static void UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment);
|
||||
|
||||
void MergeCharFormat(const QTextCharFormat &fmt);
|
||||
|
||||
void ApplyStyle(QTextCharFormat *format, const QString &family, const QString &style);
|
||||
|
||||
QVector<ViewerTextEditorToolBar *> toolbars_;
|
||||
|
||||
QImage dpi_force_;
|
||||
|
||||
QTextDocument *transparent_clone_;
|
||||
|
||||
bool block_update_toolbar_signal_;
|
||||
|
||||
bool listen_to_focus_events_;
|
||||
|
||||
private slots:
|
||||
void FocusChanged(QWidget *old, QWidget *now);
|
||||
|
||||
void FormatChanged(const QTextCharFormat &f);
|
||||
|
||||
void SetFamily(const QString &s);
|
||||
|
||||
void SetStyle(const QString &s);
|
||||
|
||||
void SetFontStrikethrough(bool e);
|
||||
|
||||
void SetSmallCaps(bool e);
|
||||
|
||||
void SetFontStretch(int i);
|
||||
|
||||
void SetFontKerning(qreal i);
|
||||
|
||||
void SetLineHeight(qreal i);
|
||||
|
||||
void LockScrollBarMaximumToZero();
|
||||
|
||||
void DocumentChanged();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VIEWERTEXTEDITOR_H
|
||||
Reference in New Issue
Block a user