app: code cleanup

Use simplified anyhow result
Rewrite the expect/error messages.
Refactor the start pipeline API
Rename the treeview ids
This commit is contained in:
Stéphane Cerveau 2022-01-11 14:38:18 +01:00
parent 08b8bc6138
commit 695bd142d3
5 changed files with 132 additions and 135 deletions

11
TODO.md
View file

@ -25,6 +25,15 @@ TODO:
- [x] Connect the logs to the window
- [] Create a window for the video output
- [] Add multiple graphviews with tabs.
- [] Property window in the main window
- [] Connect the GPS status to GST status
- [] Implement graph dot render/load
- [] Implement a command line parser to graph
- [] Unable to create a pad in an element without the template
- [] Remove a pad from the graph
- [] Implement graphview unit test
- [] Implement pipeline unit test
- [] Save node position in XML
## bugs
@ -35,5 +44,5 @@ TODO:
## Code cleanup
[] remove useless code from graphview
[] Move render to a specific module
[X] Move render to a specific module
[x] Move GST render to a specific module

View file

@ -16,6 +16,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use glib::SignalHandlerId;
use glib::Value;
use gtk::gdk::Rectangle;
@ -29,8 +30,8 @@ use gtk::{gio, gio::SimpleAction, glib, graphene};
use once_cell::unsync::OnceCell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops;
use std::rc::{Rc, Weak};
use std::{error, ops};
use crate::about;
use crate::logger;
@ -78,26 +79,29 @@ impl GPSAppWeak {
}
impl GPSApp {
fn new(application: &gtk::Application) -> anyhow::Result<GPSApp, Box<dyn error::Error>> {
fn new(application: &gtk::Application) -> anyhow::Result<GPSApp> {
let glade_src = include_str!("gps.ui");
let builder = Builder::from_string(glade_src);
let window: ApplicationWindow = builder.object("mainwindow").expect("Couldn't get window");
let window: ApplicationWindow = builder
.object("mainwindow")
.expect("Couldn't get the main window");
window.set_application(Some(application));
window.set_title(Some("GstPipelineStudio"));
let settings = Settings::load_settings();
window.set_size_request(settings.app_width, settings.app_height);
let paned: Paned = builder
.object("graph_logs-paned")
.expect("Couldn't get window");
.expect("Couldn't get graph_logs-paned");
paned.set_position(settings.app_graph_logs_paned_pos);
let paned: Paned = builder
.object("graph_favorites-paned")
.expect("Couldn't get window");
.expect("Couldn't get graph_favorites-paned");
paned.set_position(settings.app_graph_favorites_paned_pos);
if settings.app_maximized {
window.maximize();
}
let pipeline = Pipeline::new().expect("Unable to initialize the pipeline");
let pipeline = Pipeline::new().expect("Unable to initialize GStreamer subsystem");
let app = GPSApp(Rc::new(GPSAppInner {
window,
graphview: RefCell::new(GraphView::new()),
@ -113,11 +117,8 @@ impl GPSApp {
// Create application and error out if that fails for whatever reason
let app = match GPSApp::new(application) {
Ok(app) => app,
Err(_err) => {
/* utils::show_error_dialog(
true,
format!("Error creating application: {}", err).as_str(),
); */
Err(err) => {
println!("Error creating application: {}", err);
return;
}
};
@ -125,7 +126,6 @@ impl GPSApp {
// When the application is activated show the UI. This happens when the first process is
// started, and in the first process whenever a second process is started
let app_weak = app.downgrade();
application.connect_activate(glib::clone!(@weak application => move |_| {
let app = upgrade_weak!(app_weak);
app.build_ui(&application);
@ -140,7 +140,7 @@ impl GPSApp {
let window: ApplicationWindow = app
.builder
.object("mainwindow")
.expect("Couldn't get window");
.expect("Couldn't get the main window");
let mut settings = Settings::load_settings();
settings.app_maximized = window.is_maximized();
settings.app_width = window.width();
@ -148,19 +148,19 @@ impl GPSApp {
let paned: Paned = app
.builder
.object("graph_logs-paned")
.expect("Couldn't get window");
.expect("Couldn't get graph_logs-paned");
settings.app_graph_logs_paned_pos = paned.position();
let paned: Paned = app
.builder
.object("graph_favorites-paned")
.expect("Couldn't get window");
.expect("Couldn't get graph_favorites-paned");
settings.app_graph_favorites_paned_pos = paned.position();
Settings::save_settings(&settings);
let pop_menu: PopoverMenu = app
.builder
.object("app_pop_menu")
.expect("Couldn't get pop over menu for app");
.expect("Couldn't get app_pop_menu");
pop_menu.unparent();
app.drop();
@ -203,12 +203,12 @@ impl GPSApp {
let mainwindow: ApplicationWindow = self
.builder
.object("mainwindow")
.expect("Couldn't get mainwindow");
.expect("Couldn't get the main window");
let pop_menu: PopoverMenu = self
.builder
.object("app_pop_menu")
.expect("Couldn't get popover menu");
.expect("Couldn't get app_pop_menu");
if let Some((x, y)) = widget.translate_coordinates(&mainwindow, x, y) {
let point = graphene::Point::new(x as f32, y as f32);
@ -232,12 +232,12 @@ impl GPSApp {
let application = gio::Application::default()
.expect("No default application")
.downcast::<gtk::Application>()
.expect("Default application has wrong type");
.expect("Unable to downcast default application");
let action = application
.lookup_action(action_name)
.expect("Unable to find action")
.unwrap_or_else(|| panic!("Unable to find action {}", action_name))
.dynamic_cast::<SimpleAction>()
.expect("Unable to cast to SimpleAction");
.expect("Unable to dynamic cast to SimpleAction");
if let Some(signal_handler_id) = self.menu_signal_handlers.borrow_mut().remove(action_name)
{
@ -270,7 +270,7 @@ impl GPSApp {
let window: ApplicationWindow = app
.builder
.object("mainwindow")
.expect("Couldn't get window");
.expect("Couldn't get main window");
let file_chooser: FileChooserDialog = FileChooserDialog::new(
Some(message),
Some(&window),
@ -289,7 +289,7 @@ impl GPSApp {
file.path()
.expect("Couldn't get file path")
.to_str()
.expect("unable to convert to string"),
.expect("Unable to convert to string"),
);
f(app, filename);
}
@ -336,8 +336,8 @@ impl GPSApp {
fn setup_logger_list(&self) {
let logger_list: TreeView = self
.builder
.object("logger_list")
.expect("Couldn't get window");
.object("treeview-logger")
.expect("Couldn't get treeview-logger");
let column = TreeViewColumn::new();
let cell = CellRendererText::new();
column.pack_start(&cell, true);
@ -358,8 +358,8 @@ impl GPSApp {
fn add_to_logger_list(&self, log_entry: String) {
let logger_list: TreeView = self
.builder
.object("logger_list")
.expect("Couldn't get window");
.object("treeview-logger")
.expect("Couldn't get treeview-logger");
if let Some(model) = logger_list.model() {
let list_store = model
.dynamic_cast::<ListStore>()
@ -382,8 +382,8 @@ impl GPSApp {
fn setup_favorite_list(&self, application: &Application) {
let favorite_list: TreeView = self
.builder
.object("favorites_list")
.expect("Couldn't get window");
.object("treeview-favorites")
.expect("Couldn't get treeview-favorites");
let column = TreeViewColumn::new();
let cell = CellRendererText::new();
@ -402,7 +402,7 @@ impl GPSApp {
.get(&iter, 0)
.get::<String>()
.expect("Treeview selection, column 1");
GPS_DEBUG!("{}", element_name);
GPS_DEBUG!("{} selected", element_name);
app.add_new_element(&element_name);
}
});
@ -418,14 +418,14 @@ impl GPSApp {
let element_name = model
.get(&iter, 0)
.get::<String>()
.expect("Treeview selection, column 1");
GPS_DEBUG!("{}", element_name);
.expect("Treeview selection, column 0");
GPS_DEBUG!("Element {} selected", element_name);
let pop_menu = app.app_pop_menu_at_position(&favorite_list, x, y);
let menu: gio::MenuModel = app
.builder
.object("fav_menu")
.expect("Couldn't get menu model for graph");
.expect("Couldn't get fav_menu model");
pop_menu.set_menu_model(Some(&menu));
let app_weak = app.downgrade();
@ -451,8 +451,8 @@ impl GPSApp {
if !favorites.contains(&element_name) {
let favorite_list: TreeView = self
.builder
.object("favorites_list")
.expect("Couldn't get window");
.object("treeview-favorites")
.expect("Couldn't get treeview-favorites");
if let Some(model) = favorite_list.model() {
let list_store = model
.dynamic_cast::<ListStore>()
@ -472,7 +472,7 @@ impl GPSApp {
let drawing_area_window: Viewport = self
.builder
.object("drawing_area")
.expect("Couldn't get window");
.expect("Couldn't get drawing_area");
drawing_area_window.set_child(Some(&*self.graphview.borrow()));
@ -482,7 +482,7 @@ impl GPSApp {
let status_bar: Statusbar = self
.builder
.object("status_bar")
.expect("Couldn't get window");
.expect("Couldn't get status_bar");
status_bar.push(status_bar.context_id("Description"), "GPS is ready");
self.setup_app_actions(application);
@ -490,7 +490,7 @@ impl GPSApp {
let pop_menu: PopoverMenu = self
.builder
.object("app_pop_menu")
.expect("Couldn't get pop over menu for app");
.expect("Couldn't get app_pop_menu");
pop_menu.set_parent(window);
let app_weak = self.downgrade();
@ -543,48 +543,26 @@ impl GPSApp {
let app = upgrade_weak!(app_weak);
GPSApp::display_plugin_list(&app);
});
let add_button: Button = self
.builder
.object("button-play")
.expect("Couldn't get app_button");
let app_weak = self.downgrade();
add_button.connect_clicked(glib::clone!(@weak window => move |_| {
// entry.set_text("Clicked!");
let app = upgrade_weak!(app_weak);
let graph_view = app.graphview.borrow();
let pipeline = app.pipeline.borrow();
if pipeline.state() == PipelineState::Stopped {
if let Err(err) = pipeline.create_pipeline(&pipeline.render_gst_launch(&graph_view)) {
GPS_ERROR!("Unable to start a pipeline: {}", err);
}
pipeline.set_state(PipelineState::Playing).expect("Unable to change state");
} else if pipeline.state() == PipelineState::Paused {
pipeline.set_state(PipelineState::Playing).expect("Unable to change state");
} else {
pipeline.set_state(PipelineState::Paused).expect("Unable to change state");
}
}));
let add_button: Button = self
.builder
.object("button-pause")
.expect("Couldn't get app_button");
let app_weak = self.downgrade();
add_button.connect_clicked(glib::clone!(@weak window => move |_| {
self.connect_button_action("button-play", move |_| {
let app = upgrade_weak!(app_weak);
let graph_view = app.graphview.borrow();
let pipeline = app.pipeline.borrow();
if pipeline.state() == PipelineState::Stopped {
if let Err(err) = pipeline.create_pipeline(&pipeline.render_gst_launch(&graph_view)) {
GPS_ERROR!("Unable to start a pipeline: {}", err);
}
pipeline.set_state(PipelineState::Paused).expect("Unable to change state");
} else if pipeline.state() == PipelineState::Paused {
pipeline.set_state(PipelineState::Playing).expect("Unable to change state");
} else {
pipeline.set_state(PipelineState::Paused).expect("Unable to change state");
}
}));
let _ = app
.pipeline
.borrow()
.start_pipeline(&graph_view, PipelineState::Playing);
});
let app_weak = self.downgrade();
self.connect_button_action("button-pause", move |_| {
let app = upgrade_weak!(app_weak);
let graph_view = app.graphview.borrow();
let _ = app
.pipeline
.borrow()
.start_pipeline(&graph_view, PipelineState::Paused);
});
let app_weak = self.downgrade();
self.connect_button_action("button-stop", move |_| {
@ -592,6 +570,7 @@ impl GPSApp {
let pipeline = app.pipeline.borrow();
let _ = pipeline.set_state(PipelineState::Stopped);
});
let app_weak = self.downgrade();
self.connect_button_action("button-clear", move |_| {
let app = upgrade_weak!(app_weak);
@ -614,7 +593,7 @@ impl GPSApp {
let menu: gio::MenuModel = app
.builder
.object("graph_menu")
.expect("Couldn't get menu model for graph");
.expect("Couldn't graph_menu");
pop_menu.set_menu_model(Some(&menu));
let app_weak = app.downgrade();
@ -624,7 +603,6 @@ impl GPSApp {
GPSApp::display_plugin_list(&app);
}
);
pop_menu.show();
None
}),
@ -795,13 +773,13 @@ impl GPSApp {
graph_view.remove_all_nodes();
}
fn save_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
fn save_graph(&self, filename: &str) -> anyhow::Result<()> {
let graph_view = self.graphview.borrow_mut();
graph_view.render_xml(filename)?;
Ok(())
}
fn load_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
fn load_graph(&self, filename: &str) -> anyhow::Result<()> {
self.clear_graph();
let graph_view = self.graphview.borrow_mut();
graph_view.load_xml(filename)?;

View file

@ -139,7 +139,7 @@
</object>
</child>
<child>
<object class="GtkButton" id="apply-plugin-properties">
<object class="GtkButton" id="button-apply-plugin-properties">
<property name="halign">end</property>
<property name="hexpand">1</property>
<property name="receives-default">1</property>
@ -254,7 +254,7 @@
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="GtkTreeView" id="favorites_list">
<object class="GtkTreeView" id="treeview-favorites">
</object>
</property>
</object>
@ -264,7 +264,7 @@
<child>
<object class="GtkScrolledWindow">
<property name="child">
<object class="GtkTreeView" id="logger_list"/>
<object class="GtkTreeView" id="treeview-logger"/>
</property>
</object>
</child>

View file

@ -25,7 +25,6 @@ use gst::prelude::*;
use gstreamer as gst;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::ops;
use std::rc::{Rc, Weak};
@ -88,7 +87,7 @@ pub struct PipelineInner {
}
impl Pipeline {
pub fn new() -> Result<Self, Box<dyn error::Error>> {
pub fn new() -> anyhow::Result<Self> {
let pipeline = Pipeline(Rc::new(PipelineInner {
pipeline: RefCell::new(None),
current_state: Cell::new(PipelineState::Stopped),
@ -97,36 +96,58 @@ impl Pipeline {
Ok(pipeline)
}
pub fn create_pipeline(&self, description: &str) -> Result<(), Box<dyn error::Error>> {
pub fn create_pipeline(&self, description: &str) -> anyhow::Result<()> {
GPS_INFO!("Creating pipeline {}", description);
/* create playbin */
// Create pipeline from the description
let pipeline = gst::parse_launch(&description.to_string())?;
if let Ok(pipeline) = pipeline.downcast::<gst::Pipeline>() {
//pipeline.set_property_message_forward(true);
let bus = pipeline.bus().expect("Pipeline had no bus");
let pipeline_weak = self.downgrade();
bus.add_watch_local(move |_bus, msg| {
let pipeline = upgrade_weak!(pipeline_weak, glib::Continue(false));
pipeline.on_pipeline_message(msg);
glib::Continue(true)
})?;
*self.pipeline.borrow_mut() = Some(pipeline);
/* start playing */
} else {
GPS_ERROR!("Couldn't downcast pipeline")
GPS_ERROR!("Can not create a proper pipeline from gstreamer parse_launch");
}
Ok(())
}
pub fn set_state(&self, state: PipelineState) -> Result<(), Box<dyn error::Error>> {
pub fn start_pipeline(
&self,
graphview: &GraphView,
new_state: PipelineState,
) -> anyhow::Result<PipelineState> {
if self.state() == PipelineState::Stopped {
self.create_pipeline(&self.render_gst_launch(graphview))
.map_err(|err| {
GPS_ERROR!("Unable to start a pipeline: {}", err);
})
.unwrap();
self.set_state(new_state)
.map_err(|_| GPS_ERROR!("Unable to change state"))
.unwrap();
} else if self.state() == PipelineState::Paused {
self.set_state(PipelineState::Playing)
.map_err(|_| GPS_ERROR!("Unable to change state"))
.unwrap();
} else {
self.set_state(PipelineState::Paused)
.map_err(|_| GPS_ERROR!("Unable to change state"))
.unwrap();
}
Ok(self.state())
}
pub fn set_state(&self, new_state: PipelineState) -> anyhow::Result<PipelineState> {
if let Some(pipeline) = self.pipeline.borrow().to_owned() {
match state {
match new_state {
PipelineState::Playing => pipeline.set_state(gst::State::Playing)?,
PipelineState::Paused => pipeline.set_state(gst::State::Paused)?,
PipelineState::Stopped => {
@ -134,9 +155,9 @@ impl Pipeline {
gst::StateChangeSuccess::Success
}
};
self.current_state.set(state);
self.current_state.set(new_state);
}
Ok(())
Ok(new_state)
}
pub fn state(&self) -> PipelineState {
@ -181,7 +202,7 @@ impl Pipeline {
};
}
pub fn elements_list() -> Result<Vec<ElementInfo>, Box<dyn error::Error>> {
pub fn elements_list() -> anyhow::Result<Vec<ElementInfo>> {
let registry = gst::Registry::get();
let mut elements: Vec<ElementInfo> = Vec::new();
let plugins = gst::Registry::plugin_list(&registry);
@ -204,24 +225,15 @@ impl Pipeline {
Ok(elements)
}
pub fn element_feature(
element_name: &str,
) -> anyhow::Result<gst::PluginFeature, Box<dyn error::Error>> {
fn element_feature(element_name: &str) -> Option<gst::PluginFeature> {
let registry = gst::Registry::get();
let feature = gst::Registry::find_feature(
&registry,
element_name,
gst::ElementFactory::static_type(),
)
.expect("Unable to find the element name");
Ok(feature)
gst::Registry::find_feature(&registry, element_name, gst::ElementFactory::static_type())
}
pub fn element_description(
element_name: &str,
) -> anyhow::Result<String, Box<dyn error::Error>> {
pub fn element_description(element_name: &str) -> anyhow::Result<String> {
let mut desc = String::from("");
let feature = Pipeline::element_feature(element_name)?;
let feature = Pipeline::element_feature(element_name)
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
desc.push_str("<b>Factory details:</b>\n");
@ -330,15 +342,13 @@ impl Pipeline {
}
}
pub fn element_properties(
element_name: &str,
) -> anyhow::Result<HashMap<String, String>, Box<dyn error::Error>> {
pub fn element_properties(element_name: &str) -> anyhow::Result<HashMap<String, String>> {
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");
.expect("Unable to get the factory from the feature");
let element = factory.create(None)?;
let params = element.class().list_properties();
@ -364,8 +374,10 @@ impl Pipeline {
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Factory not found");
let element = factory.create(None).expect("Unable to get factory");
.expect("Unable to get the factory from the feature");
let element = factory
.create(None)
.expect("Unable to create an element from the feature");
match element.dynamic_cast::<gst::URIHandler>() {
Ok(uri_handler) => uri_handler.uri_type() == gst::URIType::Src,
Err(_e) => false,
@ -373,7 +385,7 @@ impl Pipeline {
}
// Render graph methods
pub fn process_node(
fn process_gst_node(
&self,
graphview: &GraphView,
node: &Node,
@ -401,7 +413,7 @@ impl Pipeline {
description.push_str(&format!("{}. ", node.unique_name()));
} else {
description =
self.process_node(graphview, &node, elements, description.clone());
self.process_gst_node(graphview, &node, elements, description.clone());
}
}
}
@ -415,7 +427,7 @@ impl Pipeline {
let mut description = String::from("");
for source_node in source_nodes {
description =
self.process_node(graphview, &source_node, &mut elements, description.clone());
self.process_gst_node(graphview, &source_node, &mut elements, description.clone());
}
description
}

View file

@ -61,7 +61,7 @@ pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
let dialog: Dialog = app
.builder
.object("dialog-plugin-list")
.expect("Couldn't get window");
.expect("Couldn't get the dialog-plugin-list window");
if app.plugin_list_initialized.get().is_none() {
dialog.set_title(Some("Plugin list"));
@ -70,13 +70,13 @@ pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
let text_view: TextView = app
.builder
.object("textview-plugin-list")
.expect("Couldn't get window");
.expect("Couldn't get textview-plugin-list window");
let text_buffer: TextBuffer = text_view.buffer();
let tree: TreeView = app
.builder
.object("treeview-plugin-list")
.expect("Couldn't get window");
.expect("Couldn't get treeview-plugin-list window");
if tree.n_columns() < 2 {
append_column(&tree, 0);
append_column(&tree, 1);
@ -94,8 +94,8 @@ pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
let element_name = model
.get(&iter, 1)
.get::<String>()
.expect("Treeview selection, column 1");
let description = Pipeline::element_description(&element_name).expect("Unable to get element list from GStreamer");
.expect("Unable to get the treeview selection, column 1");
let description = Pipeline::element_description(&element_name).expect("Unable to get element description from GStreamer");
text_buffer.set_text("");
text_buffer.insert_markup(&mut text_buffer.end_iter(), &description);
}
@ -110,14 +110,12 @@ pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
let element_name = model
.get(&iter, 1)
.get::<String>()
.expect("Treeview selection, column 1");
.expect("Unable to get the treeview selection, column 1");
app.add_new_element(&element_name);
}
}),
);
app.plugin_list_initialized
.set(true)
.expect("Should never happen");
app.plugin_list_initialized.set(true).unwrap();
}
dialog.show();
@ -127,7 +125,7 @@ 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");
.expect("Couldn't get dialog-plugin-properties");
dialog.set_title(Some(&format!("{} properties", element_name)));
dialog.set_default_size(640, 480);
@ -136,7 +134,7 @@ pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32)
let properties_box: Box = app
.builder
.object("box-plugin-properties")
.expect("Couldn't get window");
.expect("Couldn't get box-plugin-properties");
let update_properties: Rc<RefCell<HashMap<String, String>>> =
Rc::new(RefCell::new(HashMap::new()));
let properties = Pipeline::element_properties(element_name).unwrap();
@ -163,8 +161,8 @@ pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32)
}
let properties_apply_btn: Button = app
.builder
.object("apply-plugin-properties")
.expect("Couldn't get window");
.object("button-apply-plugin-properties")
.expect("Couldn't get button-apply-plugin-properties");
let app_weak = app.downgrade();
properties_apply_btn.connect_clicked(