graphbook: introduce element_factory_exists

This method helps to tell if a factory exists
when loading a graph.
If the factory does not exists, use light mode
to display it and prevent some menu item
and change its description.
This commit is contained in:
Stéphane Cerveau 2023-09-18 12:30:39 +02:00
parent 111750a33b
commit f4be2299b9
5 changed files with 155 additions and 117 deletions

View file

@ -100,3 +100,7 @@
### app ### app
- [x] Add multiple graphviews with tabs. - [x] Add multiple graphviews with tabs.
- [x] handle the caps setter element - [x] handle the caps setter element
## 0.3.2
### app
- [x] check that element exists before creating it on file load.

View file

@ -15,7 +15,6 @@
- [ ] unable to connect element with incompatible caps. - [ ] unable to connect element with incompatible caps.
- [ ] Implement graph dot render/load - [ ] Implement graph dot render/load
- [ ] Add probes on each pad to monitor the pipeline - [ ] Add probes on each pad to monitor the pipeline
- [ ] Render a media file - [ ] Render a media file
- [ ] Offer compatible element to a pad (autorender) - [ ] Offer compatible element to a pad (autorender)
@ -32,7 +31,6 @@
## bugs ## bugs
- [ ] check that element exists before creating it on file load.
- [ ] Combo box is not well selected if the value is not linear such as flags. See flags in playbin - [ ] Combo box is not well selected if the value is not linear such as flags. See flags in playbin
- [ ] opening a graph file can lead a different behavior in the pipeline. See videomixer graph where the zorder - [ ] opening a graph file can lead a different behavior in the pipeline. See videomixer graph where the zorder
on pads is not correctly set to right one. on pads is not correctly set to right one.

View file

@ -46,6 +46,19 @@ impl ElementInfo {
Ok(elements) Ok(elements)
} }
pub fn element_factory_exists(element_name: &str) -> bool {
match ElementInfo::element_feature(element_name) {
Some(_feature) => {
GPS_DEBUG!("Found element factory name {}", element_name);
true
}
None => {
GPS_ERROR!("Unable to find element factory name {}", element_name);
false
}
}
}
pub fn element_feature(element_name: &str) -> Option<gst::PluginFeature> { pub fn element_feature(element_name: &str) -> Option<gst::PluginFeature> {
let registry = gst::Registry::get(); let registry = gst::Registry::get();
gst::Registry::find_feature(&registry, element_name, gst::ElementFactory::static_type()) gst::Registry::find_feature(&registry, element_name, gst::ElementFactory::static_type())
@ -60,59 +73,68 @@ impl ElementInfo {
pub fn element_description(element_name: &str) -> anyhow::Result<String> { pub fn element_description(element_name: &str) -> anyhow::Result<String> {
let mut desc = String::from(""); let mut desc = String::from("");
let feature = ElementInfo::element_feature(element_name) if !ElementInfo::element_factory_exists(element_name) {
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
let rank = feature.rank();
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
desc.push_str("<b>Factory details:</b>\n"); desc.push_str("<b>Factory details:</b>\n");
desc.push_str("<b>Rank:</b>");
let _ = write!(desc, "{rank:?}",);
desc.push('\n');
desc.push_str("<b>Name:</b>"); desc.push_str("<b>Name:</b>");
desc.push_str(&factory.name()); desc.push_str(element_name);
desc.push('\n'); desc.push('\n');
desc.push('\n');
desc.push_str("Factory unavailable.");
} else {
let feature = ElementInfo::element_feature(element_name)
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
let rank = feature.rank();
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
desc.push_str("<b>Factory details:</b>\n");
desc.push_str("<b>Rank:</b>");
let _ = write!(desc, "{rank:?}",);
desc.push('\n');
desc.push_str("<b>Name:</b>");
desc.push_str(&factory.name());
desc.push('\n');
let element_keys = factory.metadata_keys(); let element_keys = factory.metadata_keys();
for key in element_keys { for key in element_keys {
let val = factory.metadata(&key); let val = factory.metadata(&key);
if let Some(val) = val { if let Some(val) = val {
desc.push_str("<b>"); desc.push_str("<b>");
desc.push_str(&key); desc.push_str(&key);
desc.push_str("</b>:"); desc.push_str("</b>:");
desc.push_str(&gtk::glib::markup_escape_text(val)); desc.push_str(&gtk::glib::markup_escape_text(val));
desc.push('\n');
}
}
let feature = factory.upcast::<gst::PluginFeature>();
let plugin = gst::PluginFeature::plugin(&feature);
if let Some(plugin) = plugin {
desc.push('\n');
desc.push_str("<b>Plugin details:</b>");
desc.push('\n');
desc.push_str("<b>Name:");
desc.push_str("</b>");
desc.push_str(gst::Plugin::plugin_name(&plugin).as_str());
desc.push('\n');
desc.push_str("<b>Description:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.description()));
desc.push('\n');
desc.push_str("<b>Filename:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(
&plugin
.filename()
.unwrap_or_default()
.as_path()
.display()
.to_string(),
));
desc.push('\n');
desc.push_str("<b>Version:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.version()));
desc.push('\n'); desc.push('\n');
} }
} }
let feature = factory.upcast::<gst::PluginFeature>();
let plugin = gst::PluginFeature::plugin(&feature);
if let Some(plugin) = plugin {
desc.push('\n');
desc.push_str("<b>Plugin details:</b>");
desc.push('\n');
desc.push_str("<b>Name:");
desc.push_str("</b>");
desc.push_str(gst::Plugin::plugin_name(&plugin).as_str());
desc.push('\n');
desc.push_str("<b>Description:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.description()));
desc.push('\n');
desc.push_str("<b>Filename:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(
&plugin
.filename()
.unwrap_or_default()
.as_path()
.display()
.to_string(),
));
desc.push('\n');
desc.push_str("<b>Version:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.version()));
desc.push('\n');
}
} }
Ok(desc) Ok(desc)
} }

View file

@ -51,41 +51,43 @@ impl PadInfo {
} }
pub fn pads(element_name: &str, include_on_request: bool) -> (Vec<PadInfo>, Vec<PadInfo>) { pub fn pads(element_name: &str, include_on_request: bool) -> (Vec<PadInfo>, Vec<PadInfo>) {
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
let mut input = vec![]; let mut input = vec![];
let mut output = vec![]; let mut output = vec![];
if let Some(feature) = ElementInfo::element_feature(element_name) {
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() { if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
if factory.num_pad_templates() > 0 { if factory.num_pad_templates() > 0 {
let pads = factory.static_pad_templates(); let pads = factory.static_pad_templates();
for pad in pads { for pad in pads {
GPS_TRACE!("Found a pad name {}", pad.name_template()); GPS_TRACE!("Found a pad name {}", pad.name_template());
if pad.presence() == gst::PadPresence::Always if pad.presence() == gst::PadPresence::Always
|| (include_on_request || (include_on_request
&& (pad.presence() == gst::PadPresence::Request && (pad.presence() == gst::PadPresence::Request
|| pad.presence() == gst::PadPresence::Sometimes)) || pad.presence() == gst::PadPresence::Sometimes))
{ {
if pad.direction() == gst::PadDirection::Src { if pad.direction() == gst::PadDirection::Src {
output.push(PadInfo { output.push(PadInfo {
name: Some(pad.name_template().to_string()), name: Some(pad.name_template().to_string()),
element_name: Some(element_name.to_string()), element_name: Some(element_name.to_string()),
direction: PortDirection::Output, direction: PortDirection::Output,
presence: PadInfo::pad_to_port_presence(pad.presence()), presence: PadInfo::pad_to_port_presence(pad.presence()),
caps: Some(pad.caps().to_string()), caps: Some(pad.caps().to_string()),
}); });
} else if pad.direction() == gst::PadDirection::Sink { } else if pad.direction() == gst::PadDirection::Sink {
input.push(PadInfo { input.push(PadInfo {
name: Some(pad.name_template().to_string()), name: Some(pad.name_template().to_string()),
element_name: Some(element_name.to_string()), element_name: Some(element_name.to_string()),
direction: PortDirection::Input, direction: PortDirection::Input,
presence: PadInfo::pad_to_port_presence(pad.presence()), presence: PadInfo::pad_to_port_presence(pad.presence()),
caps: Some(pad.caps().to_string()), caps: Some(pad.caps().to_string()),
}); });
}
} }
} }
} }
} }
(input, output)
} else {
(input, output)
} }
(input, output)
} }
} }

View file

@ -242,6 +242,10 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
if let Some(node) = current_graphtab(&app).graphview().node(node_id) { if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
let description = GPS::ElementInfo::element_description(&node.name()).ok(); let description = GPS::ElementInfo::element_description(&node.name()).ok();
node.set_tooltip_markup(description.as_deref()); node.set_tooltip_markup(description.as_deref());
if !GPS::ElementInfo::element_factory_exists(&node.name()) {
node.set_light(true);
node.set_tooltip_markup(description.as_deref());
}
for port in node.all_ports(GM::PortDirection::All) { for port in node.all_ports(GM::PortDirection::All) {
let caps = PropertyExt::property(&port,"_caps"); let caps = PropertyExt::property(&port,"_caps");
GPS_DEBUG!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id()); GPS_DEBUG!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id());
@ -382,6 +386,8 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| { glib::clone!(@weak graphbook => @default-return None, move |values: &[Value]| {
let app = upgrade_weak!(app_weak, None); let app = upgrade_weak!(app_weak, None);
let node_id = values[1].get::<u32>().expect("node id args[1]"); let node_id = values[1].get::<u32>().expect("node id args[1]");
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
let element_exists = GPS::ElementInfo::element_factory_exists(&node.name());
let point = values[2].get::<graphene::Point>().expect("point in args[2]"); let point = values[2].get::<graphene::Point>().expect("point in args[2]");
let pop_menu = app.app_pop_menu_at_position(&*current_graphtab(&app).graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64); let pop_menu = app.app_pop_menu_at_position(&*current_graphtab(&app).graphview(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
let menu: gio::MenuModel = app let menu: gio::MenuModel = app
@ -390,16 +396,6 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
.expect("Couldn't get menu model for node"); .expect("Couldn't get menu model for node");
pop_menu.set_menu_model(Some(&menu)); pop_menu.set_menu_model(Some(&menu));
let app_weak = app.downgrade();
app.connect_app_menu_action("node.add-to-favorite",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.add-to-favorite id: {}", node_id);
if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
GPSUI::elements::add_to_favorite_list(&app, node.name());
};
}
);
let app_weak = app.downgrade(); let app_weak = app.downgrade();
app.connect_app_menu_action("node.delete", app.connect_app_menu_action("node.delete",
@ -409,42 +405,56 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
current_graphtab(&app).graphview().remove_node(node_id); current_graphtab(&app).graphview().remove_node(node_id);
} }
); );
let node = app.node(node_id); if element_exists {
if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) {
let app_weak = app.downgrade(); let app_weak = app.downgrade();
app.connect_app_menu_action("node.request-pad-input", app.connect_app_menu_action("node.add-to-favorite",
move |_,_| { move |_,_| {
let app = upgrade_weak!(app_weak); let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-input id: {}", node_id); GPS_DEBUG!("node.add-to-favorite id: {}", node_id);
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string()); if let Some(node) = current_graphtab(&app).graphview().node(node_id) {
GPSUI::elements::add_to_favorite_list(&app, node.name());
};
} }
); );
} else {
app.disconnect_app_menu_action("node.request-pad-input");
}
let node = app.node(node_id);
if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) {
let app_weak = app.downgrade();
app.connect_app_menu_action("node.request-pad-output",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-output id: {}", node_id);
app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string());
}
);
} else {
app.disconnect_app_menu_action("node.request-pad-output");
}
let app_weak = app.downgrade();
app.connect_app_menu_action("node.properties", let node = app.node(node_id);
move |_,_| { if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) {
let app = upgrade_weak!(app_weak); let app_weak = app.downgrade();
GPS_DEBUG!("node.properties id {}", node_id); app.connect_app_menu_action("node.request-pad-input",
let node = current_graphtab(&app).graphview().node(node_id).unwrap(); move |_,_| {
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id); let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-input id: {}", node_id);
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string());
}
);
} else {
app.disconnect_app_menu_action("node.request-pad-input");
} }
); let node = app.node(node_id);
if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) {
let app_weak = app.downgrade();
app.connect_app_menu_action("node.request-pad-output",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-output id: {}", node_id);
app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string());
}
);
} else {
app.disconnect_app_menu_action("node.request-pad-output");
}
let app_weak = app.downgrade();
app.connect_app_menu_action("node.properties",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.properties id {}", node_id);
let node = current_graphtab(&app).graphview().node(node_id).unwrap();
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
}
);
}
pop_menu.show(); pop_menu.show();
None None
}), }),
@ -459,7 +469,9 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
let node_id = values[1].get::<u32>().expect("node id args[1]"); let node_id = values[1].get::<u32>().expect("node id args[1]");
GPS_TRACE!("Node double clicked id={}", node_id); GPS_TRACE!("Node double clicked id={}", node_id);
let node = current_graphtab(&app).graphview().node(node_id).unwrap(); let node = current_graphtab(&app).graphview().node(node_id).unwrap();
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id); if GPS::ElementInfo::element_factory_exists(&node.name()) {
GPSUI::properties::display_plugin_properties(&app, &node.name(), node_id);
}
None None
}), }),
); );