Merge branch 'dab_save_position' into 'main'

graphview: save position as a hidden property

See merge request dabrain34/GstPipelineStudio!3
This commit is contained in:
Stéphane Cerveau 2022-01-13 08:55:13 +00:00
commit 15a785f821
6 changed files with 152 additions and 39 deletions

View file

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

View file

@ -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::<u32>().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<String, String> = 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<String, String>) {
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(())
}

View file

@ -56,6 +56,7 @@ mod imp {
#[derive(Default)]
pub struct GraphView {
pub(super) id: Cell<u32>,
pub(super) nodes: RefCell<HashMap<u32, Node>>,
pub(super) links: RefCell<HashMap<u32, Link>>,
pub(super) current_node_id: Cell<u32>,
@ -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::<Self::Type>()
.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>(&String::from("id")) {
self.set_id(id.parse::<u32>().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::<f32>().unwrap();
}
if let Some(value) = node.property("_pos_y") {
pos_y = value.parse::<f32>().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;

View file

@ -71,6 +71,7 @@ mod imp {
pub(super) ports: RefCell<HashMap<u32, Port>>,
pub(super) num_ports_in: Cell<i32>,
pub(super) num_ports_out: Cell<i32>,
// Properties are differnet from GObject properties
pub(super) properties: RefCell<HashMap<String, String>>,
pub(super) selected: Cell<bool>,
}
@ -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<String, String>) {
for (key, value) in new_properties {
pub fn update_properties(&self, new_node_properties: &HashMap<String, String>) {
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<String> {
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());
}

View file

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

View file

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