node.vala 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /********************************************************************
  2. # Copyright 2014-2022 Daniel 'grindhold' Brendle
  3. #
  4. # This file is part of libgtkflow.
  5. #
  6. # libgtkflow is free software: you can redistribute it and/or
  7. # modify it under the terms of the GNU Lesser General Public License
  8. # as published by the Free Software Foundation, either
  9. # version 3 of the License, or (at your option) any later
  10. # version.
  11. #
  12. # libgtkflow is distributed in the hope that it will be
  13. # useful, but WITHOUT ANY WARRANTY; without even the implied
  14. # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  15. # PURPOSE. See the GNU Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with libgtkflow.
  19. # If not, see http://www.gnu.org/licenses/.
  20. *********************************************************************/
  21. namespace GtkFlow {
  22. public delegate Gtk.Widget NodeTitleFactory(Node node);
  23. public errordomain NodeError {
  24. TITLE_ALREADY_INITIALIZED;
  25. }
  26. /**
  27. * Defines an object that can be added to a Nodeview
  28. *
  29. * Implement this if you want custom nodes that have their own
  30. * drawing routines and special behaviour
  31. */
  32. public interface NodeRenderer : Gtk.Widget {
  33. /**
  34. * The {@link GFlow.Node} that this Node represents
  35. */
  36. public abstract GFlow.Node n {get; protected set;}
  37. /**
  38. * Expresses wheter this node is marked via rubberband selection
  39. */
  40. public abstract bool marked {get; internal set;}
  41. /**
  42. * Returns a {@link Dock} if the given {@link GFlow.Dock} resides
  43. * in this node.
  44. */
  45. public abstract Dock? retrieve_dock(GFlow.Dock d);
  46. /**
  47. * Returns the value of this node's margin
  48. */
  49. public abstract int get_margin();
  50. /**
  51. * Click offset: x coordinate
  52. *
  53. * Holds the offset-position relative to the origin of the
  54. * node at which this node has been clicked the last time.
  55. */
  56. public abstract double click_offset_x {get; protected set; default=0;}
  57. /**
  58. * Click offset: y coordinate
  59. *
  60. * Holds the offset-position relative to the origin of the
  61. * node at which this node has been clicked the last time.
  62. */
  63. public abstract double click_offset_y {get; protected set; default=0;}
  64. /**
  65. * Resize start width
  66. *
  67. * Hold the original width of the node when the last resize process
  68. * had been started
  69. */
  70. public abstract double resize_start_width {get; protected set; default=0;}
  71. /**
  72. * Resize start height
  73. *
  74. * Hold the original height of the node when the last resize process
  75. * had been started
  76. */
  77. public abstract double resize_start_height {get; protected set; default=0;}
  78. }
  79. public class NodeDockLabelWidgetFactory : Object {
  80. public GFlow.Node node {
  81. get;
  82. private set;
  83. }
  84. public NodeDockLabelWidgetFactory(GFlow.Node node) {
  85. this.node = node;
  86. }
  87. public virtual Gtk.Widget create_dock_label(GFlow.Dock dock) {
  88. var label = new Gtk.Label(dock.name);
  89. label.justify = Gtk.Justification.LEFT;
  90. label.hexpand = true;
  91. if (dock is GFlow.Source) {
  92. label.halign = Gtk.Align.END;
  93. } else {
  94. label.halign = Gtk.Align.START;
  95. }
  96. return label;
  97. }
  98. }
  99. /**
  100. * A Simple node representation
  101. *
  102. * The default {@link NodeRenderer} that comes with libgtkflow. Use this
  103. * To wrap your {@link GFlow.Node}s in order to add them to a {@link NodeView}
  104. */
  105. public class Node : Gtk.Widget, NodeRenderer {
  106. private static string CSS = "
  107. .gtkflow_node {
  108. background: rgba(0.6, 0.6, 0.6, 0.2);
  109. border-radius: 5px;
  110. border: 1px solid rgba(128, 128, 128, 0.8);
  111. box-shadow: 2px 2px 3px 3px rgba(153, 153, 153, 0.5);
  112. }
  113. .gtkflow_node_marked {
  114. background: rgba(0, 51, 128, 0.8);
  115. border-radius: 5px;
  116. border: 1px solid rgba(0, 51, 128, 0.8);
  117. box-shadow: 2px 2px 3px 3px rgba(0, 51, 153, 0.5);
  118. }
  119. ";
  120. private static Gtk.CssProvider css = new Gtk.CssProvider();
  121. private static bool initialized = false;
  122. private static void init() {
  123. if (Node.initialized) return;
  124. Node.css.load_from_data(Node.CSS.data);
  125. Node.initialized = true;
  126. }
  127. private bool title_initialized = false;
  128. construct {
  129. set_css_name("gtkflow_node");
  130. this.notify["marked"].connect(this.marked_changed);
  131. }
  132. private const int MARGIN_DEFAULT = 10;
  133. private Gtk.Grid pads_grid;
  134. private Gtk.Box node_box;
  135. private Gtk.GestureClick ctr_click;
  136. public GFlow.Node n {get; protected set;}
  137. private NodeDockLabelWidgetFactory dock_label_factory;
  138. /**
  139. * {@inheritDoc}
  140. */
  141. public bool marked {get; internal set;}
  142. public Gdk.RGBA? highlight_color {get; set; default=null;}
  143. /**
  144. * {@inheritDoc}
  145. */
  146. public double click_offset_x {get; protected set; default=0;}
  147. /**
  148. * {@inheritDoc}
  149. */
  150. public double click_offset_y {get; protected set; default=0;}
  151. /**
  152. * {@inheritDoc}
  153. */
  154. public double resize_start_width {get; protected set; default=0;}
  155. /**
  156. * {@inheritDoc}
  157. */
  158. public double resize_start_height {get; protected set; default=0;}
  159. private int n_docks = 0;
  160. private int margin = 0;
  161. ~Node() {
  162. this.pads_grid.unparent();
  163. this.node_box.unparent();
  164. }
  165. /**
  166. * Instantiate a new node
  167. *
  168. * You are required to pass a {@link GFlow.Node} to this constructor.
  169. */
  170. public Node(GFlow.Node n) {
  171. this.with_margin(n, MARGIN_DEFAULT, new NodeDockLabelWidgetFactory(n));
  172. var title_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 3);
  173. var title_label = new Gtk.Label("");
  174. title_label.set_markup ("<b>%s</b>".printf(n.name));
  175. title_label.hexpand = true;
  176. title_label.halign = Gtk.Align.START;
  177. title_box.append(title_label);
  178. var delete_icon = new Gtk.Image.from_icon_name("edit-delete");
  179. var delete_button = new Gtk.Button();
  180. delete_button.child = delete_icon;
  181. delete_button.has_frame = false;
  182. delete_button.clicked.connect(this.remove);
  183. title_box.append(delete_button);
  184. try {
  185. set_title(title_box);
  186. } catch (NodeError e) {
  187. message("Could not set title in node");
  188. }
  189. }
  190. public Node.with_margin(GFlow.Node n, int margin, NodeDockLabelWidgetFactory dock_label_factory) {
  191. Node.init();
  192. this.n = n;
  193. this.dock_label_factory = dock_label_factory;
  194. this.margin = margin;
  195. this.set_layout_manager(new Gtk.BinLayout());
  196. this.add_css_class("gtkflow_node");
  197. this.get_style_context().add_provider(Node.css,Gtk.STYLE_PROVIDER_PRIORITY_USER);
  198. this.node_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  199. this.node_box.set_parent(this);
  200. this.node_box.hexpand = true;
  201. this.node_box.vexpand = true;
  202. this.node_box.halign = Gtk.Align.FILL;
  203. this.node_box.valign = Gtk.Align.FILL;
  204. this.node_box.margin_top = this.margin;
  205. this.node_box.margin_bottom = this.margin;
  206. this.node_box.margin_start = this.margin;
  207. this.node_box.margin_end = this.margin;
  208. create_pads_grid();
  209. this.ctr_click = new Gtk.GestureClick();
  210. this.add_controller(this.ctr_click);
  211. this.ctr_click.pressed.connect(this.press_button);
  212. this.ctr_click.end.connect(this.release_button);
  213. var motion_controller = new Gtk.EventControllerMotion();
  214. motion_controller.motion.connect(this.hover_over);
  215. this.add_controller(motion_controller);
  216. }
  217. private void create_pads_grid() {
  218. this.pads_grid = new Gtk.Grid();
  219. this.pads_grid.column_homogeneous = false;
  220. this.pads_grid.column_spacing = 5;
  221. this.pads_grid.row_homogeneous = false;
  222. this.pads_grid.row_spacing = 5;
  223. node_box.append(this.pads_grid);
  224. foreach (GFlow.Source s in n.get_sources()) {
  225. this.source_added(s);
  226. }
  227. foreach (GFlow.Sink s in n.get_sinks()) {
  228. this.sink_added(s);
  229. }
  230. this.n.source_added.connect(this.source_added);
  231. this.n.source_removed.connect(this.source_removed);
  232. this.n.sink_added.connect(this.sink_added);
  233. this.n.sink_removed.connect(this.sink_removed);
  234. }
  235. /**
  236. * Retrieve a Dock-Widget from this node.
  237. *
  238. * Gives you the GtkFlow.Dock-object that corresponds to the given
  239. * GFlow.Dock. Returns null if the searched Dock is not associated
  240. * with any of the Dock-Widgets in this node.
  241. */
  242. public Dock? retrieve_dock (GFlow.Dock d) {
  243. var c = this.pads_grid.get_first_child();
  244. while (c != null) {
  245. if (!(c is Dock)) {
  246. c = c.get_next_sibling();
  247. continue;
  248. }
  249. var dw = (Dock)c;
  250. if (dw.d == d) return dw;
  251. c = c.get_next_sibling();
  252. }
  253. return null;
  254. }
  255. /**
  256. * {@inheritDoc}
  257. */
  258. public int get_margin() {
  259. return this.margin;
  260. }
  261. public void remove() {
  262. var nv = this.get_parent() as NodeView;
  263. nv.remove(this);
  264. }
  265. /**
  266. * Adds a child widget to this node
  267. */
  268. public void add_child(Gtk.Widget child) {
  269. this.node_box.append(child);
  270. }
  271. public void set_title(Gtk.Widget title) throws NodeError {
  272. if (!this.title_initialized) {
  273. this.pads_grid.attach(title, 0, 0, 3, 1);
  274. this.title_initialized = true;
  275. } else {
  276. throw new NodeError.TITLE_ALREADY_INITIALIZED("Title may only be initialized once");
  277. }
  278. }
  279. /**
  280. * Removes a child widget from this node
  281. */
  282. public void remove_child(Gtk.Widget child) {
  283. child.unparent();
  284. }
  285. protected override void dispose() {
  286. this.pads_grid.unparent();
  287. base.dispose();
  288. }
  289. /**
  290. * {@inheritDoc}
  291. */
  292. private void sink_added(GFlow.Sink s) {
  293. var dock = new Dock(s);
  294. var dock_label = dock_label_factory.create_dock_label(dock.d);
  295. this.pads_grid.attach(dock, 0, 1 + ++n_docks, 1, 1);
  296. this.pads_grid.attach(dock_label, 1, 1 + n_docks, 1, 1);
  297. }
  298. private void sink_removed(GFlow.Sink s) {
  299. var dock_widget = retrieve_dock(s);
  300. int column = -1;
  301. int row = -1;
  302. int width, height = 0;
  303. pads_grid.query_child(dock_widget, out column, out row, out width, out height);
  304. if (row != -1) {
  305. pads_grid.remove_row(row);
  306. }
  307. }
  308. private void source_added(GFlow.Source s) {
  309. var dock = new Dock(s);
  310. var dock_label = dock_label_factory.create_dock_label(dock.d);
  311. this.pads_grid.attach(dock, 2, 1 + ++n_docks, 1, 1);
  312. this.pads_grid.attach(dock_label, 1, 1 + n_docks, 1, 1);
  313. }
  314. private void source_removed(GFlow.Source s) {
  315. var dock_widget = retrieve_dock(s);
  316. int column = -1;
  317. int row = -1;
  318. int width, height = 0;
  319. pads_grid.query_child(dock_widget, out column, out row, out width, out height);
  320. if (row != -1) {
  321. pads_grid.remove_row(row);
  322. }
  323. }
  324. /**
  325. * Programmatically set a node's position
  326. */
  327. public void set_position(int x, int y) {
  328. var parent = this.get_parent();
  329. if (!(parent is NodeView)) {
  330. warning("Node is not a child of a NodeView");
  331. return;
  332. }
  333. var nodeview = ((NodeView)parent);
  334. var lc = (NodeViewLayoutChild)nodeview.layout_manager.get_layout_child(this);
  335. lc.x = x;
  336. lc.y = y;
  337. if (this.marked) {
  338. foreach (NodeRenderer n in nodeview.get_marked_nodes()) {
  339. if (n != this) continue;
  340. var mlc = (NodeViewLayoutChild) nodeview.layout_manager.get_layout_child(n);
  341. mlc.x -= x;
  342. mlc.y -= y;
  343. }
  344. }
  345. }
  346. private void press_button(int n_click, double x, double y) {
  347. var picked_widget = this.pick(x,y, Gtk.PickFlags.NON_TARGETABLE);
  348. bool do_processing = true;
  349. if (picked_widget is GtkFlow.Dock ) {
  350. do_processing = false;
  351. }
  352. if (!do_processing) return;
  353. Gdk.Rectangle resize_area = resize_area();
  354. var nv = this.get_parent() as NodeView;
  355. if (this.n.resizable && resize_area.contains_point((int)x,(int)y)) {
  356. nv.resize_node = this;
  357. this.resize_start_width = this.get_width();
  358. this.resize_start_height = this.get_height();
  359. } else {
  360. nv.move_node = this;
  361. }
  362. this.click_offset_x = x;
  363. this.click_offset_y = y;
  364. }
  365. private void hover_over(double x, double y) {
  366. if (!this.n.resizable) {
  367. return;
  368. }
  369. Gdk.Rectangle resize_area = resize_area();
  370. if (resize_area.contains_point((int)x,(int)y)) {
  371. this.set_cursor_from_name("nwse-resize");
  372. } else {
  373. this.set_cursor_from_name("default");
  374. }
  375. }
  376. private void release_button() {
  377. var nv = this.get_parent() as NodeView;
  378. nv.move_node = null;
  379. nv.resize_node = null;
  380. nv.queue_allocate();
  381. }
  382. /**
  383. * {@inheritDoc}
  384. */
  385. public new void set_parent(Gtk.Widget w) {
  386. if (!(w is NodeView)) {
  387. warning("Trying to add a GtkFlow.Node to something that is not a GtkFlow.NodeView!");
  388. return;
  389. }
  390. base.set_parent(w);
  391. }
  392. private void marked_changed() {
  393. if (this.marked) {
  394. this.add_css_class("gtkflow_node_marked");
  395. } else {
  396. this.remove_css_class("gtkflow_node_marked");
  397. }
  398. }
  399. private Gdk.Rectangle resize_area() {
  400. return {
  401. this.get_width() - 16,
  402. this.get_height() - 16,
  403. 16,
  404. 16
  405. };
  406. }
  407. }
  408. }