2021-11-25 15:13:40 +00:00
|
|
|
// graphview.rs
|
|
|
|
//
|
|
|
|
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
|
|
|
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
|
|
|
|
//
|
2022-02-09 10:28:27 +00:00
|
|
|
// This file is part of GraphManager
|
2021-11-25 15:13:40 +00:00
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
2022-01-06 13:57:17 +00:00
|
|
|
extern crate xml;
|
|
|
|
use xml::reader::EventReader;
|
|
|
|
use xml::reader::XmlEvent as XMLREvent;
|
|
|
|
use xml::writer::EmitterConfig;
|
|
|
|
use xml::writer::XmlEvent as XMLWEvent;
|
|
|
|
|
2022-01-18 15:00:37 +00:00
|
|
|
use super::{
|
2022-01-18 17:02:18 +00:00
|
|
|
link::*,
|
2022-01-18 15:00:37 +00:00
|
|
|
node::{Node, NodeType},
|
|
|
|
port::{Port, PortDirection, PortPresence},
|
2022-01-21 15:44:12 +00:00
|
|
|
property::PropertyExt,
|
2022-01-18 17:02:18 +00:00
|
|
|
selection::SelectionExt,
|
2022-01-18 15:00:37 +00:00
|
|
|
};
|
2022-01-18 17:02:18 +00:00
|
|
|
|
2021-12-07 11:20:21 +00:00
|
|
|
use once_cell::sync::Lazy;
|
2022-01-26 15:06:42 +00:00
|
|
|
use std::io::Cursor;
|
2021-11-25 15:13:40 +00:00
|
|
|
|
|
|
|
use gtk::{
|
2021-12-07 11:20:21 +00:00
|
|
|
gdk::{BUTTON_PRIMARY, BUTTON_SECONDARY},
|
2022-01-17 13:39:36 +00:00
|
|
|
glib::{self, clone, subclass::Signal},
|
2021-11-25 15:13:40 +00:00
|
|
|
graphene, gsk,
|
|
|
|
prelude::*,
|
|
|
|
subclass::prelude::*,
|
|
|
|
};
|
2022-01-13 16:26:06 +00:00
|
|
|
use log::{debug, error, info, trace, warn};
|
2021-11-25 15:13:40 +00:00
|
|
|
|
|
|
|
use std::cell::RefMut;
|
2022-01-11 12:43:25 +00:00
|
|
|
use std::{cmp::Ordering, collections::HashMap};
|
2021-11-25 15:13:40 +00:00
|
|
|
|
|
|
|
static GRAPHVIEW_STYLE: &str = include_str!("graphview.css");
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
cell::{Cell, RefCell},
|
|
|
|
rc::Rc,
|
|
|
|
};
|
|
|
|
|
|
|
|
use log::warn;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct GraphView {
|
2022-01-12 17:42:29 +00:00
|
|
|
pub(super) id: Cell<u32>,
|
2021-11-25 15:13:40 +00:00
|
|
|
pub(super) nodes: RefCell<HashMap<u32, Node>>,
|
2021-12-13 17:15:48 +00:00
|
|
|
pub(super) links: RefCell<HashMap<u32, Link>>,
|
2021-11-25 15:13:40 +00:00
|
|
|
pub(super) current_node_id: Cell<u32>,
|
|
|
|
pub(super) current_port_id: Cell<u32>,
|
|
|
|
pub(super) current_link_id: Cell<u32>,
|
|
|
|
pub(super) port_selected: RefCell<Option<Port>>,
|
2022-01-26 13:21:17 +00:00
|
|
|
pub(super) mouse_position: Cell<(f64, f64)>,
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
impl ObjectSubclass for GraphView {
|
|
|
|
const NAME: &'static str = "GraphView";
|
|
|
|
type Type = super::GraphView;
|
|
|
|
type ParentType = gtk::Widget;
|
|
|
|
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
|
|
|
// The layout manager determines how child widgets are laid out.
|
|
|
|
klass.set_layout_manager_type::<gtk::FixedLayout>();
|
|
|
|
klass.set_css_name("graphview");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectImpl for GraphView {
|
|
|
|
fn constructed(&self, obj: &Self::Type) {
|
|
|
|
self.parent_constructed(obj);
|
|
|
|
|
|
|
|
let drag_state = Rc::new(RefCell::new(None));
|
|
|
|
let drag_controller = gtk::GestureDrag::new();
|
|
|
|
|
|
|
|
drag_controller.connect_drag_begin(
|
|
|
|
clone!(@strong drag_state => move |drag_controller, x, y| {
|
|
|
|
let mut drag_state = drag_state.borrow_mut();
|
|
|
|
let widget = drag_controller
|
|
|
|
.widget()
|
|
|
|
.dynamic_cast::<Self::Type>()
|
|
|
|
.expect("drag-begin event is not on the GraphView");
|
|
|
|
// pick() should at least return the widget itself.
|
|
|
|
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("drag-begin pick() did not return a widget");
|
|
|
|
*drag_state = if target.ancestor(Port::static_type()).is_some() {
|
|
|
|
// The user targeted a port, so the dragging should be handled by the Port
|
|
|
|
// component instead of here.
|
|
|
|
None
|
|
|
|
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
|
|
|
// The user targeted a Node without targeting a specific Port.
|
|
|
|
// Drag the Node around the screen.
|
|
|
|
if let Some((x, y)) = widget.node_position(&target) {
|
|
|
|
Some((target, x, y))
|
|
|
|
} else {
|
|
|
|
error!("Failed to obtain position of dragged node, drag aborted.");
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
));
|
|
|
|
drag_controller.connect_drag_update(
|
|
|
|
clone!(@strong drag_state => move |drag_controller, x, y| {
|
|
|
|
let widget = drag_controller
|
|
|
|
.widget()
|
|
|
|
.dynamic_cast::<Self::Type>()
|
|
|
|
.expect("drag-update event is not on the GraphView");
|
|
|
|
let drag_state = drag_state.borrow();
|
|
|
|
if let Some((ref node, x1, y1)) = *drag_state {
|
|
|
|
widget.move_node(node, x1 + x as f32, y1 + y as f32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
),
|
|
|
|
);
|
2022-01-12 17:42:29 +00:00
|
|
|
drag_controller.connect_drag_end(
|
|
|
|
clone!(@strong drag_state => move |drag_controller, _x, _y| {
|
|
|
|
let widget = drag_controller
|
|
|
|
.widget()
|
|
|
|
.dynamic_cast::<Self::Type>()
|
|
|
|
.expect("drag-end event is not on the GraphView");
|
|
|
|
widget.graph_updated();
|
|
|
|
}
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2021-11-25 15:13:40 +00:00
|
|
|
obj.add_controller(&drag_controller);
|
|
|
|
|
|
|
|
let gesture = gtk::GestureClick::new();
|
2021-12-07 11:20:21 +00:00
|
|
|
gesture.set_button(0);
|
|
|
|
gesture.connect_pressed(
|
|
|
|
clone!(@weak obj, @weak drag_controller => move |gesture, _n_press, x, y| {
|
|
|
|
if gesture.current_button() == BUTTON_SECONDARY {
|
2022-01-17 13:39:36 +00:00
|
|
|
let widget = drag_controller.widget()
|
2021-11-25 15:13:40 +00:00
|
|
|
.dynamic_cast::<Self::Type>()
|
|
|
|
.expect("click event is not on the GraphView");
|
2021-12-07 11:20:21 +00:00
|
|
|
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("port pick() did not return a widget");
|
|
|
|
if let Some(target) = target.ancestor(Port::static_type()) {
|
|
|
|
let port = target.dynamic_cast::<Port>().expect("click event is not on the Port");
|
|
|
|
let node = port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
2022-01-17 13:39:36 +00:00
|
|
|
obj.emit_by_name::<()>("port-right-clicked", &[&port.id(), &node.id(), &graphene::Point::new(x as f32,y as f32)]);
|
2021-12-07 11:20:21 +00:00
|
|
|
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
|
|
|
let node = target.dynamic_cast::<Node>().expect("click event is not on the Node");
|
2021-12-13 17:15:48 +00:00
|
|
|
widget.unselect_all();
|
|
|
|
node.set_selected(true);
|
2022-01-17 13:39:36 +00:00
|
|
|
obj.emit_by_name::<()>("node-right-clicked", &[&node.id(), &graphene::Point::new(x as f32,y as f32)]);
|
2022-01-03 16:01:56 +00:00
|
|
|
} else {
|
|
|
|
widget.unselect_all();
|
2022-01-17 13:39:36 +00:00
|
|
|
obj.emit_by_name::<()>("graph-right-clicked", &[&graphene::Point::new(x as f32,y as f32)]);
|
2021-12-07 11:20:21 +00:00
|
|
|
}
|
2021-12-13 17:15:48 +00:00
|
|
|
} else if gesture.current_button() == BUTTON_PRIMARY {
|
2022-01-17 13:39:36 +00:00
|
|
|
let widget = drag_controller.widget()
|
2021-12-13 17:15:48 +00:00
|
|
|
.dynamic_cast::<Self::Type>()
|
|
|
|
.expect("click event is not on the GraphView");
|
|
|
|
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("port pick() did not return a widget");
|
|
|
|
if let Some(target) = target.ancestor(Port::static_type()) {
|
|
|
|
let port = target.dynamic_cast::<Port>().expect("click event is not on the Node");
|
|
|
|
widget.unselect_all();
|
|
|
|
port.toggle_selected();
|
|
|
|
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
|
|
|
let node = target.dynamic_cast::<Node>().expect("click event is not on the Node");
|
|
|
|
widget.unselect_all();
|
|
|
|
node.toggle_selected();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
widget.point_on_link(&graphene::Point::new(x.floor() as f32,y.floor() as f32));
|
|
|
|
}
|
2021-12-07 11:20:21 +00:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
gesture.connect_released(clone!(@weak gesture, @weak drag_controller => move |_gesture, _n_press, x, y| {
|
|
|
|
if gesture.current_button() == BUTTON_PRIMARY {
|
|
|
|
let widget = drag_controller
|
|
|
|
.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()) {
|
2022-01-26 10:49:04 +00:00
|
|
|
let port_clicked = target.dynamic_cast::<Port>().expect("click event is not on the Port");
|
|
|
|
if widget.port_is_linked(port_clicked.id()).is_none() {
|
2021-12-07 11:20:21 +00:00
|
|
|
let selected_port = widget.selected_port().to_owned();
|
|
|
|
if let Some(mut port_from) = selected_port {
|
2022-01-26 10:49:04 +00:00
|
|
|
debug!("Port {} is clicked at {}:{}", port_clicked.id(), x, y);
|
|
|
|
let mut port_to = port_clicked;
|
2021-12-07 11:20:21 +00:00
|
|
|
if widget.ports_compatible(&port_to) {
|
|
|
|
let mut node_from = port_from.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
|
|
|
let mut node_to = port_to.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
2022-01-26 10:49:04 +00:00
|
|
|
info!("add link from port {} to {} ", port_from.id(), port_to.id());
|
2021-12-10 14:26:18 +00:00
|
|
|
if port_to.direction() == PortDirection::Output {
|
2022-01-13 16:26:06 +00:00
|
|
|
debug!("swap ports and nodes to create the link");
|
2021-12-07 11:20:21 +00:00
|
|
|
std::mem::swap(&mut node_from, &mut node_to);
|
|
|
|
std::mem::swap(&mut port_from, &mut port_to);
|
|
|
|
}
|
2022-01-26 15:05:59 +00:00
|
|
|
widget.add_link(widget.create_link(
|
2021-12-13 17:15:48 +00:00
|
|
|
node_from.id(),
|
|
|
|
node_to.id(),
|
|
|
|
port_from.id(),
|
|
|
|
port_to.id(),
|
|
|
|
true,
|
|
|
|
));
|
2021-12-03 16:30:13 +00:00
|
|
|
}
|
2021-12-07 11:20:21 +00:00
|
|
|
widget.set_selected_port(None);
|
|
|
|
} else {
|
2022-01-26 10:49:04 +00:00
|
|
|
info!("add selected port id {}", port_clicked.id());
|
|
|
|
widget.set_selected_port(Some(&port_clicked));
|
2021-12-03 16:30:13 +00:00
|
|
|
}
|
2022-01-26 13:21:17 +00:00
|
|
|
} else {
|
|
|
|
// click to a linked port
|
|
|
|
widget.set_selected_port(None);
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
2022-01-26 13:21:17 +00:00
|
|
|
} else {
|
|
|
|
// Click to something else than a port
|
|
|
|
widget.set_selected_port(None);
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
obj.add_controller(&gesture);
|
2022-01-26 13:21:17 +00:00
|
|
|
|
|
|
|
let event_motion = gtk::EventControllerMotion::new();
|
|
|
|
event_motion.connect_motion(glib::clone!(@weak obj => move |_e, x, y| {
|
|
|
|
let graphview = obj;
|
|
|
|
if graphview.selected_port().is_some() {
|
|
|
|
graphview.set_mouse_position(x,y);
|
|
|
|
graphview.queue_draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
}));
|
|
|
|
obj.add_controller(&event_motion);
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn dispose(&self, _obj: &Self::Type) {
|
|
|
|
self.nodes
|
|
|
|
.borrow()
|
|
|
|
.values()
|
|
|
|
.for_each(|node| node.unparent())
|
|
|
|
}
|
2021-12-07 11:20:21 +00:00
|
|
|
|
|
|
|
fn signals() -> &'static [Signal] {
|
|
|
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
|
|
|
vec![
|
|
|
|
Signal::builder(
|
|
|
|
"port-right-clicked",
|
|
|
|
&[
|
|
|
|
u32::static_type().into(),
|
|
|
|
u32::static_type().into(),
|
|
|
|
graphene::Point::static_type().into(),
|
|
|
|
],
|
|
|
|
<()>::static_type().into(),
|
|
|
|
)
|
|
|
|
.build(),
|
|
|
|
Signal::builder(
|
|
|
|
"node-right-clicked",
|
|
|
|
&[
|
|
|
|
u32::static_type().into(),
|
|
|
|
graphene::Point::static_type().into(),
|
|
|
|
],
|
|
|
|
<()>::static_type().into(),
|
|
|
|
)
|
|
|
|
.build(),
|
2022-01-03 16:01:56 +00:00
|
|
|
Signal::builder(
|
|
|
|
"graph-right-clicked",
|
|
|
|
&[graphene::Point::static_type().into()],
|
|
|
|
<()>::static_type().into(),
|
|
|
|
)
|
|
|
|
.build(),
|
2022-01-12 17:42:29 +00:00
|
|
|
Signal::builder(
|
|
|
|
"graph-updated",
|
|
|
|
// returns graph ID
|
|
|
|
&[u32::static_type().into()],
|
|
|
|
<()>::static_type().into(),
|
|
|
|
)
|
|
|
|
.build(),
|
2022-01-20 10:51:54 +00:00
|
|
|
Signal::builder(
|
|
|
|
"node-added",
|
|
|
|
// returns graph ID and Node ID
|
|
|
|
&[u32::static_type().into(), u32::static_type().into()],
|
|
|
|
<()>::static_type().into(),
|
|
|
|
)
|
|
|
|
.build(),
|
2022-01-21 16:50:14 +00:00
|
|
|
Signal::builder(
|
|
|
|
"port-added",
|
|
|
|
// returns graph ID, Node ID, Port ID
|
|
|
|
&[
|
|
|
|
u32::static_type().into(),
|
|
|
|
u32::static_type().into(),
|
|
|
|
u32::static_type().into(),
|
|
|
|
],
|
|
|
|
<()>::static_type().into(),
|
|
|
|
)
|
|
|
|
.build(),
|
2021-12-07 11:20:21 +00:00
|
|
|
]
|
|
|
|
});
|
|
|
|
SIGNALS.as_ref()
|
|
|
|
}
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl WidgetImpl for GraphView {
|
2022-01-26 13:21:17 +00:00
|
|
|
fn snapshot(&self, _widget: &Self::Type, snapshot: >k::Snapshot) {
|
2021-11-25 15:13:40 +00:00
|
|
|
/* FIXME: A lot of hardcoded values in here.
|
|
|
|
Try to use relative units (em) and colours from the theme as much as possible. */
|
|
|
|
|
|
|
|
// Draw all children
|
|
|
|
self.nodes
|
|
|
|
.borrow()
|
|
|
|
.values()
|
|
|
|
.for_each(|node| self.instance().snapshot_child(node, snapshot));
|
|
|
|
|
2022-01-06 13:57:17 +00:00
|
|
|
for link in self.links.borrow().values() {
|
|
|
|
if let Some((from_x, from_y, to_x, to_y)) = self.link_coordinates(link) {
|
2022-01-26 13:21:17 +00:00
|
|
|
self.draw_link(
|
|
|
|
snapshot,
|
|
|
|
link.active,
|
|
|
|
link.selected(),
|
|
|
|
link.thickness as f64,
|
|
|
|
&graphene::Point::new(from_x as f32, from_y as f32),
|
|
|
|
&graphene::Point::new(to_x as f32, to_y as f32),
|
|
|
|
);
|
2021-11-25 15:13:40 +00:00
|
|
|
} else {
|
2022-01-26 13:21:17 +00:00
|
|
|
warn!("Could not get link coordinates: {:?}", link);
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-26 13:21:17 +00:00
|
|
|
|
|
|
|
if self.port_selected.borrow().is_some() {
|
|
|
|
let port = self.port_selected.borrow();
|
|
|
|
let port = port.as_ref().unwrap();
|
|
|
|
let node = port
|
|
|
|
.ancestor(Node::static_type())
|
|
|
|
.expect("Unable to reach parent")
|
|
|
|
.dynamic_cast::<Node>()
|
|
|
|
.expect("Unable to cast to Node");
|
|
|
|
let (from_x, from_y) = self.link_from_coordinates(node.id(), port.id());
|
|
|
|
let (to_x, to_y) = self.mouse_position.get();
|
|
|
|
self.draw_link(
|
|
|
|
snapshot,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
2.0,
|
|
|
|
&graphene::Point::new(from_x as f32, from_y as f32),
|
|
|
|
&graphene::Point::new(to_x as f32, to_y as f32),
|
|
|
|
);
|
|
|
|
}
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GraphView {
|
2022-01-26 13:21:17 +00:00
|
|
|
fn link_from_coordinates(&self, node_from: u32, port_from: u32) -> (f64, f64) {
|
2021-11-25 15:13:40 +00:00
|
|
|
let nodes = self.nodes.borrow();
|
|
|
|
|
2022-01-26 10:49:04 +00:00
|
|
|
let from_node = nodes
|
2022-01-26 13:21:17 +00:00
|
|
|
.get(&node_from)
|
|
|
|
.unwrap_or_else(|| panic!("Unable to get node from {}", node_from));
|
2022-01-21 16:50:14 +00:00
|
|
|
|
2022-01-26 10:49:04 +00:00
|
|
|
let from_port = from_node
|
2022-01-26 13:21:17 +00:00
|
|
|
.port(port_from)
|
|
|
|
.unwrap_or_else(|| panic!("Unable to get port from {}", port_from));
|
|
|
|
let (mut from_x, mut from_y, fw, fh) = (
|
2022-01-17 13:39:36 +00:00
|
|
|
from_port.allocation().x(),
|
|
|
|
from_port.allocation().y(),
|
|
|
|
from_port.allocation().width(),
|
|
|
|
from_port.allocation().height(),
|
|
|
|
);
|
|
|
|
let (fnx, fny) = (from_node.allocation().x(), from_node.allocation().y());
|
2021-11-25 15:13:40 +00:00
|
|
|
|
2021-12-13 17:15:48 +00:00
|
|
|
if let Some((port_x, port_y)) = from_port.translate_coordinates(from_node, 0.0, 0.0) {
|
2022-01-26 13:21:17 +00:00
|
|
|
from_x = fnx + fw + port_x as i32;
|
|
|
|
from_y = fny + (fh / 2) + port_y as i32;
|
2021-12-13 17:15:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-26 13:21:17 +00:00
|
|
|
(from_x as f64, from_y as f64)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn link_to_coordinates(&self, node_to: u32, port_to: u32) -> (f64, f64) {
|
|
|
|
let nodes = self.nodes.borrow();
|
|
|
|
|
|
|
|
let to_node = nodes
|
|
|
|
.get(&node_to)
|
|
|
|
.unwrap_or_else(|| panic!("Unable to get node to {}", node_to));
|
|
|
|
let to_port = to_node
|
|
|
|
.port(port_to)
|
|
|
|
.unwrap_or_else(|| panic!("Unable to get port to {}", port_to));
|
|
|
|
let (mut to_x, mut to_y, th) = (
|
2022-01-17 13:39:36 +00:00
|
|
|
to_port.allocation().x(),
|
|
|
|
to_port.allocation().y(),
|
|
|
|
to_port.allocation().height(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let (tnx, tny) = (to_node.allocation().x(), to_node.allocation().y());
|
|
|
|
|
2021-12-13 17:15:48 +00:00
|
|
|
if let Some((port_x, port_y)) = to_port.translate_coordinates(to_node, 0.0, 0.0) {
|
2022-01-26 13:21:17 +00:00
|
|
|
to_x += tnx + port_x as i32;
|
|
|
|
to_y = tny + (th / 2) + port_y as i32;
|
2021-12-13 17:15:48 +00:00
|
|
|
}
|
2022-01-13 16:26:06 +00:00
|
|
|
//trace!("{} {} -> {} {}", fx, fy, tx, ty);
|
2022-01-26 13:21:17 +00:00
|
|
|
(to_x.into(), to_y.into())
|
|
|
|
}
|
|
|
|
/// Retrieves coordinates for the drawn link to start at and to end at.
|
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
/// `Some((from_x, from_y, to_x, to_y))` if all objects the links refers to exist as widgets.
|
|
|
|
pub fn link_coordinates(&self, link: &Link) -> Option<(f64, f64, f64, f64)> {
|
|
|
|
let (from_x, from_y) = self.link_from_coordinates(link.node_from, link.port_from);
|
|
|
|
let (to_x, to_y) = self.link_to_coordinates(link.node_to, link.port_to);
|
|
|
|
Some((from_x, from_y, to_x, to_y))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_link(
|
|
|
|
&self,
|
|
|
|
snapshot: >k::Snapshot,
|
|
|
|
active: bool,
|
|
|
|
selected: bool,
|
|
|
|
thickness: f64,
|
|
|
|
point_from: &graphene::Point,
|
|
|
|
point_to: &graphene::Point,
|
|
|
|
) {
|
|
|
|
let alloc = self.instance().allocation();
|
|
|
|
|
|
|
|
let link_cr = snapshot.append_cairo(&graphene::Rect::new(
|
|
|
|
0.0,
|
|
|
|
0.0,
|
|
|
|
alloc.width() as f32,
|
|
|
|
alloc.height() as f32,
|
|
|
|
));
|
|
|
|
link_cr.set_line_width(thickness);
|
|
|
|
// Use dashed line for inactive links, full line otherwise.
|
|
|
|
if active {
|
|
|
|
link_cr.set_dash(&[], 0.0);
|
|
|
|
} else {
|
|
|
|
link_cr.set_dash(&[10.0, 5.0], 0.0);
|
|
|
|
}
|
|
|
|
if selected {
|
|
|
|
link_cr.set_source_rgb(1.0, 0.18, 0.18);
|
|
|
|
} else {
|
|
|
|
link_cr.set_source_rgb(0.0, 0.0, 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
link_cr.move_to(point_from.x() as f64, point_from.y() as f64);
|
|
|
|
link_cr.line_to(point_to.x() as f64, point_to.y() as f64);
|
|
|
|
link_cr.set_line_width(2.0);
|
|
|
|
|
|
|
|
if let Err(e) = link_cr.stroke() {
|
|
|
|
warn!("Failed to draw graphview links: {}", e);
|
|
|
|
};
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
pub struct GraphView(ObjectSubclass<imp::GraphView>)
|
|
|
|
@extends gtk::Widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GraphView {
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Create a new graphview
|
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
/// Graphview object
|
2021-11-25 15:13:40 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
// Load CSS from the STYLE variable.
|
|
|
|
let provider = gtk::CssProvider::new();
|
|
|
|
provider.load_from_data(GRAPHVIEW_STYLE.as_bytes());
|
|
|
|
gtk::StyleContext::add_provider_for_display(
|
|
|
|
>k::gdk::Display::default().expect("Error initializing gtk css provider."),
|
|
|
|
&provider,
|
|
|
|
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
|
|
);
|
|
|
|
glib::Object::new(&[]).expect("Failed to create GraphView")
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
|
|
|
|
/// Set graphview id
|
|
|
|
///
|
2022-01-12 17:42:29 +00:00
|
|
|
pub fn set_id(&self, id: u32) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private.id.set(id)
|
|
|
|
}
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Retrives the graphview id
|
|
|
|
///
|
2022-01-26 13:40:22 +00:00
|
|
|
pub fn id(&self) -> u32 {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private.id.get()
|
|
|
|
}
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Clear the graphview
|
|
|
|
///
|
|
|
|
pub fn clear(&self) {
|
|
|
|
self.remove_all_nodes();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Node
|
|
|
|
|
|
|
|
/// Create a new node with a new id
|
|
|
|
///
|
|
|
|
pub fn create_node(&self, name: &str, node_type: NodeType) -> Node {
|
|
|
|
let id = self.next_node_id();
|
|
|
|
self.create_node_with_id(id, name, node_type)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new node and add it to the graphview with input/output port number.
|
|
|
|
///
|
|
|
|
pub fn create_node_with_port(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
node_type: NodeType,
|
|
|
|
input: u32,
|
|
|
|
output: u32,
|
|
|
|
) -> Node {
|
|
|
|
let mut node = self.create_node(name, node_type);
|
|
|
|
|
|
|
|
let _i = 0;
|
|
|
|
for _i in 0..input {
|
|
|
|
let port = self.create_port("in", PortDirection::Input, PortPresence::Always);
|
|
|
|
self.add_port_to_node(&mut node, port);
|
|
|
|
}
|
|
|
|
let _i = 0;
|
|
|
|
for _i in 0..output {
|
|
|
|
let port = self.create_port("out", PortDirection::Output, PortPresence::Always);
|
|
|
|
self.add_port_to_node(&mut node, port);
|
|
|
|
}
|
|
|
|
node
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Add node to the graphview without port
|
|
|
|
///
|
|
|
|
pub fn add_node(&self, node: Node) {
|
2021-11-25 15:13:40 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
node.set_parent(self);
|
|
|
|
|
|
|
|
// Place widgets in colums of 3, growing down
|
2022-01-06 13:57:17 +00:00
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2021-11-25 15:13:40 +00:00
|
|
|
let y = private
|
|
|
|
.nodes
|
|
|
|
.borrow()
|
|
|
|
.values()
|
|
|
|
.filter_map(|node| {
|
|
|
|
// Map nodes to locations, discard nodes without location
|
|
|
|
self.node_position(&node.clone().upcast())
|
|
|
|
})
|
|
|
|
.filter(|(x2, _)| {
|
|
|
|
// Only look for other nodes that have a similar x coordinate
|
|
|
|
(x - x2).abs() < 50.0
|
|
|
|
})
|
|
|
|
.max_by(|y1, y2| {
|
|
|
|
// Get max in column
|
|
|
|
y1.partial_cmp(y2).unwrap_or(Ordering::Equal)
|
|
|
|
})
|
|
|
|
.map_or(20_f32, |(_x, y)| y + 100.0);
|
|
|
|
|
|
|
|
self.move_node(&node.clone().upcast(), x, y);
|
2022-01-20 10:51:54 +00:00
|
|
|
let node_id = node.id();
|
2022-01-18 17:02:18 +00:00
|
|
|
private.nodes.borrow_mut().insert(node.id(), node);
|
2022-01-20 10:51:54 +00:00
|
|
|
self.emit_by_name::<()>("node-added", &[&private.id.get(), &node_id]);
|
2022-01-18 17:02:18 +00:00
|
|
|
self.graph_updated();
|
|
|
|
}
|
2022-01-06 13:57:17 +00:00
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Remove node from the graphview
|
|
|
|
///
|
2021-11-25 15:13:40 +00:00
|
|
|
pub fn remove_node(&self, id: u32) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
let mut nodes = private.nodes.borrow_mut();
|
|
|
|
if let Some(node) = nodes.remove(&id) {
|
2022-01-26 10:49:04 +00:00
|
|
|
while let Some(link_id) = self.node_is_linked(node.id()) {
|
|
|
|
info!("Remove link id {}", link_id);
|
|
|
|
private.links.borrow_mut().remove(&link_id);
|
2021-12-08 13:15:22 +00:00
|
|
|
}
|
2021-11-25 15:13:40 +00:00
|
|
|
node.unparent();
|
|
|
|
} else {
|
|
|
|
warn!("Tried to remove non-existant node (id={}) from graph", id);
|
|
|
|
}
|
|
|
|
self.queue_draw();
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
|
|
|
|
/// Select all nodes according to the NodeType
|
|
|
|
///
|
|
|
|
/// Returns a vector of nodes
|
2021-12-03 16:30:13 +00:00
|
|
|
pub fn all_nodes(&self, node_type: NodeType) -> Vec<Node> {
|
2021-11-25 15:13:40 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
2022-01-06 13:57:17 +00:00
|
|
|
let nodes = private.nodes.borrow();
|
2021-12-03 16:30:13 +00:00
|
|
|
let nodes_list: Vec<_> = nodes
|
|
|
|
.iter()
|
|
|
|
.filter(|(_, node)| {
|
|
|
|
*node.node_type().unwrap() == node_type || node_type == NodeType::All
|
|
|
|
})
|
|
|
|
.map(|(_, node)| node.clone())
|
|
|
|
.collect();
|
2021-11-25 15:13:40 +00:00
|
|
|
nodes_list
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
|
2022-01-18 09:50:03 +00:00
|
|
|
/// Get the node with the specified node id inside the graphview.
|
|
|
|
///
|
|
|
|
/// Returns `None` if the node is not in the graphview.
|
|
|
|
pub fn node(&self, id: u32) -> Option<Node> {
|
2021-12-08 13:15:22 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
2022-01-18 09:50:03 +00:00
|
|
|
private.nodes.borrow().get(&id).cloned()
|
2021-12-08 13:15:22 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Remove all the nodes from the graphview
|
|
|
|
///
|
2021-11-25 15:13:40 +00:00
|
|
|
pub fn remove_all_nodes(&self) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
2021-12-03 16:30:13 +00:00
|
|
|
let nodes_list = self.all_nodes(NodeType::All);
|
2021-11-25 15:13:40 +00:00
|
|
|
for node in nodes_list {
|
|
|
|
self.remove_node(node.id());
|
|
|
|
}
|
|
|
|
private.current_node_id.set(0);
|
|
|
|
private.current_port_id.set(0);
|
|
|
|
private.current_link_id.set(0);
|
|
|
|
self.queue_draw();
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Check if the node is linked
|
|
|
|
///
|
|
|
|
/// Returns Some(link id) or `None` if the node is not linked.
|
2022-01-06 13:57:17 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Get the position of the specified node inside the graphview.
|
2022-01-26 15:05:59 +00:00
|
|
|
///
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Returns `None` if the node is not in the graphview.
|
|
|
|
pub(super) fn node_position(&self, node: >k::Widget) -> Option<(f32, f32)> {
|
|
|
|
let layout_manager = self
|
|
|
|
.layout_manager()
|
|
|
|
.expect("Failed to get layout manager")
|
|
|
|
.dynamic_cast::<gtk::FixedLayout>()
|
|
|
|
.expect("Failed to cast to FixedLayout");
|
|
|
|
|
|
|
|
let node = layout_manager
|
|
|
|
.layout_child(node)
|
|
|
|
.dynamic_cast::<gtk::FixedLayoutChild>()
|
|
|
|
.expect("Could not cast to FixedLayoutChild");
|
|
|
|
let transform = node
|
|
|
|
.transform()
|
|
|
|
.expect("Failed to obtain transform from layout child");
|
|
|
|
Some(transform.to_translate())
|
2022-01-21 16:50:14 +00:00
|
|
|
}
|
2022-01-26 15:06:42 +00:00
|
|
|
|
|
|
|
// Port
|
|
|
|
|
2022-01-26 15:05:59 +00:00
|
|
|
/// Create a new port with a new id
|
2022-01-18 10:26:03 +00:00
|
|
|
///
|
2022-01-21 16:50:14 +00:00
|
|
|
pub fn create_port(
|
2021-12-10 14:26:18 +00:00
|
|
|
&self,
|
2022-01-21 16:50:14 +00:00
|
|
|
name: &str,
|
|
|
|
direction: PortDirection,
|
|
|
|
presence: PortPresence,
|
|
|
|
) -> Port {
|
|
|
|
let id = self.next_port_id();
|
|
|
|
info!("Create a port with port id {}", id);
|
|
|
|
|
|
|
|
self.create_port_with_id(id, name, direction, presence)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add the port with id from node with id.
|
|
|
|
///
|
|
|
|
pub fn add_port_to_node(&self, node: &mut Node, port: Port) {
|
2021-11-25 15:13:40 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
2022-01-21 16:50:14 +00:00
|
|
|
let port_id = port.id();
|
|
|
|
node.add_port(port);
|
|
|
|
|
|
|
|
self.emit_by_name::<()>("port-added", &[&private.id.get(), &node.id(), &port_id]);
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Check if the port with id from node with id can be removed.
|
|
|
|
///
|
|
|
|
/// Return true if the port presence is not always.
|
2022-01-18 15:00:37 +00:00
|
|
|
pub fn can_remove_port(&self, node_id: u32, port_id: u32) -> bool {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
let nodes = private.nodes.borrow();
|
|
|
|
if let Some(node) = nodes.get(&node_id) {
|
|
|
|
return node.can_remove_port(port_id);
|
|
|
|
}
|
|
|
|
warn!("Unable to find a node with the id {}", node_id);
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2022-01-18 10:26:03 +00:00
|
|
|
/// Remove the port with id from node with id.
|
|
|
|
///
|
2022-01-18 15:00:37 +00:00
|
|
|
pub fn remove_port(&self, node_id: u32, port_id: u32) {
|
2021-11-25 15:13:40 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
let nodes = private.nodes.borrow();
|
|
|
|
if let Some(node) = nodes.get(&node_id) {
|
2022-01-18 10:26:03 +00:00
|
|
|
if let Some(link_id) = self.port_is_linked(port_id) {
|
|
|
|
self.remove_link(link_id);
|
|
|
|
}
|
|
|
|
node.remove_port(port_id);
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
|
2022-01-18 10:26:03 +00:00
|
|
|
/// Check if the port is linked
|
|
|
|
///
|
|
|
|
/// Returns Some(link id) or `None` if the port is not linked.
|
|
|
|
pub fn port_is_linked(&self, port_id: u32) -> Option<u32> {
|
2022-01-06 13:57:17 +00:00
|
|
|
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 {
|
2022-01-18 10:26:03 +00:00
|
|
|
return Some(*key);
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
// Link
|
2022-01-26 15:06:42 +00:00
|
|
|
|
2022-01-26 15:05:59 +00:00
|
|
|
/// Create a new link with a new id
|
|
|
|
///
|
|
|
|
pub fn create_link(
|
|
|
|
&self,
|
|
|
|
node_from_id: u32,
|
|
|
|
node_to_id: u32,
|
|
|
|
port_from_id: u32,
|
|
|
|
port_to_id: u32,
|
|
|
|
active: bool,
|
|
|
|
) -> Link {
|
|
|
|
self.create_link_with_id(
|
|
|
|
self.next_link_id(),
|
|
|
|
node_from_id,
|
|
|
|
node_to_id,
|
|
|
|
port_from_id,
|
|
|
|
port_to_id,
|
|
|
|
active,
|
|
|
|
)
|
|
|
|
}
|
2022-01-26 15:06:42 +00:00
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Add a link to the graphView
|
|
|
|
///
|
2021-12-13 17:15:48 +00:00
|
|
|
pub fn add_link(&self, link: Link) {
|
2021-11-25 15:13:40 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
if !self.link_exists(&link) {
|
2022-01-06 13:57:17 +00:00
|
|
|
private.links.borrow_mut().insert(link.id, link);
|
2022-01-12 17:42:29 +00:00
|
|
|
self.graph_updated();
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-26 15:05:59 +00:00
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Set the link state with ink id and link state (boolean)
|
|
|
|
///
|
2021-11-25 15:13:40 +00:00
|
|
|
pub fn set_link_state(&self, link_id: u32, active: bool) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
2022-01-06 13:57:17 +00:00
|
|
|
if let Some(link) = private.links.borrow_mut().get_mut(&link_id) {
|
|
|
|
link.active = active;
|
2021-11-25 15:13:40 +00:00
|
|
|
self.queue_draw();
|
|
|
|
} else {
|
|
|
|
warn!("Link state changed on unknown link (id={})", link_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Select all nodes according to the NodeType
|
2022-01-18 17:02:18 +00:00
|
|
|
///
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Returns a vector of links
|
|
|
|
pub fn all_links(&self, link_state: bool) -> Vec<Link> {
|
2021-11-25 15:13:40 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
2022-01-26 15:06:42 +00:00
|
|
|
let links = private.links.borrow();
|
|
|
|
let links_list: Vec<_> = links
|
|
|
|
.iter()
|
|
|
|
.filter(|(_, link)| link.active == link_state)
|
|
|
|
.map(|(_, node)| node.clone())
|
|
|
|
.collect();
|
|
|
|
links_list
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Retrieves the node/port id connected to the input port id
|
|
|
|
///
|
2021-12-03 16:30:13 +00:00
|
|
|
pub fn port_connected_to(&self, port_id: u32) -> Option<(u32, u32)> {
|
2022-01-18 17:02:18 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
for (_id, link) in private.links.borrow().iter() {
|
2021-12-03 16:30:13 +00:00
|
|
|
if port_id == link.port_from {
|
|
|
|
return Some((link.port_to, link.node_to));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Delete the selected element (link, node, port)
|
|
|
|
///
|
2021-12-13 17:15:48 +00:00
|
|
|
pub fn delete_selected(&self) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
let mut link_id = None;
|
|
|
|
let mut node_id = None;
|
|
|
|
for link in private.links.borrow_mut().values() {
|
|
|
|
if link.selected() {
|
|
|
|
link_id = Some(link.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for node in private.nodes.borrow_mut().values() {
|
|
|
|
if node.selected() {
|
|
|
|
node_id = Some(node.id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(id) = link_id {
|
|
|
|
self.remove_link(id);
|
|
|
|
}
|
|
|
|
if let Some(id) = node_id {
|
|
|
|
self.remove_node(id);
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
|
|
|
|
self.graph_updated();
|
2021-12-13 17:15:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
/// Render the graph with XML format in a buffer
|
2022-01-18 17:02:18 +00:00
|
|
|
///
|
2022-01-26 15:06:42 +00:00
|
|
|
pub fn render_xml(&self) -> anyhow::Result<Vec<u8>> {
|
2022-01-12 17:42:29 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
2022-01-26 15:06:42 +00:00
|
|
|
|
|
|
|
let mut buffer = Vec::new();
|
2022-01-06 13:57:17 +00:00
|
|
|
let mut writer = EmitterConfig::new()
|
|
|
|
.perform_indent(true)
|
2022-01-26 15:06:42 +00:00
|
|
|
.create_writer(&mut buffer);
|
2022-01-06 13:57:17 +00:00
|
|
|
|
2022-01-12 17:42:29 +00:00
|
|
|
writer
|
|
|
|
.write(XMLWEvent::start_element("Graph").attr("id", &private.id.get().to_string()))?;
|
2022-01-06 13:57:17 +00:00
|
|
|
|
|
|
|
//Get the nodes
|
2021-12-03 16:30:13 +00:00
|
|
|
let nodes = self.all_nodes(NodeType::All);
|
2022-01-06 13:57:17 +00:00
|
|
|
for node in nodes {
|
|
|
|
writer.write(
|
|
|
|
XMLWEvent::start_element("Node")
|
|
|
|
.attr("name", &node.name())
|
|
|
|
.attr("id", &node.id().to_string())
|
2022-01-14 17:14:38 +00:00
|
|
|
.attr("type", &node.node_type().unwrap().to_string())
|
|
|
|
.attr("pos_x", &node.position().0.to_string())
|
|
|
|
.attr("pos_y", &node.position().1.to_string()),
|
2022-01-06 13:57:17 +00:00
|
|
|
)?;
|
|
|
|
for port in node.ports().values() {
|
|
|
|
writer.write(
|
|
|
|
XMLWEvent::start_element("Port")
|
|
|
|
.attr("name", &port.name())
|
|
|
|
.attr("id", &port.id().to_string())
|
2022-01-18 15:00:37 +00:00
|
|
|
.attr("direction", &port.direction().to_string())
|
|
|
|
.attr("presence", &port.presence().to_string()),
|
2022-01-06 13:57:17 +00:00
|
|
|
)?;
|
2022-01-21 15:44:12 +00:00
|
|
|
for (name, value) in port.properties().iter() {
|
|
|
|
writer.write(
|
|
|
|
XMLWEvent::start_element("Property")
|
|
|
|
.attr("name", name)
|
|
|
|
.attr("value", value),
|
|
|
|
)?;
|
|
|
|
writer.write(XMLWEvent::end_element())?;
|
|
|
|
}
|
2022-01-06 13:57:17 +00:00
|
|
|
writer.write(XMLWEvent::end_element())?;
|
|
|
|
}
|
2021-12-08 13:15:22 +00:00
|
|
|
|
|
|
|
for (name, value) in node.properties().iter() {
|
|
|
|
writer.write(
|
|
|
|
XMLWEvent::start_element("Property")
|
|
|
|
.attr("name", name)
|
|
|
|
.attr("value", value),
|
|
|
|
)?;
|
|
|
|
writer.write(XMLWEvent::end_element())?;
|
|
|
|
}
|
2022-01-06 13:57:17 +00:00
|
|
|
writer.write(XMLWEvent::end_element())?;
|
|
|
|
}
|
|
|
|
//Get the link and write it.
|
2022-01-18 17:02:18 +00:00
|
|
|
for (_id, link) in private.links.borrow().iter() {
|
2022-01-06 13:57:17 +00:00
|
|
|
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())?;
|
2022-01-26 15:06:42 +00:00
|
|
|
Ok(buffer)
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
/// Load the graph from a file with XML format
|
|
|
|
///
|
2022-01-26 15:06:42 +00:00
|
|
|
pub fn load_from_xml(&self, buffer: Vec<u8>) -> anyhow::Result<()> {
|
|
|
|
self.clear();
|
|
|
|
let file = Cursor::new(buffer);
|
2022-01-06 13:57:17 +00:00
|
|
|
let parser = EventReader::new(file);
|
|
|
|
|
|
|
|
let mut current_node: Option<Node> = None;
|
2022-01-21 16:50:14 +00:00
|
|
|
let mut current_node_properties: HashMap<String, String> = HashMap::new();
|
2022-01-06 13:57:17 +00:00
|
|
|
let mut current_port: Option<Port> = None;
|
2022-01-21 16:50:14 +00:00
|
|
|
let mut current_port_properties: HashMap<String, String> = HashMap::new();
|
2021-12-13 17:15:48 +00:00
|
|
|
let mut current_link: Option<Link> = None;
|
2022-01-06 13:57:17 +00:00
|
|
|
for e in parser {
|
|
|
|
match e {
|
|
|
|
Ok(XMLREvent::StartElement {
|
|
|
|
ref name,
|
|
|
|
ref attributes,
|
|
|
|
..
|
|
|
|
}) => {
|
2022-01-13 16:26:06 +00:00
|
|
|
trace!("Found XLM element={}", name);
|
2022-01-06 13:57:17 +00:00
|
|
|
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" => {
|
2022-01-13 16:26:06 +00:00
|
|
|
trace!("New graph detected");
|
2022-01-12 17:42:29 +00:00
|
|
|
if let Some(id) = attrs.get::<String>(&String::from("id")) {
|
|
|
|
self.set_id(id.parse::<u32>().expect("id should be an u32"));
|
|
|
|
}
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
"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");
|
2022-01-14 17:14:38 +00:00
|
|
|
let default_value = String::from("0");
|
|
|
|
let pos_x: &String = attrs
|
|
|
|
.get::<String>(&String::from("pos_x"))
|
|
|
|
.unwrap_or(&default_value);
|
|
|
|
let pos_y: &String = attrs
|
|
|
|
.get::<String>(&String::from("pos_y"))
|
|
|
|
.unwrap_or(&default_value);
|
2022-01-21 16:50:14 +00:00
|
|
|
let node = self.create_node_with_id(
|
2022-01-06 13:57:17 +00:00
|
|
|
id.parse::<u32>().unwrap(),
|
|
|
|
name,
|
|
|
|
NodeType::from_str(node_type.as_str()),
|
2022-01-14 17:14:38 +00:00
|
|
|
);
|
|
|
|
node.set_position(
|
|
|
|
pos_x.parse::<f32>().unwrap(),
|
|
|
|
pos_y.parse::<f32>().unwrap(),
|
|
|
|
);
|
|
|
|
current_node = Some(node);
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
2021-12-08 13:15:22 +00:00
|
|
|
"Property" => {
|
|
|
|
let name = attrs
|
|
|
|
.get::<String>(&String::from("name"))
|
|
|
|
.expect("Unable to find property name");
|
|
|
|
let value: &String = attrs
|
|
|
|
.get::<String>(&String::from("value"))
|
|
|
|
.expect("Unable to find property value");
|
2022-01-21 16:50:14 +00:00
|
|
|
if current_port.is_some() {
|
|
|
|
current_port_properties.insert(name.to_string(), value.to_string());
|
|
|
|
} else if current_node.is_some() {
|
|
|
|
info!("add property to node {}={}", name, value);
|
|
|
|
current_node_properties.insert(name.to_string(), value.to_string());
|
2022-01-21 15:44:12 +00:00
|
|
|
}
|
2021-12-08 13:15:22 +00:00
|
|
|
}
|
2022-01-06 13:57:17 +00:00
|
|
|
"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");
|
2022-01-18 15:00:37 +00:00
|
|
|
let default_value = PortPresence::Always.to_string();
|
|
|
|
let presence: &String = attrs
|
|
|
|
.get::<String>(&String::from("presence"))
|
|
|
|
.unwrap_or(&default_value);
|
2022-01-21 16:50:14 +00:00
|
|
|
current_port = Some(self.create_port_with_id(
|
2022-01-06 13:57:17 +00:00
|
|
|
id.parse::<u32>().unwrap(),
|
|
|
|
name,
|
|
|
|
PortDirection::from_str(direction),
|
2022-01-18 15:00:37 +00:00
|
|
|
PortPresence::from_str(presence),
|
2022-01-06 13:57:17 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
"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");
|
2022-01-26 15:05:59 +00:00
|
|
|
current_link = Some(self.create_link_with_id(
|
2021-12-13 17:15:48 +00:00
|
|
|
id.parse::<u32>().unwrap(),
|
|
|
|
node_from.parse::<u32>().unwrap(),
|
|
|
|
node_to.parse::<u32>().unwrap(),
|
|
|
|
port_from.parse::<u32>().unwrap(),
|
|
|
|
port_to.parse::<u32>().unwrap(),
|
|
|
|
active.parse::<bool>().unwrap(),
|
|
|
|
));
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
2022-01-13 16:26:06 +00:00
|
|
|
_ => warn!("name unknown: {}", name),
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(XMLREvent::EndElement { name }) => {
|
2022-01-13 16:26:06 +00:00
|
|
|
trace!("closing {}", name);
|
2022-01-06 13:57:17 +00:00
|
|
|
match name.to_string().as_str() {
|
|
|
|
"Graph" => {
|
2022-01-13 16:26:06 +00:00
|
|
|
trace!("Graph ended with success");
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
"Node" => {
|
|
|
|
if let Some(node) = current_node {
|
|
|
|
let id = node.id();
|
2022-01-14 17:14:38 +00:00
|
|
|
let position = node.position();
|
2022-01-21 16:50:14 +00:00
|
|
|
node.update_properties(¤t_node_properties);
|
|
|
|
current_node_properties.clear();
|
2022-01-18 17:02:18 +00:00
|
|
|
self.add_node(node);
|
2022-01-18 09:50:03 +00:00
|
|
|
if let Some(node) = self.node(id) {
|
2022-01-14 17:14:38 +00:00
|
|
|
self.move_node(&node.upcast(), position.0, position.1);
|
2022-01-12 17:42:29 +00:00
|
|
|
}
|
2022-01-21 16:50:14 +00:00
|
|
|
|
2021-12-13 17:15:48 +00:00
|
|
|
self.update_current_node_id(id);
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
current_node = None;
|
|
|
|
}
|
2021-12-08 13:15:22 +00:00
|
|
|
"Property" => {}
|
2022-01-06 13:57:17 +00:00
|
|
|
"Port" => {
|
|
|
|
if let Some(port) = current_port {
|
2022-01-21 16:50:14 +00:00
|
|
|
if let Some(mut node) = current_node.clone() {
|
|
|
|
let id = port.id();
|
|
|
|
port.update_properties(¤t_port_properties);
|
|
|
|
self.add_port_to_node(&mut node, port);
|
|
|
|
current_port_properties.clear();
|
|
|
|
self.update_current_port_id(id);
|
|
|
|
}
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
2022-01-21 16:50:14 +00:00
|
|
|
|
2022-01-06 13:57:17 +00:00
|
|
|
current_port = None;
|
|
|
|
}
|
|
|
|
"Link" => {
|
|
|
|
if let Some(link) = current_link {
|
2021-12-13 17:15:48 +00:00
|
|
|
let id = link.id;
|
2022-01-06 13:57:17 +00:00
|
|
|
self.add_link(link);
|
2021-12-13 17:15:48 +00:00
|
|
|
self.update_current_link_id(id);
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
current_link = None;
|
|
|
|
}
|
2022-01-13 16:26:06 +00:00
|
|
|
_ => warn!("name unknown: {}", name),
|
2022-01-06 13:57:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
2022-01-13 16:26:06 +00:00
|
|
|
error!("Error: {}", e);
|
2022-01-06 13:57:17 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
|
|
|
|
//Private
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
fn create_node_with_id(&self, id: u32, name: &str, node_type: NodeType) -> Node {
|
|
|
|
Node::new(id, name, node_type)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_port_with_id(
|
|
|
|
&self,
|
|
|
|
id: u32,
|
|
|
|
name: &str,
|
|
|
|
direction: PortDirection,
|
|
|
|
presence: PortPresence,
|
|
|
|
) -> Port {
|
|
|
|
Port::new(id, name, direction, presence)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_link_with_id(
|
|
|
|
&self,
|
|
|
|
link_id: u32,
|
|
|
|
node_from_id: u32,
|
|
|
|
node_to_id: u32,
|
|
|
|
port_from_id: u32,
|
|
|
|
port_to_id: u32,
|
|
|
|
active: bool,
|
|
|
|
) -> Link {
|
|
|
|
Link::new(
|
|
|
|
link_id,
|
|
|
|
node_from_id,
|
|
|
|
node_to_id,
|
|
|
|
port_from_id,
|
|
|
|
port_to_id,
|
|
|
|
active,
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
fn remove_link(&self, id: u32) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
let mut links = private.links.borrow_mut();
|
|
|
|
links.remove(&id);
|
|
|
|
|
|
|
|
self.queue_draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_current_link_id(&self, link_id: u32) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
if link_id > private.current_link_id.get() {
|
|
|
|
private.current_link_id.set(link_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn link_exists(&self, new_link: &Link) -> bool {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
warn!("link already existing");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn move_node(&self, widget: >k::Widget, x: f32, y: f32) {
|
|
|
|
let node = widget
|
|
|
|
.clone()
|
|
|
|
.dynamic_cast::<Node>()
|
|
|
|
.expect("Unable to convert to Node");
|
|
|
|
node.set_position(x, y);
|
|
|
|
let layout_manager = self
|
|
|
|
.layout_manager()
|
|
|
|
.expect("Failed to get layout manager")
|
|
|
|
.dynamic_cast::<gtk::FixedLayout>()
|
|
|
|
.expect("Failed to cast to FixedLayout");
|
|
|
|
|
|
|
|
let transform = gsk::Transform::new()
|
|
|
|
// Nodes should not be able to be dragged out of the view, so we use `max(coordinate, 0.0)` to prevent that.
|
|
|
|
.translate(&graphene::Point::new(f32::max(x, 0.0), f32::max(y, 0.0)))
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
layout_manager
|
|
|
|
.layout_child(widget)
|
|
|
|
.dynamic_cast::<gtk::FixedLayoutChild>()
|
|
|
|
.expect("Could not cast to FixedLayoutChild")
|
|
|
|
.set_transform(&transform);
|
|
|
|
|
|
|
|
// FIXME: If links become proper widgets,
|
|
|
|
// we don't need to redraw the full graph everytime.
|
|
|
|
self.queue_draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unselect_nodes(&self) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
for node in private.nodes.borrow_mut().values() {
|
|
|
|
node.set_selected(false);
|
|
|
|
node.unselect_all_ports();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_current_node_id(&self, node_id: u32) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
if node_id > private.current_node_id.get() {
|
|
|
|
private.current_node_id.set(node_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unselect_links(&self) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
for link in private.links.borrow_mut().values() {
|
|
|
|
link.set_selected(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unselect_all(&self) {
|
|
|
|
self.unselect_nodes();
|
|
|
|
self.unselect_links();
|
|
|
|
self.queue_draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn point_on_link(&self, point: &graphene::Point) -> Option<Link> {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
self.unselect_all();
|
|
|
|
for link in private.links.borrow_mut().values() {
|
|
|
|
if let Some((from_x, from_y, to_x, to_y)) = private.link_coordinates(link) {
|
|
|
|
let quad = graphene::Quad::new(
|
|
|
|
&graphene::Point::new(from_x as f32, from_y as f32 - link.thickness as f32),
|
|
|
|
&graphene::Point::new(to_x as f32, to_y as f32 - link.thickness as f32),
|
|
|
|
&graphene::Point::new(to_x as f32, to_y as f32 + link.thickness as f32),
|
|
|
|
&graphene::Point::new(from_x as f32, from_y as f32 + link.thickness as f32),
|
|
|
|
);
|
|
|
|
if quad.contains(point) {
|
|
|
|
link.toggle_selected();
|
|
|
|
self.queue_draw();
|
|
|
|
return Some(link.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.queue_draw();
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn graph_updated(&self) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
self.queue_draw();
|
|
|
|
self.emit_by_name::<()>("graph-updated", &[&private.id.get()]);
|
|
|
|
}
|
|
|
|
|
2022-01-26 15:06:42 +00:00
|
|
|
fn next_node_id(&self) -> u32 {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private
|
|
|
|
.current_node_id
|
|
|
|
.set(private.current_node_id.get() + 1);
|
|
|
|
private.current_node_id.get()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn next_port_id(&self) -> u32 {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private
|
|
|
|
.current_port_id
|
|
|
|
.set(private.current_port_id.get() + 1);
|
|
|
|
private.current_port_id.get()
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
fn next_link_id(&self) -> u32 {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private
|
|
|
|
.current_link_id
|
|
|
|
.set(private.current_link_id.get() + 1);
|
|
|
|
private.current_link_id.get()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_selected_port(&self, port: Option<&Port>) {
|
2022-01-26 13:21:17 +00:00
|
|
|
if port.is_some() {
|
|
|
|
self.unselect_all();
|
|
|
|
}
|
2022-01-18 17:02:18 +00:00
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
*private.port_selected.borrow_mut() = port.cloned();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn selected_port(&self) -> RefMut<Option<Port>> {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private.port_selected.borrow_mut()
|
|
|
|
}
|
|
|
|
|
2022-01-26 13:21:17 +00:00
|
|
|
fn set_mouse_position(&self, x: f64, y: f64) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
private.mouse_position.set((x, y));
|
|
|
|
}
|
|
|
|
|
2022-01-18 17:02:18 +00:00
|
|
|
fn ports_compatible(&self, to_port: &Port) -> bool {
|
|
|
|
let current_port = self.selected_port().to_owned();
|
|
|
|
if let Some(from_port) = current_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");
|
|
|
|
let res = from_port.id() != to_port.id()
|
|
|
|
&& from_port.direction() != to_port.direction()
|
|
|
|
&& from_node.id() != to_node.id();
|
|
|
|
if !res {
|
|
|
|
warn!("Unable add the following link");
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_current_port_id(&self, port_id: u32) {
|
|
|
|
let private = imp::GraphView::from_instance(self);
|
|
|
|
if port_id > private.current_port_id.get() {
|
|
|
|
private.current_port_id.set(port_id);
|
|
|
|
}
|
|
|
|
}
|
2021-11-25 15:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for GraphView {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|