diff --git a/TODO.md b/TODO.md index 0e71372..500bcfa 100644 --- a/TODO.md +++ b/TODO.md @@ -40,8 +40,8 @@ - [ ] Remove a pad from a node - [ ] Implement graphview unit test - [ ] Implement pipeline unit test -- [ ] Save node position in XML -- [ ] Autosave the graph +- [x] Save node position in XML +- [x] Autosave the graph - [ ] Check the pîpeline live - [ ] Display pad properties with tooltip hover - [ ] Render a media file diff --git a/src/app.rs b/src/app.rs index 6575d98..b59e8cd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,7 +38,7 @@ use crate::logger; use crate::pipeline::{Pipeline, PipelineState}; use crate::plugindialogs; use crate::settings::Settings; -use crate::{GPS_DEBUG, GPS_ERROR}; +use crate::{GPS_DEBUG, GPS_ERROR, GPS_WARN}; use crate::graphmanager::{GraphView, Node, PortDirection}; @@ -110,6 +110,7 @@ impl GPSApp { plugin_list_initialized: OnceCell::new(), menu_signal_handlers: RefCell::new(HashMap::new()), })); + app.graphview.borrow_mut().set_id(0); Ok(app) } @@ -476,6 +477,17 @@ impl GPSApp { drawing_area_window.set_child(Some(&*self.graphview.borrow())); + // Setup the logger to get messages into the TreeView + let (ready_tx, ready_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + let app_weak = self.downgrade(); + logger::init_logger(ready_tx, logger::LogLevel::Debug); + self.setup_logger_list(); + let _ = ready_rx.attach(None, move |msg: String| { + let app = upgrade_weak!(app_weak, glib::Continue(false)); + app.add_to_logger_list(msg); + glib::Continue(true) + }); + let window = &self.window; window.show(); @@ -575,9 +587,29 @@ impl GPSApp { self.connect_button_action("button-clear", move |_| { let app = upgrade_weak!(app_weak); app.clear_graph(); - //app.load_graph("graphs/compositor.xml").expect("Unable to open file"); }); + let app_weak = self.downgrade(); + self.graphview + .borrow() + .connect_local( + "graph-updated", + false, + glib::clone!(@weak application => @default-return None, move |values: &[Value]| { + let app = upgrade_weak!(app_weak, None); + let id = values[1].get::().expect("id in args[1]"); + GPS_DEBUG!("Graph updated id={}", id); + let _ = app + .save_graph( + Settings::default_graph_file_path() + .to_str() + .expect("Unable to convert to string"), + ) + .map_err(|e| GPS_WARN!("Unable to save file {}", e)); + None + }), + ) + .expect("Failed to register graph-updated signal of graphview"); // When user clicks on port with right button let app_weak = self.downgrade(); self.graphview @@ -722,16 +754,15 @@ impl GPSApp { // Setup the favorite list self.setup_favorite_list(application); - // Setup the logger to get messages into the TreeView - let (ready_tx, ready_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); - let app_weak = self.downgrade(); - logger::init_logger(ready_tx, logger::LogLevel::Debug); - self.setup_logger_list(); - let _ = ready_rx.attach(None, move |msg: String| { - let app = upgrade_weak!(app_weak, glib::Continue(false)); - app.add_to_logger_list(msg); - glib::Continue(true) - }); + let _ = self + .load_graph( + Settings::default_graph_file_path() + .to_str() + .expect("Unable to convert to string"), + ) + .map_err(|_e| { + GPS_WARN!("Unable to load default graph"); + }); } // Downgrade to a weak reference @@ -743,7 +774,7 @@ impl GPSApp { fn drop(self) {} pub fn add_new_element(&self, element_name: &str) { - let graph_view = self.graphview.borrow_mut(); + let graph_view = self.graphview.borrow(); let node_id = graph_view.next_node_id(); let pads = Pipeline::pads(element_name, false); if Pipeline::element_is_uri_src_handler(element_name) { @@ -752,7 +783,7 @@ impl GPSApp { let node = app.graphview.borrow().node(&node_id).unwrap(); let mut properties: HashMap = HashMap::new(); properties.insert(String::from("location"), filename); - node.update_node_properties(&properties); + node.update_properties(&properties); }); } graph_view.add_node_with_port( @@ -765,7 +796,7 @@ impl GPSApp { pub fn update_element_properties(&self, node_id: u32, properties: &HashMap) { let node = self.graphview.borrow().node(&node_id).unwrap(); - node.update_node_properties(properties); + node.update_properties(properties); } fn clear_graph(&self) { @@ -774,14 +805,14 @@ impl GPSApp { } fn save_graph(&self, filename: &str) -> anyhow::Result<()> { - let graph_view = self.graphview.borrow_mut(); + let graph_view = self.graphview.borrow(); graph_view.render_xml(filename)?; Ok(()) } fn load_graph(&self, filename: &str) -> anyhow::Result<()> { self.clear_graph(); - let graph_view = self.graphview.borrow_mut(); + let graph_view = self.graphview.borrow(); graph_view.load_xml(filename)?; Ok(()) } diff --git a/src/graphmanager/graphview.rs b/src/graphmanager/graphview.rs index 43a252f..c40b570 100644 --- a/src/graphmanager/graphview.rs +++ b/src/graphmanager/graphview.rs @@ -56,6 +56,7 @@ mod imp { #[derive(Default)] pub struct GraphView { + pub(super) id: Cell, pub(super) nodes: RefCell>, pub(super) links: RefCell>, pub(super) current_node_id: Cell, @@ -126,6 +127,18 @@ mod imp { } ), ); + drag_controller.connect_drag_end( + clone!(@strong drag_state => move |drag_controller, _x, _y| { + let widget = drag_controller + .widget() + .expect("drag-end event has no widget") + .dynamic_cast::() + .expect("drag-end event is not on the GraphView"); + widget.graph_updated(); + } + ), + ); + obj.add_controller(&drag_controller); let gesture = gtk::GestureClick::new(); @@ -252,6 +265,13 @@ mod imp { <()>::static_type().into(), ) .build(), + Signal::builder( + "graph-updated", + // returns graph ID + &[u32::static_type().into()], + <()>::static_type().into(), + ) + .build(), ] }); SIGNALS.as_ref() @@ -374,6 +394,16 @@ impl GraphView { ); glib::Object::new(&[]).expect("Failed to create GraphView") } + pub fn set_id(&self, id: u32) { + let private = imp::GraphView::from_instance(self); + private.id.set(id) + } + + fn graph_updated(&self) { + let private = imp::GraphView::from_instance(self); + self.emit_by_name("graph-updated", &[&private.id.get()]) + .expect("unable to send signal"); + } pub fn add_node_with_port(&self, id: u32, node: Node, input: u32, output: u32) { let private = imp::GraphView::from_instance(self); @@ -423,6 +453,7 @@ impl GraphView { let port_id = self.next_port_id(); self.add_port(id, port_id, "out", PortDirection::Output); } + self.graph_updated(); } pub fn add_node(&self, id: u32, node: Node) { @@ -544,6 +575,7 @@ impl GraphView { let private = imp::GraphView::from_instance(self); if !self.link_exists(&link) { private.links.borrow_mut().insert(link.id, link); + self.graph_updated(); self.queue_draw(); } } @@ -772,12 +804,14 @@ impl GraphView { } pub fn render_xml(&self, filename: &str) -> anyhow::Result<()> { + let private = imp::GraphView::from_instance(self); let mut file = File::create(filename).unwrap(); let mut writer = EmitterConfig::new() .perform_indent(true) .create_writer(&mut file); - writer.write(XMLWEvent::start_element("Graph"))?; + writer + .write(XMLWEvent::start_element("Graph").attr("id", &private.id.get().to_string()))?; //Get the nodes let nodes = self.all_nodes(NodeType::All); @@ -806,6 +840,20 @@ impl GraphView { )?; writer.write(XMLWEvent::end_element())?; } + if let Some(position) = self.node_position(&node.upcast()) { + writer.write( + XMLWEvent::start_element("Property") + .attr("name", "_pos_x") + .attr("value", &position.0.to_string()), + )?; + writer.write(XMLWEvent::end_element())?; + writer.write( + XMLWEvent::start_element("Property") + .attr("name", "_pos_y") + .attr("value", &position.1.to_string()), + )?; + writer.write(XMLWEvent::end_element())?; + } writer.write(XMLWEvent::end_element())?; } //Get the link and write it. @@ -826,7 +874,7 @@ impl GraphView { } pub fn load_xml(&self, filename: &str) -> anyhow::Result<()> { - let file = File::open(filename).unwrap(); + let file = File::open(filename)?; let file = BufReader::new(file); let parser = EventReader::new(file); @@ -849,6 +897,9 @@ impl GraphView { match name.to_string().as_str() { "Graph" => { println!("New graph detected"); + if let Some(id) = attrs.get::(&String::from("id")) { + self.set_id(id.parse::().expect("id should be an u32")); + } } "Node" => { let id = attrs @@ -935,7 +986,21 @@ impl GraphView { "Node" => { if let Some(node) = current_node { let id = node.id(); + let mut pos_x = 0 as f32; + let mut pos_y = 0 as f32; + if let Some(value) = node.property("_pos_x") { + pos_x = value.parse::().unwrap(); + } + if let Some(value) = node.property("_pos_y") { + pos_y = value.parse::().unwrap(); + } + self.add_node(id, node); + if let Some(node) = self.node(&id) { + if pos_x != 0.0 || pos_y != 0.0 { + self.move_node(&node.upcast(), pos_x, pos_y); + } + } self.update_current_node_id(id); } current_node = None; diff --git a/src/graphmanager/node.rs b/src/graphmanager/node.rs index b58aaf5..49f6c1d 100644 --- a/src/graphmanager/node.rs +++ b/src/graphmanager/node.rs @@ -71,6 +71,7 @@ mod imp { pub(super) ports: RefCell>, pub(super) num_ports_in: Cell, pub(super) num_ports_out: Cell, + // Properties are differnet from GObject properties pub(super) properties: RefCell>, pub(super) selected: Cell, } @@ -190,13 +191,18 @@ impl Node { self_.description.set_text(description); println!("{}", description); } + pub fn hidden_property(&self, name: &str) -> bool { + name.starts_with('_') + } fn update_description(&self) { let self_ = imp::Node::from_instance(self); let mut description = String::from(""); for (name, value) in self_.properties.borrow().iter() { - description.push_str(&format!("{}:{}", name, value)); - description.push('\n'); + if !self.hidden_property(name) { + description.push_str(&format!("{}:{}", name, value)); + description.push('\n'); + } } self.set_description(&description); } @@ -280,8 +286,8 @@ impl Node { self.update_description(); } - pub fn update_node_properties(&self, new_properties: &HashMap) { - for (key, value) in new_properties { + pub fn update_properties(&self, new_node_properties: &HashMap) { + for (key, value) in new_node_properties { self.add_property(key.clone(), value.clone()); } } @@ -291,6 +297,14 @@ impl Node { private.properties.borrow() } + pub fn property(&self, name: &str) -> Option { + let private = imp::Node::from_instance(self); + if let Some(property) = private.properties.borrow().get(name) { + return Some(property.clone()); + } + None + } + pub fn toggle_selected(&self) { self.set_selected(!self.selected()); } diff --git a/src/pipeline.rs b/src/pipeline.rs index f1e35b3..cd4ef69 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -127,21 +127,15 @@ impl Pipeline { 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(); + err + })?; } + self.set_state(new_state).map_err(|error| { + GPS_ERROR!("Unable to change state {}", error); + error + })?; + Ok(self.state()) } @@ -396,7 +390,9 @@ impl Pipeline { description.push_str(&format!("{} name={} ", node.name(), unique_name)); elements.insert(unique_name.clone(), unique_name.clone()); for (name, value) in node.properties().iter() { - description.push_str(&format!("{}={}", name, value)); + if !node.hidden_property(name) { + description.push_str(&format!("{}={}", name, value)); + } } let ports = node.all_ports(PortDirection::Output); diff --git a/src/settings.rs b/src/settings.rs index 2125fec..82626c3 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -42,6 +42,13 @@ impl Settings { path } + pub fn default_graph_file_path() -> PathBuf { + let mut path = glib::user_config_dir(); + path.push(config::APP_ID); + path.push("default_graph.toml"); + path + } + // Public methods pub fn add_favorite(favorite: &str) { let mut settings = Settings::load_settings();