mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-25 02:21:00 +00:00
graphview: display contextual menu on right click
display a contextual menu if user clicks a node or port with a right click.
This commit is contained in:
parent
bc7f4424ca
commit
6c7dccd450
4 changed files with 229 additions and 38 deletions
12
TODO.md
12
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
|
||||
|
|
113
src/app.rs
113
src/app.rs
|
@ -16,11 +16,13 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// 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", &["<primary>a"]);
|
||||
//application.set_accels_for_action("app.about", &["<primary>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::<u32>().expect("port id args[1]");
|
||||
let node_id = values[2].get::<u32>().expect("node id args[2]");
|
||||
let point = values[3].get::<graphene::Point>().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::<u32>().expect("node id args[1]");
|
||||
let point = values[2].get::<graphene::Point>().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
|
||||
|
|
28
src/gps.ui
28
src/gps.ui
|
@ -28,6 +28,34 @@
|
|||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="port_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="port menu entry delete the link">_Delete link</attribute>
|
||||
<attribute name="action">app.port.delete-link</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="node_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="Node menu entry delete the element">_Delete node</attribute>
|
||||
<attribute name="action">app.node.delete</attribute>
|
||||
<attribute name="accel"><primary>n</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="Node menu entry request pad">_Request pad</attribute>
|
||||
<attribute name="action">app.node.request-pad</attribute>
|
||||
<attribute name="accel"><primary>n</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="Node menu entry request pad">_Properties</attribute>
|
||||
<attribute name="action">app.node.properties</attribute>
|
||||
<attribute name="accel"><primary>n</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
|
||||
<object class="GtkAboutDialog" id="dialog-about">
|
||||
<property name="program-name">GstPipelineStudio</property>
|
||||
<property name="authors">Stéphane Cerveau</property>
|
||||
|
|
|
@ -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::<Self::Type>()
|
||||
.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::<Port>().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::<Node>().expect("Unable to cast to Node");
|
||||
let mut node_to = port_to.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().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::<Port>().expect("click event is not on the Port");
|
||||
let node = port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().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::<Node>().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::<Self::Type>()
|
||||
.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::<Port>().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::<Node>().expect("Unable to cast to Node");
|
||||
let mut node_to = port_to.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().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<Vec<Signal>> = 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 {
|
||||
|
|
Loading…
Reference in a new issue