diff --git a/Cargo.lock b/Cargo.lock index 1e3e5f8..77bd848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -415,6 +415,7 @@ dependencies = [ "gtk4", "log", "once_cell", + "xml-rs", ] [[package]] @@ -965,3 +966,9 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/Cargo.toml b/Cargo.toml index fa45240..59a5025 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ gtk = { version = "0.3", package = "gtk4" } anyhow = "1" gstreamer = "0.16" log = "0.4.11" -once_cell = "1.7.2" \ No newline at end of file +once_cell = "1.7.2" +xml-rs = "0.8.4" \ No newline at end of file diff --git a/TODO.md b/TODO.md index 9cc690a..f8d8df3 100644 --- a/TODO.md +++ b/TODO.md @@ -14,13 +14,14 @@ TODO: - [] create contextual menu on pad or element - [] upclass the element - [] create a crate for graphview/node/port -- [] save/load pipeline +- [x] save/load pipeline - [] Run a pipeline with GStreamer - [] Run the pipeline with GStreamer - [] Control the pipeline with GStreamer - [x] Define the license - [] Connect the logs to the window - [] Create a window for the video output +- [] Add multiple graphviews with tabs. ## Code cleanup diff --git a/src/app.rs b/src/app.rs index 02b8d6a..14ba146 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,8 +19,8 @@ use gtk::prelude::*; use gtk::{gio, glib}; use gtk::{ - AboutDialog, Application, ApplicationWindow, Builder, Button, FileChooserDialog, ResponseType, - Statusbar, Viewport, + AboutDialog, Application, ApplicationWindow, Builder, Button, FileChooserAction, + FileChooserDialog, ResponseType, Statusbar, Viewport, }; use std::cell::RefCell; use std::rc::{Rc, Weak}; @@ -115,8 +115,6 @@ impl GPSApp { } pub fn build_ui(&self, application: &Application) { - //let app_weak = self.downgrade(); - let drawing_area_window: Viewport = self .builder .object("drawing_area") @@ -133,6 +131,63 @@ impl GPSApp { .expect("Couldn't get window"); status_bar.push(status_bar.context_id("Description"), "GPS is ready"); + let action = gio::SimpleAction::new("open", None); + let app_weak = self.downgrade(); + // Add a dialog to open the graph + action.connect_activate(glib::clone!(@weak window => move |_, _| { + let app = upgrade_weak!(app_weak); + let file_chooser = FileChooserDialog::new( + Some("Open File"), + Some(&window), + FileChooserAction::Open, + &[("Open", ResponseType::Ok), ("Cancel", ResponseType::Cancel)], + ); + file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| { + if response == ResponseType::Ok { + let file = d.file().expect("Couldn't get file"); + let filename = String::from(file.path().expect("Couldn't get file path").to_str().expect("unable to convert to string")); + println!("Open file {}", filename); + app.load_graph(&filename).expect("Unable to open file"); + } + + d.close(); + }); + + file_chooser.show(); + + })); + application.add_action(&action); + application.set_accels_for_action("app.open", &["o"]); + + // Add a dialog to save the graph + let action = gio::SimpleAction::new("save_as", None); + let app_weak = self.downgrade(); + action.connect_activate(glib::clone!(@weak window => move |_, _| { + let app = upgrade_weak!(app_weak); + let file_chooser = FileChooserDialog::new( + Some("Save File"), + Some(&window), + FileChooserAction::Open, + &[("Save", ResponseType::Ok), ("Cancel", ResponseType::Cancel)], + ); + file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| { + if response == ResponseType::Ok { + let file = d.file().expect("Couldn't get file"); + let filename = String::from(file.path().expect("Couldn't get file path").to_str().expect("unable to convert to string")); + println!("Save file {}", filename); + app.save_graph(&filename).expect("Unable to save file"); + } + + d.close(); + }); + + file_chooser.show(); + + })); + + application.add_action(&action); + application.set_accels_for_action("app.save", &["s"]); + let action = gio::SimpleAction::new("quit", None); action.connect_activate({ let app = application.downgrade(); @@ -173,42 +228,17 @@ impl GPSApp { let elements = Pipeline::elements_list().expect("Unable to obtain element's list"); pluginlist::display_plugin_list(&app, &elements); })); - // Create a dialog to open a file - let open_button: Button = self + + let add_button: Button = self .builder - .object("button-open-file") + .object("button-play") .expect("Couldn't get app_button"); - let open_dialog: FileChooserDialog = self - .builder - .object("dialog-open-file") - .expect("Couldn't get window"); - open_button.connect_clicked(glib::clone!(@weak window => move |_| { - open_dialog.connect_response(|dialog, _| dialog.close()); - open_dialog.add_buttons(&[ - ("Open", ResponseType::Ok), - ("Cancel", ResponseType::Cancel) - ]); + let app_weak = self.downgrade(); + add_button.connect_clicked(glib::clone!(@weak window => move |_| { + // entry.set_text("Clicked!"); + let _app = upgrade_weak!(app_weak); - open_dialog.connect_response(|open_dialog, response| { - if response == ResponseType::Ok { - let file = open_dialog.file().expect("Couldn't get file"); - println!("Files: {:?}", file); - } - open_dialog.close(); - }); - open_dialog.show(); })); - - // 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 add_button: Button = self .builder .object("button-stop") @@ -221,15 +251,15 @@ impl GPSApp { let node_id = graph_view.get_next_node_id(); let element_name = String::from("appsink"); let pads = Pipeline::get_pads(&element_name, false); - graph_view.add_node(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1); + graph_view.add_node_with_port(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1); let node_id = graph_view.get_next_node_id(); let element_name = String::from("videotestsrc"); let pads = Pipeline::get_pads(&element_name, false); - graph_view.add_node(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1); + graph_view.add_node_with_port(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1); let node_id = graph_view.get_next_node_id(); let element_name = String::from("videoconvert"); let pads = Pipeline::get_pads(&element_name, false); - graph_view.add_node(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1); + graph_view.add_node_with_port(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1); })); let add_button: Button = self @@ -256,7 +286,7 @@ impl GPSApp { let graph_view = self.graphview.borrow_mut(); let node_id = graph_view.next_node_id(); let pads = Pipeline::get_pads(&element_name, false); - graph_view.add_node( + graph_view.add_node_with_port( node_id, Node::new( node_id, @@ -267,4 +297,17 @@ impl GPSApp { pads.1, ); } + + pub fn save_graph(&self, filename: &str) -> anyhow::Result<(), Box> { + let graph_view = self.graphview.borrow_mut(); + graph_view.render_xml(filename)?; + Ok(()) + } + + pub fn load_graph(&self, filename: &str) -> anyhow::Result<(), Box> { + let graph_view = self.graphview.borrow_mut(); + graph_view.remove_all_nodes(); + graph_view.load_xml(filename)?; + Ok(()) + } } diff --git a/src/gps.ui b/src/gps.ui index 2af55fc..5b6098b 100644 --- a/src/gps.ui +++ b/src/gps.ui @@ -8,6 +8,16 @@ app.new-window <primary>n + + _Open + app.open + <primary>n + + + _Save As + app.save_as + <primary>n + _About GstPipelineStudio app.about @@ -59,13 +69,17 @@ 600 - 0 - True - - win.open - Open - + + 3 + 0 + + + primary_menu + + + open-menu-symbolic + @@ -86,14 +100,6 @@ list-add - - - 1 - gtk-open - 1 - document-open-symbolic - - 1 diff --git a/src/graphmanager/graphview.rs b/src/graphmanager/graphview.rs index bac05ba..6cc5a98 100644 --- a/src/graphmanager/graphview.rs +++ b/src/graphmanager/graphview.rs @@ -18,7 +18,15 @@ // // SPDX-License-Identifier: GPL-3.0-only -use super::{node::Node, port::Port, port::PortDirection}; +extern crate xml; +use xml::reader::EventReader; +use xml::reader::XmlEvent as XMLREvent; +use xml::writer::EmitterConfig; +use xml::writer::XmlEvent as XMLWEvent; + +use super::{node::Node, node::NodeType, port::Port, port::PortDirection}; +use std::fs::File; +use std::io::BufReader; use gtk::{ glib::{self, clone}, @@ -29,14 +37,15 @@ use gtk::{ use log::{error, warn}; use std::cell::RefMut; -use std::{cmp::Ordering, collections::HashMap}; - +use std::{cmp::Ordering, collections::HashMap, error}; #[derive(Debug, Clone)] pub struct NodeLink { + pub id: u32, pub node_from: u32, pub node_to: u32, pub port_from: u32, pub port_to: u32, + pub active: bool, } static GRAPHVIEW_STYLE: &str = include_str!("graphview.css"); @@ -54,7 +63,7 @@ mod imp { #[derive(Default)] pub struct GraphView { pub(super) nodes: RefCell>, - pub(super) links: RefCell>, + pub(super) links: RefCell>, pub(super) current_node_id: Cell, pub(super) current_port_id: Cell, pub(super) current_link_id: Cell, @@ -132,24 +141,27 @@ mod imp { .expect("click event has no widget") .dynamic_cast::() .expect("click event is not on the GraphView"); - if let Some(target) = widget.pick(x, y, gtk::PickFlags::DEFAULT) { - if let Some(target) = target.ancestor(Port::static_type()) { - let to_port = target.dynamic_cast::().expect("click event is not on the Port"); - if !widget.port_is_linked(&to_port) { - let selected_port = widget.selected_port().to_owned(); - if let Some(from_port) = selected_port { - println!("Port {} is clicked at {}:{}", to_port.id(), x, y); - if widget.ports_compatible(&to_port) { - let from_node = from_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); - let to_node = to_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); - println!("add link"); - widget.add_link(widget.get_next_link_id(), NodeLink { - node_from: from_node.id(), - node_to: to_node.id(), - port_from: from_port.id(), - port_to: to_port.id() - }, true); - } + + if let Some(target) = widget.pick(x, y, gtk::PickFlags::DEFAULT) { + if let Some(target) = target.ancestor(Port::static_type()) { + let to_port = target.dynamic_cast::().expect("click event is not on the Port"); + if let None = widget.port_is_linked(to_port.id()) { + let selected_port = widget.selected_port().to_owned(); + if let Some(from_port) = selected_port { + println!("Port {} is clicked at {}:{}", to_port.id(), x, y); + if widget.ports_compatible(&to_port) { + let from_node = from_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); + let to_node = to_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::().expect("Unable to cast to Node"); + println!("add link"); + widget.add_link(NodeLink { + id: widget.next_link_id(), + node_from: from_node.id(), + node_to: to_node.id(), + port_from: from_port.id(), + port_to: to_port.id(), + active: true + } ); + } widget.set_selected_port(None); } else { println!("add selected port id"); @@ -195,12 +207,12 @@ mod imp { link_cr.set_line_width(1.5); - for (link, active) in self.links.borrow().values() { - if let Some((from_x, from_y, to_x, to_y)) = self.get_link_coordinates(link) { + for link in self.links.borrow().values() { + if let Some((from_x, from_y, to_x, to_y)) = self.link_coordinates(link) { //println!("from_x: {} from_y: {} to_x: {} to_y: {}", from_x, from_y, to_x, to_y); // Use dashed line for inactive links, full line otherwise. - if *active { + if link.active { link_cr.set_dash(&[], 0.0); } else { link_cr.set_dash(&[10.0, 5.0], 0.0); @@ -225,13 +237,13 @@ mod imp { /// /// # Returns /// `Some((from_x, from_y, to_x, to_y))` if all objects the links refers to exist as widgets. - fn get_link_coordinates(&self, link: &NodeLink) -> Option<(f64, f64, f64, f64)> { + fn link_coordinates(&self, link: &NodeLink) -> Option<(f64, f64, f64, f64)> { let nodes = self.nodes.borrow(); // For some reason, gtk4::WidgetExt::translate_coordinates gives me incorrect values, // so we manually calculate the needed offsets here. - let from_port = &nodes.get(&link.node_from)?.get_port(link.port_from)?; + let from_port = &nodes.get(&link.node_from)?.port(&link.port_from)?; let gtk::Allocation { x: mut fx, y: mut fy, @@ -246,7 +258,7 @@ mod imp { fx += fnx + (fw / 2); fy += fny + (fh / 2); - let to_port = &nodes.get(&link.node_to)?.get_port(link.port_to)?; + let to_port = &nodes.get(&link.node_to)?.port(&link.port_to)?; let gtk::Allocation { x: mut tx, y: mut ty, @@ -284,21 +296,22 @@ impl GraphView { glib::Object::new(&[]).expect("Failed to create GraphView") } - pub fn add_node(&self, id: u32, node: Node, input: u32, output: u32) { + pub fn add_node_with_port(&self, id: u32, node: Node, input: u32, output: u32) { let private = imp::GraphView::from_instance(self); node.set_parent(self); // Place widgets in colums of 3, growing down - // let x = if let Some(node_type) = node_type { - // match node_type { - // NodeType::Src => 20.0, - // NodeType::Transform => 420.0, - // NodeType::Sink => 820.0, - // } - // } else { - // 420.0 - // }; - let x = 20.0; + let x = if let Some(node_type) = node.node_type() { + match node_type { + NodeType::Source => 20.0, + NodeType::Transform => 320.0, + NodeType::Sink => 620.0, + _ => 20.0, + } + } else { + 420.0 + }; + let y = private .nodes .borrow() @@ -335,6 +348,10 @@ impl GraphView { } } + pub fn add_node(&self, id: u32, node: Node) { + self.add_node_with_port(id, node, 0, 0); + } + pub fn remove_node(&self, id: u32) { let private = imp::GraphView::from_instance(self); let mut nodes = private.nodes.borrow_mut(); @@ -347,7 +364,7 @@ impl GraphView { } pub fn all_nodes(&self) -> Vec { let private = imp::GraphView::from_instance(self); - let nodes = private.nodes.borrow_mut(); + let nodes = private.nodes.borrow(); let nodes_list: Vec<_> = nodes.iter().map(|(_, node)| node.clone()).collect(); nodes_list } @@ -368,6 +385,17 @@ impl GraphView { self.queue_draw(); } + pub fn node_is_linked(&self, node_id: u32) -> Option { + let private = imp::GraphView::from_instance(self); + for (key, link) in private.links.borrow().iter() { + if link.node_from == node_id || link.node_to == node_id { + return Some(*key); + } + } + None + } + + // Port related methods pub fn add_port(&self, node_id: u32, port_id: u32, port: Port) { let private = imp::GraphView::from_instance(self); println!( @@ -392,18 +420,37 @@ impl GraphView { } } - pub fn add_link(&self, link_id: u32, link: NodeLink, active: bool) { + pub fn port_is_linked(&self, port_id: u32) -> Option<(u32, u32, u32)> { + let private = imp::GraphView::from_instance(self); + for (key, link) in private.links.borrow().iter() { + if link.port_from == port_id || link.port_to == port_id { + return Some((*key, link.node_from, link.port_from)); + } + } + None + } + + // Link related methods + + pub fn all_links(&self) -> Vec { + let private = imp::GraphView::from_instance(self); + let links = private.links.borrow(); + let links_list: Vec<_> = links.iter().map(|(_, link)| link.clone()).collect(); + links_list + } + + pub fn add_link(&self, link: NodeLink) { let private = imp::GraphView::from_instance(self); if !self.link_exists(&link) { - private.links.borrow_mut().insert(link_id, (link, active)); + private.links.borrow_mut().insert(link.id, link); self.queue_draw(); } } pub fn set_link_state(&self, link_id: u32, active: bool) { let private = imp::GraphView::from_instance(self); - if let Some((_, state)) = private.links.borrow_mut().get_mut(&link_id) { - *state = active; + if let Some(link) = private.links.borrow_mut().get_mut(&link_id) { + link.active = active; self.queue_draw(); } else { warn!("Link state changed on unknown link (id={})", link_id); @@ -418,17 +465,6 @@ impl GraphView { self.queue_draw(); } - pub fn node_is_linked(&self, node_id: u32) -> Option { - let private = imp::GraphView::from_instance(self); - let links = private.links.borrow_mut(); - for (key, value) in &*links { - if value.0.node_from == node_id || value.0.node_to == node_id { - return Some(*key); - } - } - None - } - /// Get the position of the specified node inside the graphview. /// /// Returns `None` if the node is not in the graphview. @@ -476,7 +512,7 @@ impl GraphView { pub(super) fn link_exists(&self, new_link: &NodeLink) -> bool { let private = imp::GraphView::from_instance(self); - for (link, _active) in private.links.borrow().values() { + for link in private.links.borrow().values() { if (new_link.port_from == link.port_from && new_link.port_to == link.port_to) || (new_link.port_to == link.port_from && new_link.port_from == link.port_to) { @@ -487,18 +523,6 @@ impl GraphView { false } - pub(super) fn port_is_linked(&self, port: &Port) -> bool { - let private = imp::GraphView::from_instance(self); - - for (id, (link, _active)) in private.links.borrow().iter() { - if port.id() == link.port_from || port.id() == link.port_to { - println!("port {} is already linked {}", port.id(), id); - return true; - } - } - return false; - } - pub(super) fn ports_compatible(&self, to_port: &Port) -> bool { let current_port = self.selected_port().to_owned(); if let Some(from_port) = current_port { @@ -539,7 +563,7 @@ impl GraphView { private.current_port_id.get() } - fn get_next_link_id(&self) -> u32 { + fn next_link_id(&self) -> u32 { let private = imp::GraphView::from_instance(self); private .current_link_id @@ -556,6 +580,181 @@ impl GraphView { let private = imp::GraphView::from_instance(self); private.port_selected.borrow_mut() } + + // Render graph methods + pub fn render_xml(&self, filename: &str) -> anyhow::Result<(), Box> { + 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"))?; + + //Get the nodes + let nodes = self.all_nodes(); + for node in nodes { + writer.write( + XMLWEvent::start_element("Node") + .attr("name", &node.name()) + .attr("id", &node.id().to_string()) + .attr("type", &node.node_type().unwrap().to_string()), + )?; + for port in node.ports().values() { + writer.write( + XMLWEvent::start_element("Port") + .attr("name", &port.name()) + .attr("id", &port.id().to_string()) + .attr("direction", &port.direction().to_string()), + )?; + writer.write(XMLWEvent::end_element())?; + } + writer.write(XMLWEvent::end_element())?; + } + //Get the link and write it. + for link in self.all_links() { + writer.write( + XMLWEvent::start_element("Link") + .attr("id", &link.id.to_string()) + .attr("node_from", &link.node_from.to_string()) + .attr("node_to", &link.node_to.to_string()) + .attr("port_from", &link.port_from.to_string()) + .attr("port_to", &link.port_to.to_string()) + .attr("active", &link.active.to_string()), + )?; + writer.write(XMLWEvent::end_element())?; + } + writer.write(XMLWEvent::end_element())?; + Ok(()) + } + + pub fn load_xml(&self, filename: &str) -> anyhow::Result<(), Box> { + let file = File::open(filename).unwrap(); + let file = BufReader::new(file); + + let parser = EventReader::new(file); + + let mut current_node: Option = None; + let mut current_port: Option = None; + let mut current_link: Option = None; + for e in parser { + match e { + Ok(XMLREvent::StartElement { + ref name, + ref attributes, + .. + }) => { + println!("{}", name); + let mut attrs = HashMap::new(); + attributes.iter().for_each(|a| { + attrs.insert(a.name.to_string(), a.value.to_string()); + }); + match name.to_string().as_str() { + "Graph" => { + println!("New graph detected"); + } + "Node" => { + let id = attrs + .get::(&String::from("id")) + .expect("Unable to find node id"); + let name = attrs + .get::(&String::from("name")) + .expect("Unable to find node name"); + let node_type: &String = attrs + .get::(&String::from("type")) + .expect("Unable to find node type"); + + current_node = Some(Node::new( + id.parse::().unwrap(), + name, + NodeType::from_str(node_type.as_str()), + )); + } + "Port" => { + let id = attrs + .get::(&String::from("id")) + .expect("Unable to find port id"); + let name = attrs + .get::(&String::from("name")) + .expect("Unable to find port name"); + let direction: &String = attrs + .get::(&String::from("direction")) + .expect("Unable to find port direction"); + current_port = Some(Port::new( + id.parse::().unwrap(), + name, + PortDirection::from_str(direction), + )); + } + "Link" => { + let id = attrs + .get::(&String::from("id")) + .expect("Unable to find link id"); + let node_from = attrs + .get::(&String::from("node_from")) + .expect("Unable to find link node_from"); + let node_to = attrs + .get::(&String::from("node_to")) + .expect("Unable to find link node_to"); + let port_from = attrs + .get::(&String::from("port_from")) + .expect("Unable to find link port_from"); + let port_to = attrs + .get::(&String::from("port_to")) + .expect("Unable to find link port_to"); + let active: &String = attrs + .get::(&String::from("active")) + .expect("Unable to find link state"); + current_link = Some(NodeLink { + id: id.parse::().unwrap(), + node_from: node_from.parse::().unwrap(), + node_to: node_to.parse::().unwrap(), + port_from: port_from.parse::().unwrap(), + port_to: port_to.parse::().unwrap(), + active: active.parse::().unwrap(), + }); + } + _ => println!("name unknown: {}", name), + } + } + Ok(XMLREvent::EndElement { name }) => { + println!("closing {}", name); + match name.to_string().as_str() { + "Graph" => { + println!("Graph ended"); + } + "Node" => { + if let Some(node) = current_node { + let id = node.id(); + self.add_node(id, node); + } + current_node = None; + } + "Port" => { + if let Some(port) = current_port { + let node = current_node.clone(); + node.expect("No current node, error...") + .add_port(port.id(), port); + } + current_port = None; + } + "Link" => { + if let Some(link) = current_link { + self.add_link(link); + } + current_link = None; + } + _ => println!("name unknown: {}", name), + } + } + Err(e) => { + println!("Error: {}", e); + break; + } + _ => {} + } + } + Ok(()) + } } impl Default for GraphView { diff --git a/src/graphmanager/node.rs b/src/graphmanager/node.rs index 6288e38..e720e9f 100644 --- a/src/graphmanager/node.rs +++ b/src/graphmanager/node.rs @@ -24,16 +24,38 @@ use gtk::subclass::prelude::*; use super::Port; use super::PortDirection; -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, Ref, RefCell}; use std::collections::HashMap; +use std::fmt; + +#[derive(Debug, Clone, PartialEq)] pub enum NodeType { Source, Transform, Sink, + All, Unknown, } +impl fmt::Display for NodeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl NodeType { + pub fn from_str(node_type_name: &str) -> NodeType { + match node_type_name { + "Source" => NodeType::Source, + "Transform" => NodeType::Transform, + "Sink" => NodeType::Sink, + "All" => NodeType::All, + _ => NodeType::Unknown, + } + } +} + mod imp { use super::*; use once_cell::unsync::OnceCell; @@ -104,11 +126,14 @@ impl Node { let private = imp::Node::from_instance(&res); private.id.set(id).expect("Node id already set"); res.set_name(name); - private.node_type.set(node_type); + private + .node_type + .set(node_type) + .expect("Node type is already set"); res } - pub fn set_name(&self, name: &str) { + fn set_name(&self, name: &str) { let self_ = imp::Node::from_instance(self); self_.label.set_text(name); println!("{}", name); @@ -130,14 +155,19 @@ impl Node { .attach(&port, 1, private.num_ports_out.get() + 1, 1, 1); private.num_ports_out.set(private.num_ports_out.get() + 1); } + _ => panic!("Port without direction"), } private.ports.borrow_mut().insert(id, port); } - pub fn get_port(&self, id: u32) -> Option { + pub fn ports(&self) -> Ref> { let private = imp::Node::from_instance(self); - private.ports.borrow_mut().get(&id).cloned() + private.ports.borrow() + } + pub fn port(&self, id: &u32) -> Option { + let private = imp::Node::from_instance(self); + private.ports.borrow().get(id).cloned() } pub fn remove_port(&self, id: u32) { @@ -146,13 +176,31 @@ impl Node { match port.direction() { PortDirection::Input => private.num_ports_in.set(private.num_ports_in.get() - 1), PortDirection::Output => private.num_ports_in.set(private.num_ports_out.get() - 1), + _ => panic!("Port without direction"), } - port.unparent(); } } + pub fn id(&self) -> u32 { let private = imp::Node::from_instance(self); private.id.get().copied().expect("Node id is not set") } + + pub fn name(&self) -> String { + let private = imp::Node::from_instance(self); + private.label.text().to_string() + } + + pub fn unique_name(&self) -> String { + let private = imp::Node::from_instance(self); + let mut unique_name = private.label.text().to_string(); + unique_name.push_str(&self.id().to_string()); + unique_name + } + + pub fn node_type(&self) -> Option<&NodeType> { + let private = imp::Node::from_instance(self); + private.node_type.get() + } } diff --git a/src/graphmanager/port.rs b/src/graphmanager/port.rs index 9307508..e8f81d6 100644 --- a/src/graphmanager/port.rs +++ b/src/graphmanager/port.rs @@ -22,11 +22,31 @@ use gtk::{ prelude::*, subclass::prelude::*, }; +use std::fmt; #[derive(Debug, Clone, PartialEq)] pub enum PortDirection { Input, Output, + Unknown, +} + +impl fmt::Display for PortDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + // or, alternatively: + // fmt::Debug::fmt(self, f) + } +} + +impl PortDirection { + pub fn from_str(port_direction_name: &str) -> PortDirection { + match port_direction_name { + "Input" => PortDirection::Input, + "Output" => PortDirection::Output, + _ => PortDirection::Unknown, + } + } } mod imp { @@ -119,4 +139,13 @@ impl Port { let private = imp::Port::from_instance(self); private.direction.get().expect("Port direction is not set") } + + pub fn name(&self) -> String { + let private = imp::Port::from_instance(self); + private + .direction + .get() + .expect("direction is not set") + .to_string() + } }