diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index 8a28ed056..4cf192643 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -32,6 +32,8 @@ namespace olive { +const QString Project::kItemMimeType = QStringLiteral("application/x-oliveprojectitemdata"); + Project::Project() : is_modified_(false), autorecovery_saved_(true) diff --git a/app/node/project/project.h b/app/node/project/project.h index 558a051aa..09e7e8606 100644 --- a/app/node/project/project.h +++ b/app/node/project/project.h @@ -117,6 +117,8 @@ public: */ static Project *GetProjectFromObject(const QObject *o); + static const QString kItemMimeType; + signals: void NameChanged(); diff --git a/app/node/project/projectviewmodel.cpp b/app/node/project/projectviewmodel.cpp index 72c1219a5..3aefbb13d 100644 --- a/app/node/project/projectviewmodel.cpp +++ b/app/node/project/projectviewmodel.cpp @@ -271,7 +271,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const QStringList ProjectViewModel::mimeTypes() const { // Allow data from this model and a file list from external sources - return {QStringLiteral("application/x-oliveprojectitemdata"), QStringLiteral("text/uri-list")}; + return {Project::kItemMimeType, QStringLiteral("text/uri-list")}; } QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const @@ -312,7 +312,7 @@ QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const } // Set byte array as the mime data and return the mime data - data->setData(QStringLiteral("application/x-oliveprojectitemdata"), encoded_data); + data->setData(Project::kItemMimeType, encoded_data); return data; } @@ -331,9 +331,9 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action // Probe mime data for its format QStringList mime_formats = data->formats(); - if (mime_formats.contains(QStringLiteral("application/x-oliveprojectitemdata"))) { + if (mime_formats.contains(Project::kItemMimeType)) { // Data is drag/drop data from this model - QByteArray model_data = data->data(QStringLiteral("application/x-oliveprojectitemdata")); + QByteArray model_data = data->data(Project::kItemMimeType); // Use QDataStream to deserialize the data QDataStream stream(&model_data, QIODevice::ReadOnly); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index f40a654f1..dd1aa256c 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -539,78 +540,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) // See if there are any items attached if (!attached_items_.isEmpty()) { - // Move those items to the cursor - MoveAttachedNodesToCursor(event->pos()); - - // See if the user clicked on an edge (only when dropping single nodes) - if (attached_items_.size() == 1) { - Node* attached_node = attached_items_.first().item->GetNode(); - - QRect edge_detect_rect(event->pos(), event->pos()); - - int edge_detect_radius = fontMetrics().height(); - edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius); - - QList items = this->items(edge_detect_rect); - - NodeViewEdge* new_drop_edge = nullptr; - - // See if there is an edge here - for (QGraphicsItem* item : qAsConst(items)) { - new_drop_edge = dynamic_cast(item); - - if (new_drop_edge) { - drop_input_.Reset(); - - NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType(); - - // 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()) { - if (input == Node::kEnabledInput) { - // Ignore enabled input - continue; - } - - 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 (drop_input_.IsValid()) { - break; - } else { - new_drop_edge = nullptr; - } - } - } - - if (drop_edge_ != new_drop_edge) { - if (drop_edge_) { - drop_edge_->SetHighlighted(false); - } - - drop_edge_ = new_drop_edge; - - if (drop_edge_) { - drop_edge_->SetHighlighted(true); - } - } - } + ProcessMovingAttachedNodes(event->pos()); } } @@ -630,72 +560,10 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) bool had_attached_items = !attached_items_.isEmpty(); if (!attached_items_.isEmpty()) { - select_context = nullptr; - - QList items_at_cursor = this->items(event->pos()); - foreach (QGraphicsItem *i, items_at_cursor) { - if (NodeViewContext *context_item = dynamic_cast(i)) { - select_context = context_item->GetContext(); - break; - } - } + select_context = GetContextAtMousePos(event->pos()); if (select_context) { - { - MultiUndoCommand *add_command = new MultiUndoCommand(); - - foreach (const AttachedItem &ai, attached_items_) { - // Add node to the same graph that the context is in - add_command->add_child(new NodeAddCommand(select_context->parent(), ai.node)); - - // Add node to the context - if (ai.item) { - add_command->add_child(new NodeSetPositionCommand(ai.node, select_context, scene_.context_map().value(select_context)->MapScenePosToNodePosInContext(ai.item->pos()))); - select_nodes.append(ai.node); - } - } - - if (add_command->child_count()) { - add_command->redo_now(); - command->add_child(add_command); - } else { - delete add_command; - } - } - - { - // Dropped attached item onto an edge, connect it between them - MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); - if (attached_items_.size() == 1) { - Node* dropping_node = nullptr; - - foreach (const AttachedItem &ai, attached_items_) { - if (ai.item) { - dropping_node = ai.node; - break; - } - } - - if (dropping_node && drop_edge_) { - // Remove old edge - drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); - - // Place new edges - drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); - drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); - } - - drop_edge_ = nullptr; - } - if (drop_edge_command->child_count()) { - drop_edge_command->redo_now(); - command->add_child(drop_edge_command); - } else { - delete drop_edge_command; - } - } - - DetachItemsFromCursor(false); + select_nodes = ProcessDroppingAttachedNodes(command, select_context, event->pos()); } else { QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); } @@ -733,6 +601,96 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event) } } +void NodeView::dragEnterEvent(QDragEnterEvent *event) +{ + if (contexts_.empty()) { + event->ignore(); + return; + } + + QStringList mime_fmts = event->mimeData()->formats(); + + if (mime_fmts.contains(Project::kItemMimeType)) { + QByteArray model_data = event->mimeData()->data(Project::kItemMimeType); + QDataStream stream(&model_data, QIODevice::ReadOnly); + + // Variables to deserialize into + quintptr item_ptr; + QVector enabled_streams; + QVector new_attached; + + int y = 0; + + while (!stream.atEnd()) { + stream >> enabled_streams >> item_ptr; + + // Get Item object + Node* item = reinterpret_cast(item_ptr); + + if (ViewerOutput* f = dynamic_cast(item)) { + NodeViewItem *new_item; + + new_item = new NodeViewItem(f, nullptr); + new_item->SetFlowDirection(scene_.GetFlowDirection()); + y++; + scene_.addItem(new_item); + + new_attached.append({new_item, f, QPointF(0, y)}); + } + } + + if (new_attached.empty()) { + event->ignore(); + } else { + SetAttachedItems(new_attached); + + event->accept(); + } + } +} + +void NodeView::dragMoveEvent(QDragMoveEvent *event) +{ + if (attached_items_.empty()) { + event->ignore(); + } else { + ProcessMovingAttachedNodes(event->pos()); + + if (GetContextAtMousePos(event->pos())) { + event->accept(); + } else { + event->ignore(); + } + } +} + +void NodeView::dropEvent(QDropEvent *event) +{ + if (Node *drop_ctx = GetContextAtMousePos(event->pos())) { + MultiUndoCommand *command = new MultiUndoCommand(); + QVector select_nodes = ProcessDroppingAttachedNodes(command, drop_ctx, event->pos()); + Core::instance()->undo_stack()->pushIfHasChildren(command); + + scene_.context_map().value(drop_ctx)->Select(select_nodes); + + event->accept(); + } else { + DetachItemsFromCursor(false); + event->ignore(); + } +} + +void NodeView::dragLeaveEvent(QDragLeaveEvent *event) +{ + if (attached_items_.empty()) { + event->ignore(); + } else { + DetachItemsFromCursor(false); + + event->accept(); + } +} + void NodeView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -1030,6 +988,172 @@ void NodeView::MoveAttachedNodesToCursor(const QPoint& p) } } +void NodeView::ProcessMovingAttachedNodes(const QPoint &pos) +{ + // Move those items to the cursor + MoveAttachedNodesToCursor(pos); + + // See if the user clicked on an edge (only when dropping single nodes) + if (attached_items_.size() == 1) { + Node* attached_node = attached_items_.first().item->GetNode(); + + QRect edge_detect_rect(pos, pos); + + int edge_detect_radius = fontMetrics().height(); + edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius); + + QList items = this->items(edge_detect_rect); + + NodeViewEdge* new_drop_edge = nullptr; + + // See if there is an edge here + for (QGraphicsItem* item : qAsConst(items)) { + new_drop_edge = dynamic_cast(item); + + if (new_drop_edge) { + drop_input_.Reset(); + + NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType(); + + // 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()) { + if (input == Node::kEnabledInput) { + // Ignore enabled input + continue; + } + + 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 (new_drop_edge->input().node()->OutputsTo(attached_node, true)) { + drop_input_.Reset(); + } + + if (drop_input_.IsValid()) { + break; + } else { + new_drop_edge = nullptr; + } + } + } + + if (drop_edge_ != new_drop_edge) { + if (drop_edge_) { + drop_edge_->SetHighlighted(false); + } + + drop_edge_ = new_drop_edge; + + if (drop_edge_) { + drop_edge_->SetHighlighted(true); + } + } + } +} + +QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, Node *select_context, const QPoint &pos) +{ + QVector select_nodes; + + { + MultiUndoCommand *add_command = new MultiUndoCommand(); + + foreach (const AttachedItem &ai, attached_items_) { + if (select_context->OutputsTo(ai.node, true)) { + continue; + } + + if (ai.item) { + select_nodes.append(ai.node); + } + + if (select_context->ContextContainsNode(ai.node)) { + continue; + } + + // Add node to the same graph that the context is in + add_command->add_child(new NodeAddCommand(select_context->parent(), ai.node)); + + // Add node to the context + if (ai.item) { + add_command->add_child(new NodeSetPositionCommand(ai.node, select_context, scene_.context_map().value(select_context)->MapScenePosToNodePosInContext(ai.item->pos()))); + } + } + + if (add_command->child_count()) { + add_command->redo_now(); + command->add_child(add_command); + } else { + delete add_command; + } + } + + { + // Dropped attached item onto an edge, connect it between them + MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); + if (attached_items_.size() == 1) { + Node* dropping_node = nullptr; + + foreach (const AttachedItem &ai, attached_items_) { + if (ai.item && !select_context->ContextContainsNode(ai.node) && !select_context->OutputsTo(ai.node, true)) { + dropping_node = ai.node; + break; + } + } + + if (dropping_node && drop_edge_) { + // Remove old edge + drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); + + // Place new edges + drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); + drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + } + + drop_edge_ = nullptr; + } + if (drop_edge_command->child_count()) { + drop_edge_command->redo_now(); + command->add_child(drop_edge_command); + } else { + delete drop_edge_command; + } + } + + DetachItemsFromCursor(false); + + return select_nodes; +} + +Node *NodeView::GetContextAtMousePos(const QPoint &p) +{ + QList items_at_cursor = this->items(p); + foreach (QGraphicsItem *i, items_at_cursor) { + if (NodeViewContext *context_item = dynamic_cast(i)) { + return context_item->GetContext(); + } + } + + return nullptr; +} + void NodeView::ConnectSelectionChangedSignal() { connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::UpdateSelectionCache); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index e367e5b2e..15a5ddc28 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -133,6 +133,11 @@ protected: virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void mouseDoubleClickEvent(QMouseEvent* event) override; + virtual void dragEnterEvent(QDragEnterEvent *event) override; + virtual void dragMoveEvent(QDragMoveEvent *event) override; + virtual void dropEvent(QDropEvent *event) override; + virtual void dragLeaveEvent(QDragLeaveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; virtual void ZoomIntoCursorPosition(QWheelEvent *event, double multiplier, const QPointF &cursor_pos) override; @@ -149,6 +154,9 @@ private: void SetFlowDirection(NodeViewCommon::FlowDirection dir); void MoveAttachedNodesToCursor(const QPoint &p); + void ProcessMovingAttachedNodes(const QPoint &pos); + QVector ProcessDroppingAttachedNodes(MultiUndoCommand *command, Node *select_context, const QPoint &pos); + Node *GetContextAtMousePos(const QPoint &p); void ConnectSelectionChangedSignal(); void DisconnectSelectionChangedSignal(); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index d17b9a3d8..e25255497 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -55,10 +55,10 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event) QStringList mime_formats = event->GetMimeData()->formats(); // Listen for MIME data from a ProjectViewModel - if (mime_formats.contains(QStringLiteral("application/x-oliveprojectitemdata"))) { + if (mime_formats.contains(Project::kItemMimeType)) { // Data is drag/drop data from a ProjectViewModel - QByteArray model_data = event->GetMimeData()->data(QStringLiteral("application/x-oliveprojectitemdata")); + QByteArray model_data = event->GetMimeData()->data(Project::kItemMimeType); // Use QDataStream to deserialize the data QDataStream stream(&model_data, QIODevice::ReadOnly); diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 28b182b5a..9ede7b066 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -87,7 +87,7 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl if (!streams.isEmpty()) { data_stream << streams << reinterpret_cast(GetConnectedNode()); - mimedata->setData(QStringLiteral("application/x-oliveprojectitemdata"), encoded_data); + mimedata->setData(Project::kItemMimeType, encoded_data); drag->setMimeData(mimedata); drag->exec(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 7a4e25da5..ded1dd4fe 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1514,14 +1514,14 @@ void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to) void ViewerWidget::DragEntered(QDragEnterEvent* event) { - if (event->mimeData()->formats().contains(QStringLiteral("application/x-oliveprojectitemdata"))) { + if (event->mimeData()->formats().contains(Project::kItemMimeType)) { event->accept(); } } void ViewerWidget::Dropped(QDropEvent *event) { - QByteArray mimedata = event->mimeData()->data(QStringLiteral("application/x-oliveprojectitemdata")); + QByteArray mimedata = event->mimeData()->data(Project::kItemMimeType); QDataStream stream(&mimedata, QIODevice::ReadOnly); // Variables to deserialize into