node.vala 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. /**
  22. * Flowgraphs for Gtk
  23. */
  24. namespace GtkFlow {
  25. /**
  26. * Represents an element that can generate, process or receive data
  27. * This is done by adding Sources and Sinks to it. The inner logic of
  28. * The node can be represented towards the user as arbitrary Gtk widget.
  29. *
  30. * IMPORTANT: DO NOT USE THIS CLASS IN APPLICATION CODE
  31. * this node is solely public to mitigate a problem
  32. * generating gir bindings
  33. */
  34. public class Node : Gtk.Container {
  35. // Determines the space between the title and the first dock (y-axis)
  36. // as well as the space between the title and the close-button if any (x-axis)
  37. public GFlow.Node gnode {public get; private set; default = null;}
  38. public Gtk.Widget? title {public get; private set; default = null;}
  39. public NodeView? node_view {get; internal set; default=null;}
  40. internal bool selected {get; set; default=false;}
  41. internal Gdk.RGBA? highlight_color {get; set; default=null;}
  42. private NodeRenderer? _node_renderer = null;
  43. public NodeRenderer? node_renderer {
  44. get {return this._node_renderer;}
  45. set {
  46. if (this._node_renderer != null) {
  47. this._node_renderer.size_changed.disconnect(this.size_changed_callback);
  48. this._node_renderer.child_redraw.disconnect(this.child_redraw_callback);
  49. }
  50. this._node_renderer = value;
  51. this._node_renderer.size_changed.connect(this.size_changed_callback);
  52. this._node_renderer.child_redraw.connect(this.child_redraw_callback);
  53. }
  54. }
  55. private void size_changed_callback() {
  56. this.render();
  57. }
  58. public Cairo.Context? current_cairo_ctx {get; set; default=null;}
  59. private void child_redraw_callback(Gtk.Widget w) {
  60. if (this.current_cairo_ctx == null) {
  61. warning("Child Redraw: No context to draw on");
  62. return;
  63. }
  64. Cairo.Context? cr = this.current_cairo_ctx;
  65. Gtk.Allocation node_alloc, child_alloc;
  66. this.get_allocation(out node_alloc);
  67. w.get_allocation(out child_alloc);
  68. cr.save();
  69. cr.translate(node_alloc.x + child_alloc.x, node_alloc.y + child_alloc.y);
  70. w.draw(cr);
  71. cr.restore();
  72. }
  73. private List<DockRenderer?> dock_renderers = new List<DockRenderer?>();
  74. private List<Gtk.Widget> childlist = new List<Gtk.Widget>();
  75. private HashTable<weak Gtk.Widget, ulong> childlist_alloc_handles = new HashTable<weak Gtk.Widget, ulong>(direct_hash, direct_equal);
  76. public Node (GFlow.Node n) {
  77. this.gnode = n;
  78. this.node_renderer = new DefaultNodeRenderer(this);
  79. foreach (GFlow.Dock d in this.gnode.get_sources())
  80. this.register_dock(d);
  81. foreach (GFlow.Dock d in this.gnode.get_sinks())
  82. this.register_dock(d);
  83. this.gnode.source_added.connect((s)=>{this.register_dock(s);});
  84. this.gnode.sink_added.connect((s)=>{this.register_dock(s);});
  85. this.gnode.source_removed.connect((s)=>{this.unregister_dock(s);});
  86. this.gnode.sink_removed.connect((s)=>{this.unregister_dock(s);});
  87. this.node_renderer.update_name_layout(this.gnode.name);
  88. this.gnode.notify["name"].connect(()=>{
  89. this.node_renderer.update_name_layout(this.gnode.name);
  90. this.recalculate_size();
  91. });
  92. this.gnode.notify["highlight-color"].connect(()=>{
  93. message("changed color of node highlight");
  94. this.render();
  95. });
  96. this.notify["node-view"].connect(()=>{
  97. this.render_all();
  98. });
  99. this.set_border_width(this.node_renderer.resize_handle_size);
  100. this.show_all();
  101. }
  102. public void render() {
  103. if (this.node_view != null) {
  104. this.node_view.queue_draw();
  105. }
  106. }
  107. public void render_all() {
  108. if (this.node_renderer != null)
  109. this.node_renderer.update_name_layout(this.gnode.name);
  110. foreach(DockRenderer dr in this.dock_renderers)
  111. if (dr != null) {
  112. bool show_types = this.node_view != null ? this.node_view.show_types : false;
  113. dr.update_name_layout(show_types);
  114. this.recalculate_size();
  115. }
  116. }
  117. private void register_dock(GFlow.Dock d) {
  118. DefaultDockRenderer dr = new DefaultDockRenderer(this, d);
  119. dr.size_changed.connect(this.render);
  120. d.notify["name"].connect(()=>{
  121. dr.update_name_layout(this.node_view != null ? this.node_view.show_types : false);
  122. this.recalculate_size();
  123. });
  124. d.notify["typename"].connect(()=>{
  125. dr.update_name_layout(this.node_view != null ? this.node_view.show_types : false);
  126. this.recalculate_size();
  127. });
  128. d.changed.connect(this.render);
  129. this.dock_renderers.append(dr);
  130. dr.update_name_layout(this.node_view != null ? this.node_view.show_types : false);
  131. if (this.get_realized())
  132. this.render();
  133. }
  134. public DockRenderer? get_dock_renderer(GFlow.Dock d) {
  135. foreach (DockRenderer dock_renderer in this.dock_renderers) {
  136. if (dock_renderer.get_dock() == d)
  137. return dock_renderer;
  138. }
  139. return null;
  140. }
  141. private void unregister_dock(GFlow.Dock d) {
  142. DockRenderer? dr = this.get_dock_renderer(d);
  143. if (dr != null) {
  144. this.dock_renderers.remove_link(
  145. this.dock_renderers.find_custom(dr, (x,y)=>{return (int)(x!=y);})
  146. );
  147. //m.free();
  148. }
  149. if (this.get_realized())
  150. this.render();
  151. }
  152. public Node.with_child(GFlow.Node n, Gtk.Widget c, Gtk.Widget? title=null) {
  153. this(n);
  154. this.add(c);
  155. if (title != null) {
  156. this.title = title;
  157. this.add(title);
  158. title.show();
  159. }
  160. this.realize.connect(()=>{
  161. // Bad fix for the wigglebug: Find more elegant solution than this.
  162. // key is that the children will only render correctly if the node they
  163. // reside in has already been realized. redrawing without changing
  164. // the position, however, will result in NOP.
  165. var p = this.get_position();
  166. this.set_position(p.x+1,p.y);
  167. this.set_position(p.x,p.y);
  168. this.node_view.queue_draw();
  169. });
  170. this.show_all();
  171. }
  172. public void on_child_size_allocate(Gtk.Allocation _) {
  173. Gtk.Allocation alloc;
  174. this.get_allocation(out alloc);
  175. this.size_allocate(alloc);
  176. this.node_view.queue_draw();
  177. }
  178. public new void size_allocate(Gtk.Allocation alloc) {
  179. if (!this.get_visible() && !this.is_toplevel())
  180. return;
  181. int mw = (int)this.node_renderer.get_min_width(
  182. this.dock_renderers, this.childlist,
  183. (int)this.get_border_width(),
  184. this.title
  185. );
  186. int mh = (int)this.node_renderer.get_min_height(
  187. this.dock_renderers, this.childlist,
  188. (int)this.get_border_width()
  189. );
  190. if (alloc.width < mw)
  191. alloc.width = mw;
  192. if (alloc.height < mh)
  193. alloc.height = mh;
  194. ((Gtk.Widget)this).size_allocate(alloc);
  195. }
  196. public void set_position(int x, int y) {
  197. Gtk.Allocation alloc;
  198. this.get_allocation(out alloc);
  199. alloc.x = x;
  200. alloc.y = y;
  201. this.size_allocate(alloc);
  202. this.node_view.queue_draw();
  203. }
  204. public unowned Gdk.Point get_position() {
  205. Gtk.Allocation alloc;
  206. this.get_allocation(out alloc);
  207. Gdk.Point pos = Gdk.Point();
  208. pos.x = alloc.x;
  209. pos.y = alloc.y;
  210. return pos;
  211. }
  212. public unowned List<DockRenderer> get_dock_renderers() {
  213. return this.dock_renderers;
  214. }
  215. public override void forall_internal(bool include_internals, Gtk.Callback c) {
  216. foreach (Gtk.Widget child in this.childlist) {
  217. c(child);
  218. }
  219. }
  220. public override void add(Gtk.Widget w) {
  221. w.set_parent(this);
  222. this.childlist.append(w);
  223. ulong handle = w.size_allocate.connect(this.on_child_size_allocate);
  224. this.childlist_alloc_handles.set(w, handle);
  225. if (this.get_realized())
  226. this.render();
  227. }
  228. public override void remove(Gtk.Widget w) {
  229. w.unparent();
  230. ulong handle = this.childlist_alloc_handles.get(w);
  231. w.disconnect(handle);
  232. this.childlist_alloc_handles.remove(w);
  233. this.childlist.remove_link(
  234. this.childlist.find_custom(w, (x,y)=>{return (int)(x!=y);})
  235. );
  236. }
  237. public unowned List<Gtk.Widget> get_childlist() {
  238. return this.childlist;
  239. }
  240. public new void set_border_width(uint border_width) {
  241. int nr = this.node_renderer.resize_handle_size;
  242. if (border_width < nr) {
  243. warning("Cannot set border width smaller than %d", nr);
  244. return;
  245. }
  246. base.set_border_width(border_width);
  247. if (this.get_realized())
  248. this.render();
  249. }
  250. public override void realize() {
  251. this.recalculate_size();
  252. Gtk.Allocation alloc;
  253. this.get_allocation(out alloc);
  254. var attr = Gdk.WindowAttr();
  255. attr.window_type = Gdk.WindowType.CHILD;
  256. attr.x = alloc.x;
  257. attr.y = alloc.y;
  258. attr.width = alloc.width;
  259. attr.height = alloc.height;
  260. attr.visual = this.get_visual();
  261. attr.event_mask = this.get_events();
  262. Gdk.WindowAttributesType mask = Gdk.WindowAttributesType.X
  263. | Gdk.WindowAttributesType.X
  264. | Gdk.WindowAttributesType.VISUAL;
  265. var window = new Gdk.Window(this.get_parent_window(), attr, mask);
  266. this.set_window(window);
  267. this.register_window(window);
  268. this.set_realized(true);
  269. this.render();
  270. }
  271. /**
  272. * Checks if the node needs to be resized in order to fill the minimum
  273. * size requirements
  274. */
  275. public void recalculate_size() {
  276. Gtk.Allocation alloc;
  277. this.get_allocation(out alloc);
  278. uint mw = this.node_renderer.get_min_width(
  279. this.dock_renderers, this.childlist,
  280. (int) this.get_border_width(),
  281. this.title
  282. );
  283. uint mh = this.node_renderer.get_min_height(
  284. this.dock_renderers, this.childlist,
  285. (int) this.get_border_width()
  286. );
  287. if (mw > alloc.width)
  288. alloc.width = (int)mw;
  289. if (mh > alloc.height)
  290. alloc.height = (int)mh;
  291. this.size_allocate(alloc);
  292. }
  293. }
  294. public struct NodeProperties {
  295. bool editable;
  296. bool deletable;
  297. bool resizable;
  298. bool selected;
  299. }
  300. }