From 6c7dccd45050a390ae619a734da08c370b87e5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Tue, 7 Dec 2021 12:20:21 +0100 Subject: [PATCH] graphview: display contextual menu on right click display a contextual menu if user clicks a node or port with a right click. --- TODO.md | 12 ++-- src/app.rs | 113 ++++++++++++++++++++++++++++++++- src/gps.ui | 28 +++++++++ src/graphmanager/graphview.rs | 114 +++++++++++++++++++++++++--------- 4 files changed, 229 insertions(+), 38 deletions(-) diff --git a/TODO.md b/TODO.md index f8d8df3..c112bc3 100644 --- a/TODO.md +++ b/TODO.md @@ -11,18 +11,22 @@ TODO: - [x] unable to connect in and in out and out - [] unable to connnec element with incompatible caps. - [x] unable to connect a port which is already connected -- [] create contextual menu on pad or element +- [x] create contextual menu on pad or element - [] upclass the element - [] create a crate for graphview/node/port - [x] save/load pipeline -- [] Run a pipeline with GStreamer -- [] Run the pipeline with GStreamer -- [] Control the pipeline with GStreamer +- [x] Run a pipeline with GStreamer +- [x] Run the pipeline with GStreamer +- [x] Control the pipeline with GStreamer - [x] Define the license - [] Connect the logs to the window - [] Create a window for the video output - [] Add multiple graphviews with tabs. +## bugs + +- [] crash with x11 on contextual menu + ## Code cleanup [] remove useless code from graphview diff --git a/src/app.rs b/src/app.rs index 4a589a7..1b2ed49 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,11 +16,13 @@ // along with this program. If not, see . // // SPDX-License-Identifier: GPL-3.0-only +use glib::Value; +use gtk::gdk::Rectangle; use gtk::prelude::*; -use gtk::{gio, glib}; +use gtk::{gio, glib, graphene}; use gtk::{ AboutDialog, Application, ApplicationWindow, Builder, Button, FileChooserAction, - FileChooserDialog, ResponseType, Statusbar, Viewport, + FileChooserDialog, PopoverMenu, ResponseType, Statusbar, Viewport, }; use std::cell::RefCell; use std::rc::{Rc, Weak}; @@ -242,7 +244,7 @@ impl GPSApp { } }); application.add_action(&action); - application.set_accels_for_action("app.about", &["a"]); + //application.set_accels_for_action("app.about", &["a"]); // Create a dialog to select GStreamer elements. let add_button: Button = self @@ -312,7 +314,112 @@ impl GPSApp { let app = upgrade_weak!(app_weak); let graph_view = app.graphview.borrow_mut(); graph_view.remove_all_nodes(); + let node_id = graph_view.next_node_id(); + graph_view.add_node_with_port( + node_id, + Node::new( + node_id, + "videotestsrc", + Pipeline::element_type("videotestsrc"), + ), + 0, + 1, + ); })); + + let app_weak = self.downgrade(); + // When user clicks on port with right button + self.graphview + .borrow() + .connect_local( + "port-right-clicked", + false, + glib::clone!(@weak application => @default-return None, move |values: &[Value]| { + let app = upgrade_weak!(app_weak, None); + + let port_id = values[1].get::().expect("port id args[1]"); + let node_id = values[2].get::().expect("node id args[2]"); + let point = values[3].get::().expect("point in args[3]"); + + let port_menu: gio::MenuModel = app + .builder + .object("port_menu") + .expect("Couldn't get menu model for port"); + + let pop_menu: PopoverMenu = PopoverMenu::from_model(Some(&port_menu)); + pop_menu.set_parent(&*app.graphview.borrow_mut()); + pop_menu.set_pointing_to(&Rectangle { + x: point.to_vec2().x() as i32, + y: point.to_vec2().y() as i32, + width: 0, + height: 0, + }); + // add an action to delete link + let action = gio::SimpleAction::new("port.delete-link", None); + action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| { + println!("port.delete-link port {} node {}", port_id, node_id); + pop_menu.unparent(); + })); + application.add_action(&action); + + pop_menu.show(); + None + }), + ) + .expect("Failed to register port-right-clicked signal of graphview"); + + // When user clicks on node with right button + let app_weak = self.downgrade(); + self.graphview + .borrow() + .connect_local( + "node-right-clicked", + false, + glib::clone!(@weak application => @default-return None, move |values: &[Value]| { + let app = upgrade_weak!(app_weak, None); + + let node_id = values[1].get::().expect("node id args[1]"); + let point = values[2].get::().expect("point in args[2]"); + + let node_menu: gio::MenuModel = app + .builder + .object("node_menu") + .expect("Couldn't get menu model for node"); + + let pop_menu: PopoverMenu = PopoverMenu::from_model(Some(&node_menu)); + pop_menu.set_parent(&*app.graphview.borrow_mut()); + pop_menu.set_pointing_to(&Rectangle { + x: point.to_vec2().x() as i32, + y: point.to_vec2().y() as i32, + width: 0, + height: 0, + }); + let action = gio::SimpleAction::new("node.delete", None); + action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| { + println!("node.delete {}", node_id); + pop_menu.unparent(); + })); + application.add_action(&action); + + let action = gio::SimpleAction::new("node.request-pad", None); + action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| { + println!("node.request-pad {}", node_id); + pop_menu.unparent(); + })); + application.add_action(&action); + let action = gio::SimpleAction::new("node.properties", None); + action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| { + println!("node.properties {}", node_id); + + pop_menu.unparent(); + })); + application.add_action(&action); + + pop_menu.show(); + None + }), + ) + .expect("Failed to register node-right-clicked signal of graphview"); } // Downgrade to a weak reference diff --git a/src/gps.ui b/src/gps.ui index 5b6098b..8547984 100644 --- a/src/gps.ui +++ b/src/gps.ui @@ -28,6 +28,34 @@ + +
+ + _Delete link + app.port.delete-link + +
+
+ +
+ + _Delete node + app.node.delete + <primary>n + + + _Request pad + app.node.request-pad + <primary>n + + + _Properties + app.node.properties + <primary>n + +
+
+ GstPipelineStudio Stéphane Cerveau diff --git a/src/graphmanager/graphview.rs b/src/graphmanager/graphview.rs index e7cdc6e..d99b5a2 100644 --- a/src/graphmanager/graphview.rs +++ b/src/graphmanager/graphview.rs @@ -25,10 +25,13 @@ use xml::writer::EmitterConfig; use xml::writer::XmlEvent as XMLWEvent; use super::{node::Node, node::NodeType, port::Port, port::PortDirection}; +use glib::subclass::Signal; +use once_cell::sync::Lazy; use std::fs::File; use std::io::BufReader; use gtk::{ + gdk::{BUTTON_PRIMARY, BUTTON_SECONDARY}, glib::{self, clone}, graphene, gsk, prelude::*, @@ -135,41 +138,63 @@ mod imp { obj.add_controller(&drag_controller); let gesture = gtk::GestureClick::new(); - gesture.connect_released(clone!(@weak gesture => move |_gesture, _n_press, x, y| { - let widget = drag_controller - .widget() - .expect("click event has no widget") + gesture.set_button(0); + gesture.connect_pressed( + clone!(@weak obj, @weak drag_controller => move |gesture, _n_press, x, y| { + if gesture.current_button() == BUTTON_SECONDARY { + let widget = drag_controller.widget().expect("click event has no widget") .dynamic_cast::() .expect("click event is not on the GraphView"); - if let Some(target) = widget.pick(x, y, gtk::PickFlags::DEFAULT) { - if let Some(target) = target.ancestor(Port::static_type()) { - let mut port_to = target.dynamic_cast::().expect("click event is not on the Port"); - if widget.port_is_linked(port_to.id()).is_none() { - let selected_port = widget.selected_port().to_owned(); - if let Some(mut port_from) = selected_port { - println!("Port {} is clicked at {}:{}", port_to.id(), x, y); - if widget.ports_compatible(&port_to) { - let mut node_from = port_from.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); - let mut node_to = port_to.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); - println!("add link"); - if *port_to.direction() == PortDirection::Output { - println!("swap ports and nodes to create the link"); - std::mem::swap(&mut node_from, &mut node_to); - std::mem::swap(&mut port_from, &mut port_to); + let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("port pick() did not return a widget"); + if let Some(target) = target.ancestor(Port::static_type()) { + let port = target.dynamic_cast::().expect("click event is not on the Port"); + let node = port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); + obj.emit_by_name("port-right-clicked", &[&port.id(), &node.id(), &graphene::Point::new(x as f32,y as f32)]).expect("unable to send signal"); + } else if let Some(target) = target.ancestor(Node::static_type()) { + let node = target.dynamic_cast::().expect("click event is not on the Node"); + obj.emit_by_name("node-right-clicked", &[&node.id(), &graphene::Point::new(x as f32,y as f32)]).expect("unable to send signal"); + } + } + }), + ); + + gesture.connect_released(clone!(@weak gesture, @weak drag_controller => move |_gesture, _n_press, x, y| { + if gesture.current_button() == BUTTON_PRIMARY { + let widget = drag_controller + .widget() + .expect("click event has no widget") + .dynamic_cast::() + .expect("click event is not on the GraphView"); + if let Some(target) = widget.pick(x, y, gtk::PickFlags::DEFAULT) { + if let Some(target) = target.ancestor(Port::static_type()) { + let mut port_to = target.dynamic_cast::().expect("click event is not on the Port"); + if widget.port_is_linked(port_to.id()).is_none() { + let selected_port = widget.selected_port().to_owned(); + if let Some(mut port_from) = selected_port { + println!("Port {} is clicked at {}:{}", port_to.id(), x, y); + if widget.ports_compatible(&port_to) { + let mut node_from = port_from.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); + let mut node_to = port_to.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); + println!("add link"); + if *port_to.direction() == PortDirection::Output { + println!("swap ports and nodes to create the link"); + std::mem::swap(&mut node_from, &mut node_to); + std::mem::swap(&mut port_from, &mut port_to); + } + widget.add_link(NodeLink { + id: widget.next_link_id(), + node_from: node_from.id(), + node_to: node_to.id(), + port_from: port_from.id(), + port_to: port_to.id(), + active: true + } ); } - widget.add_link(NodeLink { - id: widget.next_link_id(), - node_from: node_from.id(), - node_to: node_to.id(), - port_from: port_from.id(), - port_to: port_to.id(), - active: true - } ); + widget.set_selected_port(None); + } else { + println!("add selected port id"); + widget.set_selected_port(Some(&port_to)); } - widget.set_selected_port(None); - } else { - println!("add selected port id"); - widget.set_selected_port(Some(&port_to)); } } } @@ -184,6 +209,33 @@ mod imp { .values() .for_each(|node| node.unparent()) } + + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![ + Signal::builder( + "port-right-clicked", + &[ + u32::static_type().into(), + u32::static_type().into(), + graphene::Point::static_type().into(), + ], + <()>::static_type().into(), + ) + .build(), + Signal::builder( + "node-right-clicked", + &[ + u32::static_type().into(), + graphene::Point::static_type().into(), + ], + <()>::static_type().into(), + ) + .build(), + ] + }); + SIGNALS.as_ref() + } } impl WidgetImpl for GraphView {