plugindialogs: rename module and support properties in element

rename pluginlist module to plugindialogs
Add display_plugin_properties to modify properties
from an element.
Add a way to modify the location if element is URI_TYPE_SRC
Able to remove a node from the graph
This commit is contained in:
Stéphane Cerveau 2021-12-08 14:15:22 +01:00
parent 6c7dccd450
commit 6756b34919
9 changed files with 310 additions and 53 deletions

View file

@ -26,7 +26,9 @@ TODO:
## bugs ## bugs
- [] crash with x11 on contextual menu - [] crash with x11 on contextual menu
- [] check that element exists before creating it on file load.
## Code cleanup ## Code cleanup
[] remove useless code from graphview [] remove useless code from graphview
[] Move render to a specific module

View file

@ -25,11 +25,12 @@ use gtk::{
FileChooserDialog, PopoverMenu, ResponseType, Statusbar, Viewport, FileChooserDialog, PopoverMenu, ResponseType, Statusbar, Viewport,
}; };
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::{error, ops}; use std::{error, ops};
use crate::pipeline::{Pipeline, PipelineState}; use crate::pipeline::{Pipeline, PipelineState};
use crate::pluginlist; use crate::plugindialogs;
use crate::graphmanager::{GraphView, Node}; use crate::graphmanager::{GraphView, Node};
@ -115,6 +116,47 @@ impl GPSApp {
app.drop(); app.drop();
}); });
} }
pub fn get_file_from_dialog<F: Fn(GPSApp, String) + 'static>(app: &GPSApp, save: bool, f: F) {
let mut message = "Open file";
let mut ok_button = "Open";
let cancel_button = "Cancel";
let mut action = FileChooserAction::Open;
if save {
message = "Save file";
ok_button = "Save";
action = FileChooserAction::Save;
}
let file_chooser: FileChooserDialog = FileChooserDialog::new(
Some(message),
Some(&app.window),
action,
&[
(ok_button, ResponseType::Ok),
(cancel_button, ResponseType::Cancel),
],
);
let app_weak = app.downgrade();
file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| {
let app = upgrade_weak!(app_weak);
if response == ResponseType::Ok {
let file = d.file().expect("Couldn't get file");
let filename = String::from(
file.path()
.expect("Couldn't get file path")
.to_str()
.expect("unable to convert to string"),
);
f(app, filename);
}
d.close();
});
file_chooser.show();
}
pub fn show_error_dialog(fatal: bool, message: &str) { pub fn show_error_dialog(fatal: bool, message: &str) {
let app = gio::Application::default() let app = gio::Application::default()
.expect("No default application") .expect("No default application")
@ -165,25 +207,11 @@ impl GPSApp {
// Add a dialog to open the graph // Add a dialog to open the graph
action.connect_activate(glib::clone!(@weak window => move |_, _| { action.connect_activate(glib::clone!(@weak window => move |_, _| {
let app = upgrade_weak!(app_weak); let app = upgrade_weak!(app_weak);
let file_chooser = FileChooserDialog::new( GPSApp::get_file_from_dialog(&app, false, move |app, filename|
Some("Open File"), {
Some(&window),
FileChooserAction::Open,
&[("Open", ResponseType::Ok), ("Cancel", ResponseType::Cancel)],
);
file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| {
if response == ResponseType::Ok {
let file = d.file().expect("Couldn't get file");
let filename = String::from(file.path().expect("Couldn't get file path").to_str().expect("unable to convert to string"));
println!("Open file {}", filename); println!("Open file {}", filename);
app.load_graph(&filename).expect("Unable to open file"); app.load_graph(&filename).expect("Unable to open file");
}
d.close();
}); });
file_chooser.show();
})); }));
application.add_action(&action); application.add_action(&action);
application.set_accels_for_action("app.open", &["<primary>o"]); application.set_accels_for_action("app.open", &["<primary>o"]);
@ -192,26 +220,13 @@ impl GPSApp {
let action = gio::SimpleAction::new("save_as", None); let action = gio::SimpleAction::new("save_as", None);
let app_weak = self.downgrade(); let app_weak = self.downgrade();
action.connect_activate(glib::clone!(@weak window => move |_, _| { action.connect_activate(glib::clone!(@weak window => move |_, _| {
let app = upgrade_weak!(app_weak); let app = upgrade_weak!(app_weak);
let file_chooser = FileChooserDialog::new( GPSApp::get_file_from_dialog(&app, true, move |app, filename|
Some("Save File"), {
Some(&window),
FileChooserAction::Open,
&[("Save", ResponseType::Ok), ("Cancel", ResponseType::Cancel)],
);
file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| {
if response == ResponseType::Ok {
let file = d.file().expect("Couldn't get file");
let filename = String::from(file.path().expect("Couldn't get file path").to_str().expect("unable to convert to string"));
println!("Save file {}", filename); println!("Save file {}", filename);
app.save_graph(&filename).expect("Unable to save file"); app.save_graph(&filename).expect("Unable to save file");
}
d.close();
}); });
file_chooser.show();
})); }));
application.add_action(&action); application.add_action(&action);
@ -255,7 +270,7 @@ impl GPSApp {
add_button.connect_clicked(glib::clone!(@weak window => move |_| { add_button.connect_clicked(glib::clone!(@weak window => move |_| {
let app = upgrade_weak!(app_weak); let app = upgrade_weak!(app_weak);
let elements = Pipeline::elements_list().expect("Unable to obtain element's list"); let elements = Pipeline::elements_list().expect("Unable to obtain element's list");
pluginlist::display_plugin_list(&app, &elements); plugindialogs::display_plugin_list(&app, &elements);
})); }));
let add_button: Button = self let add_button: Button = self
@ -319,7 +334,7 @@ impl GPSApp {
node_id, node_id,
Node::new( Node::new(
node_id, node_id,
"videotestsrc", "filesrc",
Pipeline::element_type("videotestsrc"), Pipeline::element_type("videotestsrc"),
), ),
0, 0,
@ -395,8 +410,11 @@ impl GPSApp {
height: 0, height: 0,
}); });
let action = gio::SimpleAction::new("node.delete", None); let action = gio::SimpleAction::new("node.delete", None);
let app_weak = app.downgrade();
action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| { action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| {
let app = upgrade_weak!(app_weak);
println!("node.delete {}", node_id); println!("node.delete {}", node_id);
app.graphview.borrow_mut().remove_node(node_id);
pop_menu.unparent(); pop_menu.unparent();
})); }));
application.add_action(&action); application.add_action(&action);
@ -406,15 +424,18 @@ impl GPSApp {
println!("node.request-pad {}", node_id); println!("node.request-pad {}", node_id);
pop_menu.unparent(); pop_menu.unparent();
})); }));
application.add_action(&action); application.add_action(&action);
let action = gio::SimpleAction::new("node.properties", None); let action = gio::SimpleAction::new("node.properties", None);
action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| { action.connect_activate(glib::clone!(@weak pop_menu => move |_,_| {
println!("node.properties {}", node_id); println!("node.properties {}", node_id);
let node = app.graphview.borrow().node(&node_id).unwrap();
plugindialogs::display_plugin_properties(&app, &node.name(), node_id);
pop_menu.unparent(); pop_menu.unparent();
})); }));
application.add_action(&action); application.add_action(&action);
pop_menu.show(); pop_menu.show();
None None
}), }),
@ -434,6 +455,15 @@ impl GPSApp {
let graph_view = self.graphview.borrow_mut(); let graph_view = self.graphview.borrow_mut();
let node_id = graph_view.next_node_id(); let node_id = graph_view.next_node_id();
let pads = Pipeline::pads(&element_name, false); let pads = Pipeline::pads(&element_name, false);
if Pipeline::element_is_uri_src_handler(&element_name) {
GPSApp::get_file_from_dialog(self, false, move |app, filename| {
println!("Open file {}", filename);
let node = app.graphview.borrow().node(&node_id).unwrap();
let mut properties: HashMap<String, String> = HashMap::new();
properties.insert(String::from("location"), filename);
node.update_node_properties(&properties);
});
}
graph_view.add_node_with_port( graph_view.add_node_with_port(
node_id, node_id,
Node::new( Node::new(
@ -446,6 +476,11 @@ impl GPSApp {
); );
} }
pub fn update_element_properties(&self, node_id: u32, properties: &HashMap<String, String>) {
let node = self.graphview.borrow().node(&node_id).unwrap();
node.update_node_properties(properties);
}
pub fn save_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> { pub fn save_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
let graph_view = self.graphview.borrow_mut(); let graph_view = self.graphview.borrow_mut();
graph_view.render_xml(filename)?; graph_view.render_xml(filename)?;

View file

@ -68,8 +68,8 @@
<property name="default-height">260</property> <property name="default-height">260</property>
<property name="hide-on-close">True</property> <property name="hide-on-close">True</property>
<child> <child>
<object class="GtkBox" id="v_box"> <object class="GtkPaned">
<property name="orientation">horizontal</property> <property name="position">200</property>s
<child> <child>
<object class="GtkScrolledWindow" id="scrolled_window"> <object class="GtkScrolledWindow" id="scrolled_window">
<property name="hexpand">True</property> <property name="hexpand">True</property>
@ -80,7 +80,7 @@
</object> </object>
</child> </child>
<child> <child>
<object class="GtkScrolledWindow" id="scrolled2_window"> <object class="GtkScrolledWindow">
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
<child> <child>
@ -91,6 +91,36 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkDialog" id="dialog-plugin-properties">
<property name="transient-for">mainwindow</property>
<property name="default-width">320</property>
<property name="default-height">260</property>
<property name="hide-on-close">True</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkBox" id="box-plugin-properties">
<property name="orientation">vertical</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="apply-plugin-properties">
<property name="halign">end</property>
<property name="hexpand">1</property>
<property name="receives-default">1</property>
<property name="label" translatable="yes">apply</property>
</object>
</child>
</object>
</child>
</object>
<object class="GtkApplicationWindow" id="mainwindow"> <object class="GtkApplicationWindow" id="mainwindow">
<property name="title" translatable="yes">GstPipelineStudio</property> <property name="title" translatable="yes">GstPipelineStudio</property>
<property name="default-width">800</property> <property name="default-width">800</property>
@ -153,7 +183,7 @@
<object class="GtkButton" id="button-clear"> <object class="GtkButton" id="button-clear">
<property name="hexpand">1</property> <property name="hexpand">1</property>
<property name="receives-default">1</property> <property name="receives-default">1</property>
<property name="label">clear</property> <property name="label" translatable="yes">_clear</property>
</object> </object>
</child> </child>
</object> </object>

View file

@ -412,6 +412,10 @@ impl GraphView {
let private = imp::GraphView::from_instance(self); let private = imp::GraphView::from_instance(self);
let mut nodes = private.nodes.borrow_mut(); let mut nodes = private.nodes.borrow_mut();
if let Some(node) = nodes.remove(&id) { if let Some(node) = nodes.remove(&id) {
if let Some(link_id) = self.node_is_linked(node.id()) {
let mut links = private.links.borrow_mut();
links.remove(&link_id);
}
node.unparent(); node.unparent();
} else { } else {
warn!("Tried to remove non-existant node (id={}) from graph", id); warn!("Tried to remove non-existant node (id={}) from graph", id);
@ -431,14 +435,15 @@ impl GraphView {
nodes_list nodes_list
} }
pub fn node(&self, id: &u32) -> Option<super::node::Node> {
let private = imp::GraphView::from_instance(self);
private.nodes.borrow().get(id).cloned()
}
pub fn remove_all_nodes(&self) { pub fn remove_all_nodes(&self) {
let private = imp::GraphView::from_instance(self); let private = imp::GraphView::from_instance(self);
let nodes_list = self.all_nodes(NodeType::All); let nodes_list = self.all_nodes(NodeType::All);
for node in nodes_list { for node in nodes_list {
if let Some(link_id) = self.node_is_linked(node.id()) {
let mut links = private.links.borrow_mut();
links.remove(&link_id);
}
self.remove_node(node.id()); self.remove_node(node.id());
} }
private.current_node_id.set(0); private.current_node_id.set(0);
@ -493,7 +498,6 @@ impl GraphView {
} }
// Link related methods // Link related methods
pub fn all_links(&self) -> Vec<NodeLink> { pub fn all_links(&self) -> Vec<NodeLink> {
let private = imp::GraphView::from_instance(self); let private = imp::GraphView::from_instance(self);
let links = private.links.borrow(); let links = private.links.borrow();
@ -668,6 +672,10 @@ impl GraphView {
description.push_str(" ! "); description.push_str(" ! ");
} }
for (name, value) in node.properties().iter() {
description.push_str(&format!(" {}={}", name, value));
}
println!("{}", description); println!("{}", description);
for port in ports { for port in ports {
if let Some((_port_to, node_to)) = self.port_connected_to(port.id()) { if let Some((_port_to, node_to)) = self.port_connected_to(port.id()) {
@ -714,6 +722,15 @@ impl GraphView {
)?; )?;
writer.write(XMLWEvent::end_element())?; writer.write(XMLWEvent::end_element())?;
} }
for (name, value) in node.properties().iter() {
writer.write(
XMLWEvent::start_element("Property")
.attr("name", name)
.attr("value", value),
)?;
writer.write(XMLWEvent::end_element())?;
}
writer.write(XMLWEvent::end_element())?; writer.write(XMLWEvent::end_element())?;
} }
//Get the link and write it. //Get the link and write it.
@ -775,6 +792,17 @@ impl GraphView {
NodeType::from_str(node_type.as_str()), NodeType::from_str(node_type.as_str()),
)); ));
} }
"Property" => {
let name = attrs
.get::<String>(&String::from("name"))
.expect("Unable to find property name");
let value: &String = attrs
.get::<String>(&String::from("value"))
.expect("Unable to find property value");
let node = current_node.clone();
node.expect("current node does not exist")
.add_property(name.clone(), value.clone());
}
"Port" => { "Port" => {
let id = attrs let id = attrs
.get::<String>(&String::from("id")) .get::<String>(&String::from("id"))
@ -835,6 +863,7 @@ impl GraphView {
} }
current_node = None; current_node = None;
} }
"Property" => {}
"Port" => { "Port" => {
if let Some(port) = current_port { if let Some(port) = current_port {
let node = current_node.clone(); let node = current_node.clone();

View file

@ -67,6 +67,7 @@ mod imp {
pub(super) ports: RefCell<HashMap<u32, Port>>, pub(super) ports: RefCell<HashMap<u32, Port>>,
pub(super) num_ports_in: Cell<i32>, pub(super) num_ports_in: Cell<i32>,
pub(super) num_ports_out: Cell<i32>, pub(super) num_ports_out: Cell<i32>,
pub(super) properties: RefCell<HashMap<String, String>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -97,6 +98,7 @@ mod imp {
ports: RefCell::new(HashMap::new()), ports: RefCell::new(HashMap::new()),
num_ports_in: Cell::new(0), num_ports_in: Cell::new(0),
num_ports_out: Cell::new(0), num_ports_out: Cell::new(0),
properties: RefCell::new(HashMap::new()),
} }
} }
} }
@ -214,4 +216,21 @@ impl Node {
let private = imp::Node::from_instance(self); let private = imp::Node::from_instance(self);
private.node_type.get() private.node_type.get()
} }
pub fn add_property(&self, name: String, value: String) {
let private = imp::Node::from_instance(self);
println!("{} {} updated", name, value);
private.properties.borrow_mut().insert(name, value);
}
pub fn update_node_properties(&self, new_properties: &HashMap<String, String>) {
for (key, value) in new_properties {
self.add_property(key.clone(), value.clone());
}
}
pub fn properties(&self) -> Ref<HashMap<String, String>> {
let private = imp::Node::from_instance(self);
private.properties.borrow()
}
} }

View file

@ -12,3 +12,22 @@ macro_rules! upgrade_weak {
upgrade_weak!($x, ()) upgrade_weak!($x, ())
}; };
} }
macro_rules! str_some_value (
($value:expr, $t:ty) => (
{
let value = $value.get::<$t>().expect("ser_some_value macro");
value
}
);
);
macro_rules! str_opt_value (
($value:expr, $t:ty) => (
{
match $value.get::<$t>() {
Ok(v) => Some(v),
Err(_e) => None
}
}
);
);

View file

@ -23,7 +23,7 @@ mod app;
mod common; mod common;
mod graphmanager; mod graphmanager;
mod pipeline; mod pipeline;
mod pluginlist; mod plugindialogs;
use gtk::prelude::*; use gtk::prelude::*;
use crate::app::GPSApp; use crate::app::GPSApp;

View file

@ -21,6 +21,7 @@ use crate::graphmanager::NodeType;
use gst::prelude::*; use gst::prelude::*;
use gstreamer as gst; use gstreamer as gst;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::ops; use std::ops;
@ -304,6 +305,64 @@ impl Pipeline {
element_type element_type
} }
fn value_as_str(v: &glib::Value) -> Option<String> {
match v.type_() {
glib::Type::I8 => Some(str_some_value!(v, i8).to_string()),
glib::Type::U8 => Some(str_some_value!(v, u8).to_string()),
glib::Type::BOOL => Some(str_some_value!(v, bool).to_string()),
glib::Type::I32 => Some(str_some_value!(v, i32).to_string()),
glib::Type::U32 => Some(str_some_value!(v, u32).to_string()),
glib::Type::I64 => Some(str_some_value!(v, i64).to_string()),
glib::Type::U64 => Some(str_some_value!(v, u64).to_string()),
glib::Type::F32 => Some(str_some_value!(v, f32).to_string()),
glib::Type::F64 => Some(str_some_value!(v, f64).to_string()),
glib::Type::STRING => str_opt_value!(v, String),
_ => None,
}
}
pub fn element_properties(
element_name: &str,
) -> anyhow::Result<HashMap<String, String>, Box<dyn error::Error>> {
let mut properties_list = HashMap::new();
let feature = Pipeline::element_feature(element_name).expect("Unable to get feature");
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Factory not found");
let element = factory.create(None)?;
let params = element.class().list_properties();
for param in params {
println!("Property_name {}", param.name());
if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE
{
let value = Pipeline::value_as_str(&element.property(param.name()).unwrap())
.unwrap_or_else(|| String::from(""));
properties_list.insert(String::from(param.name()), value);
} else if let Some(value) = Pipeline::value_as_str(param.default_value()) {
properties_list.insert(String::from(param.name()), value);
} else {
println!("Unable to add property_name {}", param.name());
}
}
Ok(properties_list)
}
pub fn element_is_uri_src_handler(element_name: &str) -> bool {
let feature = Pipeline::element_feature(element_name).expect("Unable to get feature");
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Factory not found");
let element = factory.create(None).expect("Unable to get factory");
match element.dynamic_cast::<gst::URIHandler>() {
Ok(uri_handler) => uri_handler.uri_type() == gst::URIType::Src,
Err(_e) => false,
}
}
} }
impl Drop for PipelineInner { impl Drop for PipelineInner {

View file

@ -1,4 +1,4 @@
// pluginlist.rs // plugindialogs.rs
// //
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com> // Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
// //
@ -22,8 +22,14 @@ use crate::pipeline::Pipeline;
use gtk::glib; use gtk::glib;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::TextBuffer; use gtk::TextBuffer;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use gtk::{CellRendererText, Dialog, ListStore, TextView, TreeView, TreeViewColumn}; use gtk::{
Box, Button, CellRendererText, Dialog, Entry, Label, ListStore, TextView, TreeView,
TreeViewColumn,
};
fn create_and_fill_model(elements: &[ElementInfo]) -> ListStore { fn create_and_fill_model(elements: &[ElementInfo]) -> ListStore {
// Creation of a model with two rows. // Creation of a model with two rows.
@ -115,3 +121,61 @@ pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
dialog.show(); dialog.show();
} }
pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32) {
let dialog: Dialog = app
.builder
.object("dialog-plugin-properties")
.expect("Couldn't get window");
dialog.set_title(Some(&format!("{} properties", element_name)));
dialog.set_default_size(640, 480);
dialog.set_modal(true);
let properties_box: Box = app
.builder
.object("box-plugin-properties")
.expect("Couldn't get window");
let update_properties: Rc<RefCell<HashMap<String, String>>> =
Rc::new(RefCell::new(HashMap::new()));
let properties = Pipeline::element_properties(element_name).unwrap();
for (name, value) in properties {
let entry_box = Box::new(gtk::Orientation::Horizontal, 6);
let label = Label::new(Some(&name));
label.set_hexpand(true);
label.set_halign(gtk::Align::Start);
label.set_margin_start(4);
entry_box.append(&label);
let entry: Entry = Entry::new();
entry.set_text(&value);
entry.set_hexpand(true);
entry.set_halign(gtk::Align::Start);
entry.set_widget_name(&name);
entry.connect_changed(
glib::clone!(@weak entry, @strong update_properties => move |_| {
println!("{}:{}", entry.widget_name(), entry.text());
update_properties.borrow_mut().insert(entry.widget_name().to_string(), entry.text().to_string());
}),
);
entry_box.append(&entry);
properties_box.append(&entry_box);
}
let properties_apply_btn: Button = app
.builder
.object("apply-plugin-properties")
.expect("Couldn't get window");
let app_weak = app.downgrade();
properties_apply_btn.connect_clicked(
glib::clone!(@strong update_properties, @weak dialog => move |_| {
let app = upgrade_weak!(app_weak);
app.update_element_properties(node_id, &update_properties.borrow());
dialog.close();
}),
);
dialog.show();
for p in update_properties.borrow().values() {
println!("updated properties {}", p);
}
}