// properties.rs // // Copyright 2022 Stéphane Cerveau // // This file is part of GstPipelineStudio // // SPDX-License-Identifier: GPL-3.0-only use crate::app::GPSApp; use crate::common; use crate::gps as GPS; use crate::graphbook; use crate::logger; use crate::ui as GPSUI; use crate::{GPS_INFO, GPS_TRACE}; use gtk::glib; use gtk::prelude::*; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; pub fn property_to_widget( app: &GPSApp, node_id: u32, element_name: &str, property_name: &str, param: &glib::ParamSpec, f: F, ) -> Option { match param.type_() { _t if param.type_() == glib::ParamSpecBoolean::static_type() => { let check_button = gtk::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::().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_by_feature_name(element_name, param.name()) { check_button.set_active(value.parse::().unwrap_or(false)); } } else if let Some(value) = common::value_as_str(param.default_value()) { check_button.set_active(value.parse::().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::()) } t if [ glib::ParamSpecInt::static_type(), glib::ParamSpecUInt::static_type(), glib::ParamSpecInt64::static_type(), glib::ParamSpecUInt64::static_type(), glib::ParamSpecString::static_type(), glib::ParamSpecFloat::static_type(), ] .contains(&t) => { let entry = gtk::Entry::new(); entry.set_width_request(350); 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_by_feature_name(element_name, param.name()) { entry.set_text(&value); } } else if let Some(value) = common::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::()) } t if [ glib::ParamSpecEnum::static_type(), glib::ParamSpecFlags::static_type(), ] .contains(&t) => { let combo = gtk::ComboBoxText::new(); combo.set_widget_name(property_name); GPS_TRACE!("add ComboBox property : {}", combo.widget_name()); // Add an empty entry to be able to reset the value combo.append_text(""); if t.is_a(glib::ParamSpecEnum::static_type()) { let param = param .clone() .downcast::() .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::() .expect("Should be a ParamSpecFlags"); 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) { //Retrieve the first value (index) from the property combo.set_active(Some(value.parse::().unwrap_or(0) + 1)); } 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_by_feature_name(element_name, param.name()) { combo.set_active(Some(value.parse::().unwrap_or(0) + 1)); } } combo.connect_changed(move |c| { if let Some(text) = c.active_text() { let value = text.to_string(); let value = value.split_once(':'); f( c.widget_name().to_string(), value.unwrap_or_default().0.to_string(), ); } }); Some(combo.upcast::()) } _ => { 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) { let update_properties: Rc>> = Rc::new(RefCell::new(HashMap::new())); let properties = GPS::ElementInfo::element_properties_by_feature_name(element_name).unwrap(); let grid = gtk::Grid::new(); grid.set_column_spacing(4); grid.set_row_spacing(4); grid.set_margin_bottom(12); 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 let widget = property_to_widget( app, node_id, element_name, name, param, glib::clone!(@strong update_properties => move |name, value| { GPS_INFO!("property changed: {}:{}", name, value); update_properties.borrow_mut().insert(name, value); }), ); if let Some(widget) = widget { let label = gtk::Label::builder() .label(name) .hexpand(true) .halign(gtk::Align::Start) .margin_start(4) .build(); grid.attach(&label, 0, i, 1, 1); grid.attach(&widget, 1, i, 1, 1); i += 1; } } let dialog = GPSUI::dialog::create_dialog( &format!("{element_name} properties"), app, &grid, glib::clone!(@strong update_properties => move |app, dialog| { app.update_element_properties(node_id, &update_properties.borrow()); dialog.close(); }), ); dialog.show(); } pub fn display_pad_properties( app: &GPSApp, element_name: &str, port_name: &str, node_id: u32, port_id: u32, ) { let update_properties: Rc>> = Rc::new(RefCell::new(HashMap::new())); let grid = gtk::Grid::new(); grid.set_column_spacing(4); grid.set_row_spacing(4); grid.set_margin_bottom(12); let mut i = 0; let properties = app.pad_properties(node_id, port_id); for (name, value) in properties { let property_name = gtk::Label::builder() .label(&name) .hexpand(true) .halign(gtk::Align::Start) .margin_start(4) .build(); let property_value = gtk::Entry::new(); property_value.set_width_request(150); property_value.set_text(&value); property_value.connect_changed( glib::clone!(@weak property_name, @weak property_value, @strong update_properties=> move |_| { update_properties.borrow_mut().insert(property_name.text().to_string(), property_value.text().to_string()); }), ); grid.attach(&property_name, 0, i, 1, 1); grid.attach(&property_value, 1, i, 1, 1); i += 1; } // Add a new property allowing to set pads property. let label = gtk::Label::builder() .label("Add a new Property") .hexpand(true) .halign(gtk::Align::Start) .margin_start(4) .build(); let property_name = gtk::Entry::new(); property_name.set_width_request(150); let property_value = gtk::Entry::new(); property_value.set_width_request(150); property_name.connect_changed( glib::clone!(@weak property_name, @weak property_value, @strong update_properties=> move |_| { update_properties.borrow_mut().insert(property_name.text().to_string(), property_value.text().to_string()); }), ); property_value.connect_changed( glib::clone!(@weak property_name, @weak property_value, @strong update_properties=> move |_| { update_properties.borrow_mut().insert(property_name.text().to_string(), property_value.text().to_string()); }), ); grid.attach(&label, 0, i, 1, 1); grid.attach(&property_name, 1, i, 1, 1); grid.attach(&property_value, 2, i, 1, 1); // Add all specific properties from the given element let dialog = GPSUI::dialog::create_dialog( &format!("{port_name} properties from {element_name}"), app, &grid, glib::clone!(@strong update_properties => move |app, dialog| { for p in update_properties.borrow().values() { GPS_INFO!("updated properties {}", p); } app.update_pad_properties(node_id, port_id, &update_properties.borrow()); dialog.close(); }), ); dialog.show(); } pub fn display_pipeline_details(app: &GPSApp) { let grid = gtk::Grid::new(); grid.set_column_spacing(4); grid.set_row_spacing(4); grid.set_margin_bottom(12); if let Some(elements) = graphbook::current_graphtab(app) .player() .pipeline_elements() { let elements_list = elements.join(" "); let label = gtk::Label::builder() .label(format!("{} elements:", elements.len())) .hexpand(true) .halign(gtk::Align::Start) .valign(gtk::Align::Start) .margin_start(4) .build(); let value = gtk::Label::builder() .label(elements_list) .hexpand(true) .halign(gtk::Align::Start) .margin_start(4) .wrap(true) .build(); grid.attach(&label, 0, 0_i32, 1, 1); grid.attach(&value, 1, 0_i32, 1, 1); let dialog = GPSUI::dialog::create_dialog("Pipeline properties", app, &grid, move |_app, dialog| { dialog.close(); }); dialog.show(); } }