GPS: Add xml save/load in graphview

Can now save and load in XML
graphs/nodes/ports/links
This commit is contained in:
Stéphane Cerveau 2022-01-06 14:57:17 +01:00
parent 5f91fbaef7
commit 52286ada4a
8 changed files with 465 additions and 131 deletions

7
Cargo.lock generated
View file

@ -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"

View file

@ -10,4 +10,5 @@ gtk = { version = "0.3", package = "gtk4" }
anyhow = "1"
gstreamer = "0.16"
log = "0.4.11"
once_cell = "1.7.2"
once_cell = "1.7.2"
xml-rs = "0.8.4"

View file

@ -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

View file

@ -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", &["<primary>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", &["<primary>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<dyn error::Error>> {
let graph_view = self.graphview.borrow_mut();
graph_view.render_xml(filename)?;
Ok(())
}
pub fn load_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
let graph_view = self.graphview.borrow_mut();
graph_view.remove_all_nodes();
graph_view.load_xml(filename)?;
Ok(())
}
}

View file

@ -8,6 +8,16 @@
<attribute name="action">app.new-window</attribute>
<attribute name="accel">&lt;primary&gt;n</attribute>
</item>
<item>
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the graph">_Open</attribute>
<attribute name="action">app.open</attribute>
<attribute name="accel">&lt;primary&gt;n</attribute>
</item>
<item>
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
<attribute name="action">app.save_as</attribute>
<attribute name="accel">&lt;primary&gt;n</attribute>
</item>
<item>
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the About dialog.">_About GstPipelineStudio</attribute>
<attribute name="action">app.about</attribute>
@ -59,13 +69,17 @@
<property name="default-height">600</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header-bar">
<property name="hexpand">0</property>
<property name="show-title-buttons">True</property>
<child type="start">
<object class="GtkButton">
<property name="action-name">win.open</property>
<property name="label">Open</property>
</object>
<object class="GtkMenuButton" id="gear_menu_button">
<property name="valign">3</property>
<property name="focus-on-click">0</property>
<property name="popover">
<object class="GtkPopoverMenu" id="gear_menu">
<property name="menu-model">primary_menu</property>
</object>
</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
</child>
@ -86,14 +100,6 @@
<property name="icon-name">list-add</property>
</object>
</child>
<child>
<object class="GtkButton" id="button-open-file">
<property name="hexpand">1</property>
<property name="label">gtk-open</property>
<property name="receives-default">1</property>
<property name="icon-name">document-open-symbolic</property>
</object>
</child>
<child>
<object class="GtkButton" id="button-play">
<property name="hexpand">1</property>

View file

@ -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<HashMap<u32, Node>>,
pub(super) links: RefCell<HashMap<u32, (NodeLink, bool)>>,
pub(super) links: RefCell<HashMap<u32, NodeLink>>,
pub(super) current_node_id: Cell<u32>,
pub(super) current_port_id: Cell<u32>,
pub(super) current_link_id: Cell<u32>,
@ -132,24 +141,27 @@ mod imp {
.expect("click event has no widget")
.dynamic_cast::<Self::Type>()
.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::<Port>().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::<Node>().expect("Unable to cast to Node");
let to_node = to_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().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::<Port>().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::<Node>().expect("Unable to cast to Node");
let to_node = to_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().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<Node> {
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<u32> {
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<NodeLink> {
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<u32> {
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<dyn error::Error>> {
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<dyn error::Error>> {
let file = File::open(filename).unwrap();
let file = BufReader::new(file);
let parser = EventReader::new(file);
let mut current_node: Option<Node> = None;
let mut current_port: Option<Port> = None;
let mut current_link: Option<NodeLink> = 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>(&String::from("id"))
.expect("Unable to find node id");
let name = attrs
.get::<String>(&String::from("name"))
.expect("Unable to find node name");
let node_type: &String = attrs
.get::<String>(&String::from("type"))
.expect("Unable to find node type");
current_node = Some(Node::new(
id.parse::<u32>().unwrap(),
name,
NodeType::from_str(node_type.as_str()),
));
}
"Port" => {
let id = attrs
.get::<String>(&String::from("id"))
.expect("Unable to find port id");
let name = attrs
.get::<String>(&String::from("name"))
.expect("Unable to find port name");
let direction: &String = attrs
.get::<String>(&String::from("direction"))
.expect("Unable to find port direction");
current_port = Some(Port::new(
id.parse::<u32>().unwrap(),
name,
PortDirection::from_str(direction),
));
}
"Link" => {
let id = attrs
.get::<String>(&String::from("id"))
.expect("Unable to find link id");
let node_from = attrs
.get::<String>(&String::from("node_from"))
.expect("Unable to find link node_from");
let node_to = attrs
.get::<String>(&String::from("node_to"))
.expect("Unable to find link node_to");
let port_from = attrs
.get::<String>(&String::from("port_from"))
.expect("Unable to find link port_from");
let port_to = attrs
.get::<String>(&String::from("port_to"))
.expect("Unable to find link port_to");
let active: &String = attrs
.get::<String>(&String::from("active"))
.expect("Unable to find link state");
current_link = Some(NodeLink {
id: id.parse::<u32>().unwrap(),
node_from: node_from.parse::<u32>().unwrap(),
node_to: node_to.parse::<u32>().unwrap(),
port_from: port_from.parse::<u32>().unwrap(),
port_to: port_to.parse::<u32>().unwrap(),
active: active.parse::<bool>().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 {

View file

@ -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<super::port::Port> {
pub fn ports(&self) -> Ref<HashMap<u32, Port>> {
let private = imp::Node::from_instance(self);
private.ports.borrow_mut().get(&id).cloned()
private.ports.borrow()
}
pub fn port(&self, id: &u32) -> Option<super::port::Port> {
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()
}
}

View file

@ -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()
}
}