properties: support more properties

Support enum and flags properties in
addition to numerous and string ones.
This commit is contained in:
Stéphane Cerveau 2022-01-20 18:18:52 +01:00
parent 24e5e947d5
commit 39815eb8d9
5 changed files with 218 additions and 56 deletions

View file

@ -30,6 +30,7 @@
- [x] Remove a port from a node if possible - [x] Remove a port from a node if possible
- [ ] Implement graphview unit test - [ ] Implement graphview unit test
- [x] add a css class for pad (presence always or sometimes) - [x] add a css class for pad (presence always or sometimes)
- [ ] Add property to port to store some specific value(Caps)
### app ### app

View file

@ -694,6 +694,11 @@ impl GPSApp {
node.update_properties(properties); node.update_properties(properties);
} }
pub fn element_property(&self, node_id: u32, property_name: &str) -> Option<String> {
let node = self.graphview.borrow().node(node_id).unwrap();
node.property(property_name)
}
fn clear_graph(&self) { fn clear_graph(&self) {
let graph_view = self.graphview.borrow_mut(); let graph_view = self.graphview.borrow_mut();
graph_view.remove_all_nodes(); graph_view.remove_all_nodes();

View file

@ -131,24 +131,22 @@ impl ElementInfo {
element_type element_type
} }
pub fn element_property(element_name: &str, property_name: &str) -> anyhow::Result<String> {
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
fn value_as_str(v: &glib::Value) -> Option<String> { let factory = feature
match v.type_() { .downcast::<gst::ElementFactory>()
glib::Type::I8 => Some(str_some_value!(v, i8).to_string()), .expect("Unable to get the factory from the feature");
glib::Type::U8 => Some(str_some_value!(v, u8).to_string()), let element = factory.create(None)?;
glib::Type::BOOL => Some(str_some_value!(v, bool).to_string()), let value = element
glib::Type::I32 => Some(str_some_value!(v, i32).to_string()), .try_property::<String>(property_name)
glib::Type::U32 => Some(str_some_value!(v, u32).to_string()), .unwrap_or_default();
glib::Type::I64 => Some(str_some_value!(v, i64).to_string()), Ok(value)
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>> { pub fn element_properties(
element_name: &str,
) -> anyhow::Result<HashMap<String, glib::ParamSpec>> {
let mut properties_list = HashMap::new(); let mut properties_list = HashMap::new();
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature"); let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
@ -159,19 +157,16 @@ impl ElementInfo {
let params = element.class().list_properties(); let params = element.class().list_properties();
for param in params.iter() { for param in params.iter() {
if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE let value = element
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE .try_property::<String>(param.name())
{ .unwrap_or_default();
let value = element GPS_INFO!(
.try_property::<String>(param.name()) "Property_name {}={} type={:?}",
.unwrap_or_else(|_| String::from("")); param.name(),
GPS_INFO!("Property_name {}={}", param.name(), value); value,
properties_list.insert(String::from(param.name()), value); param.type_()
} else if let Some(value) = ElementInfo::value_as_str(param.default_value()) { );
properties_list.insert(String::from(param.name()), value); properties_list.insert(String::from(param.name()), param.clone());
} else {
GPS_INFO!("Unable to add property_name {}", param.name());
}
} }
Ok(properties_list) Ok(properties_list)
} }

View file

@ -193,8 +193,10 @@ impl Pipeline {
description.push_str(&format!("{} name={} ", node.name(), unique_name)); description.push_str(&format!("{} name={} ", node.name(), unique_name));
elements.insert(unique_name.clone(), unique_name.clone()); elements.insert(unique_name.clone(), unique_name.clone());
for (name, value) in node.properties().iter() { for (name, value) in node.properties().iter() {
//This allow to have an index in front of a property such as an enum.
let value = value.split_once(':').unwrap_or((value, value));
if !node.hidden_property(name) { if !node.hidden_property(name) {
description.push_str(&format!("{}={} ", name, value)); description.push_str(&format!("{}={} ", name, value.1));
} }
} }

View file

@ -19,7 +19,7 @@
use crate::app::GPSApp; use crate::app::GPSApp;
use crate::gps as GPS; use crate::gps as GPS;
use crate::logger; use crate::logger;
use crate::GPS_TRACE; use crate::{GPS_INFO, GPS_TRACE};
use gtk::glib; use gtk::glib;
use gtk::prelude::*; use gtk::prelude::*;
@ -27,7 +27,154 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use gtk::{Box, Button, Dialog, Entry, Label}; use gtk::{Box, Button, CheckButton, ComboBoxText, Dialog, Entry, Label, Widget};
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 property_to_widget<F: Fn(String, String) + 'static>(
app: &GPSApp,
node_id: u32,
element_name: &str,
property_name: &str,
param: &glib::ParamSpec,
f: F,
) -> Option<Widget> {
match param.type_() {
_t if param.type_() == glib::ParamSpecBoolean::static_type() => {
let check_button = CheckButton::new();
check_button.set_widget_name(property_name);
GPS_TRACE!("add CheckBox property : {}", check_button.widget_name());
if let Some(value) = app.element_property(node_id, property_name) {
check_button.set_active(value.parse::<bool>().unwrap_or(false));
} else if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE
{
if let Ok(value) = GPS::ElementInfo::element_property(element_name, param.name()) {
check_button.set_active(value.parse::<bool>().unwrap_or(false));
}
} else if let Some(value) = value_as_str(param.default_value()) {
check_button.set_active(value.parse::<bool>().unwrap_or(false));
}
check_button.connect_toggled(glib::clone!(@weak check_button => move |c| {
f(c.widget_name().to_string(), c.is_active().to_string() );
}));
Some(check_button.upcast::<Widget>())
}
t if [
glib::ParamSpecInt::static_type(),
glib::ParamSpecUInt::static_type(),
glib::ParamSpecInt64::static_type(),
glib::ParamSpecUInt64::static_type(),
glib::ParamSpecString::static_type(),
]
.contains(&t) =>
{
let entry = Entry::new();
entry.set_widget_name(property_name);
GPS_TRACE!("Add Edit property : {}", entry.widget_name());
if let Some(value) = app.element_property(node_id, property_name) {
entry.set_text(&value);
} else if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE
{
if let Ok(value) = GPS::ElementInfo::element_property(element_name, param.name()) {
entry.set_text(&value);
}
} else if let Some(value) = value_as_str(param.default_value()) {
entry.set_text(&value);
}
entry.connect_changed(glib::clone!(@weak entry=> move |e| {
f(e.widget_name().to_string(), e.text().to_string())
}));
Some(entry.upcast::<Widget>())
}
t if [
glib::ParamSpecEnum::static_type(),
glib::ParamSpecFlags::static_type(),
]
.contains(&t) =>
{
let combo = ComboBoxText::new();
combo.set_widget_name(property_name);
GPS_TRACE!("add ComboBox property : {}", combo.widget_name());
if t.is_a(glib::ParamSpecEnum::static_type()) {
let param = param
.clone()
.downcast::<glib::ParamSpecEnum>()
.expect("Should be a ParamSpecEnum");
let enums = param.enum_class();
for value in enums.values() {
combo.append_text(&format!(
"{}:{}:{}",
value.value(),
value.nick(),
value.name()
));
}
} else if t.is_a(glib::ParamSpecFlags::static_type()) {
let param = param
.clone()
.downcast::<glib::ParamSpecFlags>()
.expect("Should be a ParamSpecEnum");
let flags = param.flags_class();
for value in flags.values() {
combo.append_text(&format!(
"{}:{}: {}",
value.value(),
value.nick(),
value.name()
));
}
}
if let Some(value) = app.element_property(node_id, property_name) {
let value = value.split_once(':').unwrap_or(("0", ""));
//Retrieve the first value (index) from the property
combo.set_active(Some(value.0.parse::<u32>().unwrap_or(0)));
} else if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE
{
if let Ok(value) = GPS::ElementInfo::element_property(element_name, param.name()) {
combo.set_active(Some(value.parse::<u32>().unwrap_or(0)));
}
}
combo.connect_changed(move |c| {
if let Some(text) = c.active_text() {
let text = text.to_string();
let fields: Vec<&str> = text.split(':').collect();
f(
c.widget_name().to_string(),
format!("{}:{}", fields[0], fields[1]),
)
}
});
Some(combo.upcast::<Widget>())
}
_ => {
GPS_INFO!(
"Property not supported : name={} type={}",
property_name,
param.type_()
);
None
}
}
}
pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32) { pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32) {
let dialog: Dialog = app let dialog: Dialog = app
@ -50,34 +197,46 @@ pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32)
properties_box.remove(&child); properties_box.remove(&child);
} }
for (name, value) in properties { let grid = gtk::Grid::builder()
let entry_box = Box::new(gtk::Orientation::Horizontal, 1); .margin_start(6)
let name_box = Box::new(gtk::Orientation::Vertical, 6); .margin_end(6)
let value_box = Box::new(gtk::Orientation::Vertical, 6); .margin_top(6)
//Label .margin_bottom(6)
let label = Label::new(Some(&name)); .halign(gtk::Align::Start)
label.set_hexpand(true); .valign(gtk::Align::Center)
label.set_halign(gtk::Align::Start); .row_spacing(6)
label.set_margin_start(4); .column_spacing(100)
name_box.append(&label); .width_request(100)
.build();
let mut properties: Vec<(&String, &glib::ParamSpec)> = properties.iter().collect();
properties.sort_by(|a, b| a.0.cmp(b.0));
let mut i = 0;
for (name, param) in properties {
//Entry //Entry
let entry: Entry = Entry::new(); let widget = property_to_widget(
entry.set_text(&value); app,
entry.set_hexpand(true); node_id,
entry.set_halign(gtk::Align::End); element_name,
entry.set_widget_name(&name); name,
entry.connect_changed( param,
glib::clone!(@weak entry, @strong update_properties => move |_| { glib::clone!(@strong update_properties => move |name, value| {
GPS_TRACE!("property changed: {}:{}", entry.widget_name(), entry.text()); GPS_TRACE!("property changed: {}:{}", name, value);
update_properties.borrow_mut().insert(entry.widget_name().to_string(), entry.text().to_string()); update_properties.borrow_mut().insert(name, value);
}), }),
); );
value_box.append(&entry); if let Some(widget) = widget {
let label = Label::new(Some(name));
entry_box.append(&name_box); label.set_hexpand(true);
entry_box.append(&value_box); label.set_halign(gtk::Align::Start);
properties_box.append(&entry_box); label.set_margin_start(4);
grid.attach(&label, 0, i, 1, 1);
grid.attach(&widget, 1, i, 1, 1);
i += 1;
}
} }
properties_box.append(&grid);
let properties_apply_btn: Button = app let properties_apply_btn: Button = app
.builder .builder
.object("button-apply-plugin-properties") .object("button-apply-plugin-properties")
@ -94,6 +253,6 @@ pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32)
dialog.show(); dialog.show();
for p in update_properties.borrow().values() { for p in update_properties.borrow().values() {
GPS_TRACE!("updated properties {}", p); GPS_INFO!("updated properties {}", p);
} }
} }