nodeview.vala 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. /********************************************************************
  2. # Copyright 2014-2019 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. /**
  23. * A Gtk Widget that shows nodes and their connections to the user
  24. * It also lets the user edit said connections.
  25. */
  26. public class NodeView : Gtk.Container {
  27. private List<Node> nodes = new List<Node>();
  28. // The node that is currently being dragged around
  29. private const int DRAG_THRESHOLD = 3;
  30. private Node? drag_node = null;
  31. private bool drag_threshold_fulfilled = false;
  32. // Coordinates where the drag started
  33. private double drag_start_x = 0;
  34. private double drag_start_y = 0;
  35. // Difference from the chosen drag-point to the
  36. // upper left corner of the drag_node
  37. private int drag_diff_x = 0;
  38. private int drag_diff_y = 0;
  39. // Remember if a closebutton was pressed
  40. private bool close_button_pressed = false;
  41. // Remember if we are resizing a node
  42. private Node? resize_node = null;
  43. private int resize_start_x = 0;
  44. private int resize_start_y = 0;
  45. // Remember positions of rubberband
  46. private Gtk.Allocation? rubber_alloc = null;
  47. private int rubber_start_x = 0;
  48. private int rubber_start_y = 0;
  49. // Remember the last dock the mouse hovered over, so we can unhighlight it
  50. private GFlow.Dock? hovered_dock = null;
  51. // The dock that we are targeting for dragging a new connector
  52. private GFlow.Dock? drag_dock = null;
  53. // The dock that we are targeting to drop a connector on
  54. private GFlow.Dock? drop_dock = null;
  55. // The connector that is being used to draw a non-established connection
  56. private Gtk.Allocation? temp_connector = null;
  57. /**
  58. * Connect to this signal if you wish to set custom colors for the
  59. * connectors depending on what values they transport. Whenever
  60. * the value of a connected {@link GFlow.Source} changes, this
  61. * signal will be emitted. Return the color you desire as hex-string
  62. * similar to those used in css without the preceding hash ('#').
  63. * Example: red would be "ff0000"
  64. */
  65. public virtual signal string color_calculation(GLib.Value v) {
  66. Gtk.StyleContext sc = this.get_style_context();
  67. Gdk.RGBA fg = sc.get_color(Gtk.StateFlags.NORMAL);
  68. return "%2x%2x%2x".printf(col_f2h(fg.red), col_f2h(fg.green), col_f2h(fg.blue));
  69. }
  70. /**
  71. * Determines whether docks should be rendered with type-indicators
  72. */
  73. public bool show_types {get; set; default=false;}
  74. /**
  75. * Determines whether the displayed Nodes can be edited by the user
  76. * e.g. alter their positions by dragging and dropping or drawing
  77. * new collections or erasing old ones
  78. */
  79. public bool editable {get; set; default=true;}
  80. /**
  81. * If this property is set to true, the nodeview will not perform
  82. * any check wheter newly created connections will result in cycles
  83. * in the graph. It's completely up to the application programmer
  84. * to make sure that the logic inside the nodes he uses avoids
  85. * endlessly backpropagated loops
  86. */
  87. public bool allow_recursion {get; set; default=false;}
  88. /**
  89. * Creates a new empty {@link NodeView}
  90. */
  91. public NodeView() {
  92. Object();
  93. this.set_size_request(100,100);
  94. this.draw.connect((cr)=>{ return this.do_draw(cr); });
  95. this.motion_notify_event.connect((e)=>{ return this.do_motion_notify_event(e); });
  96. this.button_press_event.connect((e)=>{ return this.do_button_press_event(e); });
  97. this.button_release_event.connect((e)=>{ return this.do_button_release_event(e); });
  98. this.notify["show-types"].connect(()=>{this.render_all();});
  99. }
  100. private void add_common(Node n) {
  101. if (this.nodes.index(n) == -1) {
  102. this.nodes.insert(n,0);
  103. n.node_view = this;
  104. }
  105. this.queue_draw();
  106. n.set_parent(this);
  107. }
  108. private void render_all() {
  109. foreach (Node n in this.nodes)
  110. n.render_all();
  111. this.queue_draw();
  112. }
  113. /**
  114. * This methods adds a {@link GFlow.Node} to this NodeView
  115. */
  116. public void add_node(GFlow.Node gn) {
  117. Node n = new Node(gn);
  118. n.set_allocation({1,1,0,0});
  119. this.add_common(n);
  120. }
  121. /**
  122. * This method adds a {@link GFlow.Node} to this nodeview and
  123. * assigns an arbitrary {@link Gtk.Widget} as its child.
  124. */
  125. public void add_with_child(GFlow.Node gn, Gtk.Widget child) {
  126. Node n = new Node.with_child(gn, child);
  127. this.add_common(n);
  128. }
  129. /**
  130. * This tells the NodeView to use another {@link NodeRenderer} than
  131. * the DefaultNodeRenderer for the given {@link GFlow.Node}
  132. *
  133. * If you want to use one of libgtkflow's internal node renderers,
  134. * you can just pass the parameter nrt with one of the values in
  135. * {@link GtkFlow.NodeRendererType}. If you want to place a custom
  136. * renderer, set nrt to {@link GtkFlow.NodeRendererType.CUSTOM} and
  137. * pass the instance of your custom node renderer to the third parameter
  138. * called nr.
  139. */
  140. public void set_node_renderer(GFlow.Node gn, NodeRendererType nrt, NodeRenderer? nr=null) throws GFlow.NodeError, NodeRendererError {
  141. Node n = this.get_node_from_gflow_node(gn);
  142. switch (nrt) {
  143. case NodeRendererType.DOCKLINE:
  144. n.node_renderer = new DocklineNodeRenderer(n);
  145. break;
  146. case NodeRendererType.DEFAULT:
  147. n.node_renderer = new DefaultNodeRenderer(n);
  148. break;
  149. case NodeRendererType.CUSTOM:
  150. if (nr == null) {
  151. throw new NodeRendererError.NO_CUSTOM_NODE_RENDERER("You must pass a valid node renderer, not null");
  152. }
  153. n.node_renderer = nr;
  154. break;
  155. default:
  156. throw new NodeRendererError.NOT_A_NODE_RENDERER("This is not a valid node renderer");
  157. }
  158. this.queue_draw();
  159. }
  160. /**
  161. * Use this method to register childwidgets for custom node
  162. * renderes to the supplied {@link GFlow.Node}
  163. */
  164. public void register_child(GFlow.Node gn, Gtk.Widget child) {
  165. Node n = this.get_node_from_gflow_node(gn);
  166. if (n != null) {
  167. n.add(child);
  168. n.show_all();
  169. }
  170. }
  171. /**
  172. * Use this method to unregister childwidgets from the supplied
  173. * {@link GFlow.Node}
  174. */
  175. public void unregister_child(GFlow.Node gn, Gtk.Widget child) {
  176. Node n = this.get_node_from_gflow_node(gn);
  177. if (n != null) {
  178. n.remove(child);
  179. }
  180. }
  181. internal Node? get_node_from_gflow_node(GFlow.Node gn) {
  182. foreach (Node n in this.nodes) {
  183. if (n.gnode == gn) {
  184. return n;
  185. }
  186. }
  187. return null;
  188. }
  189. /**
  190. * Autolayout this graph
  191. */
  192. public void layout(Layout l) {
  193. var passnodes = this.nodes.copy();
  194. l.arrange(passnodes);
  195. }
  196. /**
  197. * Returns nodes that reside inside the given rectangle
  198. */
  199. private List<Node> get_nodes_in_rect(Gtk.Allocation alloc) {
  200. var result = new List<Node>();
  201. Gdk.Rectangle res;
  202. Gtk.Allocation node_alloc;
  203. foreach (Node n in this.nodes) {
  204. n.get_allocation(out node_alloc);
  205. node_alloc.union(alloc, out res);
  206. if (alloc.equal(res)) {
  207. result.append(n);
  208. }
  209. }
  210. return result;
  211. }
  212. /**
  213. * This signal is being triggered when a node is
  214. * being removed from this NodeView
  215. */
  216. public signal void node_removed(GFlow.Node n);
  217. /**
  218. * Remove a {@link GFlow.Node} from this NodeView
  219. */
  220. public void remove_node(GFlow.Node n) {
  221. n.unlink_all();
  222. Node gn = this.get_node_from_gflow_node(n);
  223. gn.forall_internal(true, (c)=>{c.destroy();});
  224. if (this.nodes.index(gn) != -1) {
  225. this.nodes.remove(gn);
  226. gn.node_view = null;
  227. assert (gn is Gtk.Widget);
  228. (gn as Gtk.Widget).destroy();
  229. this.node_removed(n);
  230. this.queue_draw();
  231. }
  232. }
  233. private Node? get_node_on_position(double x,double y) {
  234. Gtk.Allocation alloc;
  235. foreach (Node n in this.nodes) {
  236. n.get_allocation(out alloc);
  237. if ( x >= alloc.x && y >= alloc.y &&
  238. x <= alloc.x + alloc.width && y <= alloc.y + alloc.height ) {
  239. return n;
  240. }
  241. }
  242. return null;
  243. }
  244. private bool do_button_press_event(Gdk.EventButton e) {
  245. if ( e.type == Gdk.EventType.@2BUTTON_PRESS
  246. || e.type == Gdk.EventType.@3BUTTON_PRESS)
  247. return false;
  248. if (!this.editable)
  249. return false;
  250. Node? n = this.get_node_on_position(e.x, e.y);
  251. GFlow.Dock? targeted_dock = null;
  252. Gdk.Point pos = {(int)e.x,(int)e.y};
  253. if (n != null) {
  254. if (!n.selected) {
  255. this.unselect_all();
  256. }
  257. Gtk.Allocation alloc;
  258. n.get_allocation(out alloc);
  259. bool cbp = n.gnode.deletable && n.node_renderer.is_on_closebutton(
  260. pos, alloc,
  261. n.border_width
  262. );
  263. if (cbp) {
  264. this.close_button_pressed = true;
  265. this.unselect_all();
  266. }
  267. targeted_dock = n.node_renderer.get_dock_on_position(
  268. pos, n.get_dock_renderers(),
  269. n.border_width, alloc
  270. );
  271. if (targeted_dock != null) {
  272. this.drag_dock = targeted_dock;
  273. this.drag_dock.active = true;
  274. int startpos_x = 0, startpos_y = 0;
  275. if (this.drag_dock is GFlow.Sink && this.drag_dock.is_linked()){
  276. GFlow.Source s = (this.drag_dock as GFlow.Sink).sources.last().nth_data(0);
  277. Node srcnode = this.get_node_from_gflow_node(s.node);
  278. Gtk.Allocation src_alloc;
  279. srcnode.get_allocation(out src_alloc);
  280. if (!srcnode.node_renderer.get_dock_position(
  281. s, srcnode.get_dock_renderers(),
  282. (int)srcnode.border_width, src_alloc,
  283. out startpos_x, out startpos_y )) {
  284. warning("No dock on position. Aborting drag");
  285. return false;
  286. }
  287. Gdk.Point startpos = {startpos_x,startpos_y};
  288. this.temp_connector = {startpos.x, startpos.y,
  289. (int)e.x-startpos.x, (int)e.y-startpos.y};
  290. } else {
  291. if (!n.node_renderer.get_dock_position(
  292. this.drag_dock, n.get_dock_renderers(),
  293. (int)n.border_width, alloc,
  294. out startpos_x, out startpos_y )) {
  295. warning("No dock on position. Aborting drag");
  296. return false;
  297. }
  298. Gdk.Point startpos = {startpos_x,startpos_y};
  299. this.temp_connector = {startpos.x, startpos.y, 0, 0};
  300. }
  301. this.queue_draw();
  302. return true;
  303. }
  304. }
  305. // Set a new drag node.
  306. if (n != null) {
  307. Gtk.Allocation alloc;
  308. n.get_allocation(out alloc);
  309. bool on_resize = n.gnode.resizable && n.node_renderer.is_on_resize_handle(
  310. pos, alloc,
  311. n.border_width
  312. );
  313. if (on_resize && this.resize_node == null) {
  314. this.resize_node = n;
  315. this.resize_node.get_allocation(out alloc);
  316. if ((this.get_style_context().get_state() & Gtk.StateFlags.DIR_LTR) > 0) {
  317. this.resize_start_x = alloc.width;
  318. this.resize_start_y = alloc.height;
  319. } else {
  320. this.resize_start_x = 0;
  321. this.resize_start_y = alloc.height;
  322. }
  323. } else if (this.resize_node == null && this.drag_node == null) {
  324. this.drag_node = n;
  325. this.drag_node.get_allocation(out alloc);
  326. } else {
  327. return false;
  328. }
  329. this.drag_start_x = e.x;
  330. this.drag_start_y = e.y;
  331. this.drag_diff_x = (int)this.drag_start_x - alloc.x;
  332. this.drag_diff_y = (int)this.drag_start_y - alloc.y;
  333. } else {
  334. this.unselect_all();
  335. this.rubber_alloc = {(int)e.x, (int)e.y, 0, 0};
  336. this.rubber_start_x = (int)e.x;
  337. this.rubber_start_y = (int)e.y;
  338. }
  339. return false;
  340. }
  341. /**
  342. * Every currently selected node is being unselected
  343. */
  344. public void unselect_all() {
  345. foreach (Node n in this.nodes) {
  346. n.selected = false;
  347. }
  348. this.queue_draw();
  349. }
  350. private List<Node> get_selected_nodes() {
  351. var result = new List<Node>();
  352. foreach (Node n in this.nodes) {
  353. if (n.selected)
  354. result.append(n);
  355. }
  356. return result;
  357. }
  358. /**
  359. * Returns each {@link GFlow.Node} that is currently selected
  360. */
  361. public List<GFlow.Node> get_selected() {
  362. var result = new List<GFlow.Node>();
  363. foreach (Node n in this.nodes) {
  364. if (n.selected)
  365. result.append(n.gnode);
  366. }
  367. return result;
  368. }
  369. //Empty remove implementation to avoid warning message
  370. /**
  371. * Empty default implementation. Do not use. To remove {@link GFlow.Node}s
  372. * from a NodeView please use {@link NodeView.remove_node}
  373. */
  374. public override void remove(Gtk.Widget w) {}
  375. private bool do_button_release_event(Gdk.EventButton e) {
  376. if (!this.editable)
  377. return false;
  378. // Determine if this was a closebutton press
  379. if (this.close_button_pressed) {
  380. Node? n = this.get_node_on_position(e.x, e.y);
  381. if (n != null) {
  382. Gdk.Point pos = {(int)e.x,(int)e.y};
  383. Gtk.Allocation alloc;
  384. n.get_allocation(out alloc);
  385. bool cbp = n.node_renderer.is_on_closebutton(
  386. pos, alloc,
  387. n.border_width
  388. );
  389. if (cbp) {
  390. this.remove_node(n.gnode);
  391. this.close_button_pressed = false;
  392. return true;
  393. }
  394. }
  395. }
  396. // Try to build a new connection
  397. if (this.drag_dock != null) {
  398. try {
  399. if (this.drag_dock is GFlow.Source && this.drop_dock is GFlow.Sink) {
  400. (this.drag_dock as GFlow.Source).link(this.drop_dock as GFlow.Sink);
  401. }
  402. else if (this.drag_dock is GFlow.Sink && this.drop_dock is GFlow.Source) {
  403. (this.drop_dock as GFlow.Source).link(this.drag_dock as GFlow.Sink);
  404. }
  405. else if (this.drag_dock is GFlow.Sink && this.drop_dock is GFlow.Sink) {
  406. GFlow.Source? src = (this.drag_dock as GFlow.Sink).sources.last().nth_data(0);
  407. if (src != null) {
  408. src.unlink(this.drag_dock as GFlow.Sink);
  409. src.link(this.drop_dock as GFlow.Sink);
  410. }
  411. }
  412. else if (this.drag_dock is GFlow.Sink && this.drop_dock == null) {
  413. GFlow.Source? src = (this.drag_dock as GFlow.Sink).sources.last().nth_data(0);
  414. if (src != null) {
  415. src.unlink(this.drag_dock as GFlow.Sink);
  416. }
  417. }
  418. } catch (GLib.Error e) {
  419. warning(e.message);
  420. }
  421. }
  422. this.stop_dragging();
  423. this.queue_draw();
  424. return false;
  425. }
  426. private void stop_dragging() {
  427. this.drag_start_x = 0;
  428. this.drag_start_y = 0;
  429. this.drag_diff_x = 0;
  430. this.drag_diff_y = 0;
  431. this.rubber_alloc = null;
  432. this.drag_node = null;
  433. if (this.drag_dock != null) {
  434. this.drag_dock.active = false;
  435. }
  436. this.drag_dock = null;
  437. if (this.drop_dock != null) {
  438. this.drop_dock.active = false;
  439. }
  440. this.drop_dock = null;
  441. this.temp_connector = null;
  442. this.drag_threshold_fulfilled = false;
  443. this.resize_node = null;
  444. this.get_window().set_cursor(null);
  445. }
  446. private Gdk.Cursor resize_cursor = null;
  447. private Gdk.Cursor? get_resize_cursor() {
  448. if (resize_cursor == null && this.get_realized()) {
  449. if ((this.get_style_context().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  450. resize_cursor = new Gdk.Cursor.for_display(
  451. this.get_window().get_display(),
  452. Gdk.CursorType.BOTTOM_RIGHT_CORNER
  453. );
  454. } else {
  455. resize_cursor = new Gdk.Cursor.for_display(
  456. this.get_window().get_display(),
  457. Gdk.CursorType.BOTTOM_LEFT_CORNER
  458. );
  459. }
  460. }
  461. return resize_cursor;
  462. }
  463. private bool do_motion_notify_event(Gdk.EventMotion e) {
  464. if (!this.editable)
  465. return false;
  466. // Check if we are on a node. If yes, check if we are
  467. // currently pointing on a dock. if this is true, we
  468. // Want to draw a new connector instead of dragging the node
  469. Node? n = this.get_node_on_position(e.x, e.y);
  470. GFlow.Dock? targeted_dock = null;
  471. if (n != null) {
  472. Gdk.Point pos = {(int)e.x, (int)e.y};
  473. Gtk.Allocation alloc;
  474. n.get_allocation(out alloc);
  475. // If the close button is pressed, deactivate it when
  476. // we moved the node
  477. if (this.close_button_pressed) {
  478. bool cbp = n.node_renderer.is_on_closebutton(
  479. pos, alloc,
  480. n.border_width
  481. );
  482. if (!cbp)
  483. this.close_button_pressed = false;
  484. }
  485. // Update cursor if we are on the resize area
  486. bool on_resize = n.gnode.resizable && n.node_renderer.is_on_resize_handle(
  487. pos, alloc,
  488. n.border_width
  489. );
  490. if (on_resize)
  491. this.get_window().set_cursor(this.get_resize_cursor());
  492. else if (this.resize_node == null)
  493. this.get_window().set_cursor(null);
  494. targeted_dock = n.node_renderer.get_dock_on_position(
  495. pos, n.get_dock_renderers(),
  496. n.border_width, alloc
  497. );
  498. if (this.drag_dock == null && targeted_dock != this.hovered_dock) {
  499. this.set_hovered_dock(targeted_dock);
  500. }
  501. else if (this.drag_dock != null && targeted_dock != null
  502. && targeted_dock != this.hovered_dock
  503. && this.is_suitable_target(this.drag_dock, targeted_dock)) {
  504. this.set_hovered_dock(targeted_dock);
  505. }
  506. } else {
  507. // If we are leaving the node we will also have to
  508. // un-highlight the last hovered dock
  509. if (this.hovered_dock != null)
  510. this.hovered_dock.highlight = false;
  511. this.hovered_dock = null;
  512. this.queue_draw();
  513. // Update cursor to be default as we are guaranteed not on any
  514. // resize handle outside of any node.
  515. // The check for resize node is a cosmetical fix. If there is a
  516. // Node bing resized in haste, the cursor tends to flicker
  517. if (this.resize_node == null)
  518. this.get_window().set_cursor(null);
  519. }
  520. // Check if the cursor has been dragged a few pixels (defined by DRAG_THRESHOLD)
  521. // If yes, actually start dragging
  522. if ( ( this.drag_node != null || this.drag_dock != null || this.resize_node != null)
  523. && (Math.fabs(drag_start_x - e.x) > NodeView.DRAG_THRESHOLD
  524. || Math.fabs(drag_start_y - e.y) > NodeView.DRAG_THRESHOLD )) {
  525. this.drag_threshold_fulfilled = true;
  526. }
  527. // Actually something
  528. if (this.drag_threshold_fulfilled ) {
  529. Gtk.Allocation alloc;
  530. if (this.drag_node != null) {
  531. // Actually move the node(s)
  532. var nodes_to_drag = this.get_selected_nodes();
  533. Gtk.Allocation drag_node_alloc;
  534. this.drag_node.get_allocation(out drag_node_alloc);
  535. if (nodes_to_drag.length() == 0) {
  536. nodes_to_drag.append(this.drag_node);
  537. }
  538. Gtk.Allocation union = {0,0,0,0};
  539. bool first = true;
  540. Gdk.Point upperleft = {int.MAX,int.MAX};
  541. foreach (Node node in nodes_to_drag) {
  542. node.get_allocation(out alloc);
  543. upperleft.x = (int)Math.fmin(alloc.x, upperleft.x);
  544. upperleft.y = (int)Math.fmin(alloc.y, upperleft.y);
  545. alloc.x = (int)Math.fmax(0,(int)e.x - this.drag_diff_x);
  546. alloc.y = (int)Math.fmax(0,(int)e.y - this.drag_diff_y);
  547. if (first) {
  548. union = alloc;
  549. } else {
  550. Gdk.Rectangle tmp;
  551. union.union(alloc, out tmp);
  552. union = (Gtk.Allocation)tmp;
  553. }
  554. }
  555. foreach (Node node in nodes_to_drag) {
  556. node.get_allocation(out alloc);
  557. int dn_diff_x = alloc.x - drag_node_alloc.x;
  558. int dn_diff_y = alloc.y - drag_node_alloc.y;
  559. int ul_diff_x = alloc.x - upperleft.x;
  560. int ul_diff_y = alloc.y - upperleft.y;
  561. alloc.x = (int)Math.fmax(ul_diff_x,(int)e.x - this.drag_diff_x + dn_diff_x);
  562. alloc.y = (int)Math.fmax(ul_diff_y,(int)e.y - this.drag_diff_y + dn_diff_y);
  563. node.size_allocate(alloc);
  564. }
  565. }
  566. if (this.drag_dock != null) {
  567. // Manipulate the temporary connector
  568. this.temp_connector.width = (int)e.x-this.temp_connector.x;
  569. this.temp_connector.height = (int)e.y-this.temp_connector.y;
  570. if (targeted_dock == null) {
  571. this.set_drop_dock(null);
  572. }
  573. else if (this.is_suitable_target(this.drag_dock, targeted_dock))
  574. this.set_drop_dock(targeted_dock);
  575. }
  576. if (this.resize_node != null) {
  577. // resize the node
  578. this.resize_node.get_allocation(out alloc);
  579. if ((this.get_style_context().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  580. alloc.width = resize_start_x + (int)e.x - (int)this.drag_start_x;
  581. alloc.height = resize_start_y + (int)e.y - (int)this.drag_start_y;
  582. } else {
  583. //FIXME: far from perfect. strange behaviour when resizing
  584. alloc.x = (int)e.x;
  585. alloc.width = alloc.width + ((int)this.drag_start_x - (int)e.x);
  586. alloc.height = resize_start_y + (int)e.y - (int)this.drag_start_y;
  587. }
  588. this.resize_node.size_allocate(alloc);
  589. }
  590. this.allocate_minimum();
  591. this.queue_draw();
  592. }
  593. if (this.rubber_alloc != null) {
  594. if (e.x >= this.rubber_start_x) {
  595. this.rubber_alloc.x = (int)this.rubber_start_x;
  596. this.rubber_alloc.width = (int)e.x - this.rubber_alloc.x;
  597. } else {
  598. this.rubber_alloc.x = (int)e.x;
  599. this.rubber_alloc.width = this.rubber_start_x - (int)e.x;
  600. }
  601. if (e.y >= this.rubber_start_y) {
  602. this.rubber_alloc.y = (int)this.rubber_start_y;
  603. this.rubber_alloc.height = (int)e.y - this.rubber_alloc.y;
  604. } else {
  605. this.rubber_alloc.y = (int)e.y;
  606. this.rubber_alloc.height = this.rubber_start_y - (int)e.y;
  607. }
  608. var selected_nodes = this.get_nodes_in_rect(this.rubber_alloc);
  609. foreach (Node node in this.nodes) {
  610. node.selected = selected_nodes.index(node) != -1;
  611. }
  612. this.allocate_minimum();
  613. this.queue_draw();
  614. }
  615. return false;
  616. }
  617. /**
  618. * Allocates the minimum size needed to draw the
  619. * contained nodes at their respective positions
  620. * with their respective sizes to this NodeView
  621. * If the minimunsize is smaller than the containing
  622. * parent (which ideally should be a scrolledwindow
  623. * scrolledwindow) the parent's size will be allocated
  624. * in order to prevent the nodeview from collapsing
  625. * to a size of 0x0 px thus being inable to receive
  626. * mouse-events
  627. */
  628. private void allocate_minimum() {
  629. int minwidth = 0, minheight = 0, _ = 0;
  630. this.get_preferred_width(out minwidth, out _);
  631. this.get_preferred_height(out minheight, out _);
  632. this.set_size_request(minwidth, minheight);
  633. Gtk.Allocation nv_alloc;
  634. this.get_allocation(out nv_alloc);
  635. nv_alloc.width = minwidth;
  636. nv_alloc.height = minheight;
  637. if (this.parent != null) {
  638. Gtk.Allocation palloc;
  639. this.parent.get_allocation(out palloc);
  640. nv_alloc.width = int.max(minwidth, palloc.width);
  641. nv_alloc.height = int.max(minheight, palloc.height);
  642. }
  643. this.size_allocate(nv_alloc);
  644. }
  645. /**
  646. * Calculates the NodeView's minimum and preferred widths
  647. */
  648. public new void get_preferred_width(out int minimum_width, out int natural_width) {
  649. double x_min = 0, x_max = 0;
  650. Gtk.Allocation alloc;
  651. foreach (Node n in this.nodes) {
  652. n.get_allocation(out alloc);
  653. x_min = Math.fmin(x_min, alloc.x);
  654. x_max = Math.fmax(x_max, alloc.x+alloc.width);
  655. }
  656. if (this.rubber_alloc != null) {
  657. x_min = Math.fmin(x_min, rubber_alloc.x);
  658. x_max = Math.fmax(x_max, rubber_alloc.x + rubber_alloc.width);
  659. }
  660. if (this.temp_connector != null) {
  661. x_min = Math.fmin(x_min, temp_connector.x);
  662. x_max = Math.fmax(x_max, temp_connector.x + temp_connector.width);
  663. }
  664. x_min = Math.fmax(0, x_min);
  665. minimum_width = natural_width = (int)x_max - (int)x_min;
  666. }
  667. /**
  668. * Calculates the NodeView's minimum and preferred heights
  669. */
  670. public new void get_preferred_height(out int minimum_height, out int natural_height) {
  671. double y_min = 0, y_max = 0;
  672. Gtk.Allocation alloc;
  673. foreach (Node n in this.nodes) {
  674. n.get_allocation(out alloc);
  675. y_min = Math.fmin(y_min, alloc.y);
  676. y_max = Math.fmax(y_max, alloc.y+alloc.height);
  677. }
  678. if (this.rubber_alloc != null) {
  679. y_min = Math.fmin(y_min, rubber_alloc.y);
  680. y_max = Math.fmax(y_max, rubber_alloc.y + rubber_alloc.height);
  681. }
  682. if (this.temp_connector != null) {
  683. y_min = Math.fmin(y_min, temp_connector.y);
  684. y_max = Math.fmax(y_max, temp_connector.y + temp_connector.height);
  685. }
  686. y_min = Math.fmax(0, y_min);
  687. minimum_height = natural_height = (int)y_max - (int)y_min;
  688. }
  689. /**
  690. * Determines wheter one dock can be dropped on another
  691. */
  692. private bool is_suitable_target (GFlow.Dock from, GFlow.Dock to) {
  693. // Check whether the docks have the same type
  694. if (!from.has_same_type(to))
  695. return false;
  696. // Check if the target would lead to a recursion
  697. // If yes, return the value of allow_recursion. If this
  698. // value is set to true, it's completely fine to have
  699. // a recursive graph
  700. if (to is GFlow.Source && from is GFlow.Sink) {
  701. if (!this.allow_recursion)
  702. if (from.node.is_recursive_forward(to.node) ||
  703. to.node.is_recursive_backward(from.node))
  704. return false;
  705. }
  706. if (to is GFlow.Sink && from is GFlow.Source) {
  707. if (!this.allow_recursion)
  708. if (to.node.is_recursive_forward(from.node) ||
  709. from.node.is_recursive_backward(to.node))
  710. return false;
  711. }
  712. if (to is GFlow.Sink && from is GFlow.Sink) {
  713. GFlow.Source? s = (from as GFlow.Sink).sources.last().nth_data(0);
  714. if (s == null)
  715. return false;
  716. if (!this.allow_recursion)
  717. if (to.node.is_recursive_forward(s.node) ||
  718. s.node.is_recursive_backward(to.node))
  719. return false;
  720. }
  721. // If the from from-target is a sink, check if the
  722. // to target is either a source which does not belong to the own node
  723. // or if the to target is another sink (this is valid as we can
  724. // move a connection from one sink to another
  725. if (from is GFlow.Sink
  726. && ((to is GFlow.Sink
  727. && to != from)
  728. || (to is GFlow.Source
  729. && (!to.node.has_dock(from) || this.allow_recursion)))) {
  730. return true;
  731. }
  732. // Check if the from-target is a source. if yes, make sure the
  733. // to-target is a sink and it does not belong to the own node
  734. else if (from is GFlow.Source
  735. && to is GFlow.Sink
  736. && (!to.node.has_dock(from) || this.allow_recursion)) {
  737. return true;
  738. }
  739. return false;
  740. }
  741. /**
  742. * Sets the dock that is currently being hovered over to drop
  743. * a connector on
  744. */
  745. private void set_drop_dock(GFlow.Dock? d) {
  746. if (this.drop_dock != null)
  747. this.drop_dock.active = false;
  748. this.drop_dock = d;
  749. if (this.drop_dock != null)
  750. this.drop_dock.active = true;
  751. this.queue_draw();
  752. }
  753. /**
  754. * Sets the dock that is currently being hovered over
  755. */
  756. private void set_hovered_dock(GFlow.Dock? d) {
  757. if (this.hovered_dock != null)
  758. this.hovered_dock.highlight = false;
  759. this.hovered_dock = d;
  760. if (this.hovered_dock != null)
  761. this.hovered_dock.highlight = true;
  762. this.queue_draw();
  763. }
  764. /**
  765. * Manually set the position of the given {@link GFlow.Node} on this nodeview
  766. */
  767. public void set_node_position(GFlow.Node gn, int x, int y) {
  768. Node n = this.get_node_from_gflow_node(gn);
  769. n.set_position(x,y);
  770. this.allocate_minimum();
  771. this.queue_draw();
  772. }
  773. /**
  774. * Return the position of the given {@link GFlow.Node} on this nodeview
  775. */
  776. public unowned Gdk.Point get_node_position(GFlow.Node gn) {
  777. Node n = this.get_node_from_gflow_node(gn);
  778. return n.get_position();
  779. }
  780. /**
  781. * Returns the allocation of the given {@link GFlow.Node}
  782. */
  783. public unowned Gtk.Allocation get_node_allocation(GFlow.Node gn) {
  784. Gtk.Allocation alloc;
  785. Node n = this.get_node_from_gflow_node(gn);
  786. n.get_allocation(out alloc);
  787. return alloc;
  788. }
  789. private bool do_draw(Cairo.Context cr) {
  790. Gtk.StyleContext sc = this.get_style_context();
  791. Gdk.RGBA bg = sc.get_background_color(Gtk.StateFlags.NORMAL);
  792. cr.set_source_rgba(bg.red, bg.green, bg.blue, bg.alpha);
  793. cr.paint();
  794. // Draw nodes
  795. this.nodes.reverse();
  796. foreach (Node n in this.nodes) {
  797. n.current_cairo_ctx = cr;
  798. Gtk.Allocation alloc;
  799. n.get_allocation(out alloc);
  800. var node_properties = NodeProperties();
  801. node_properties.editable = this.editable;
  802. node_properties.deletable = n.gnode.deletable;
  803. node_properties.resizable = n.gnode.resizable;
  804. node_properties.selected = n.selected;
  805. n.node_renderer.draw_node(
  806. this,
  807. cr,
  808. alloc,
  809. n.get_dock_renderers(),
  810. n.get_childlist(),
  811. (int)n.border_width,
  812. node_properties
  813. );
  814. n.current_cairo_ctx = null;
  815. }
  816. this.nodes.reverse();
  817. // Draw connectors
  818. foreach (Node n in this.nodes) {
  819. foreach(GFlow.Source source in n.gnode.get_sources()) {
  820. Gtk.Allocation alloc;
  821. n.get_allocation(out alloc);
  822. int source_pos_x = 0, source_pos_y = 0;
  823. if (!n.node_renderer.get_dock_position(
  824. source,
  825. n.get_dock_renderers(),
  826. (int)n.border_width,
  827. alloc, out source_pos_x, out source_pos_y)) {
  828. warning("No dock on position. Ommiting connector");
  829. continue;
  830. }
  831. Gdk.Point source_pos = {source_pos_x,source_pos_y};
  832. foreach(GFlow.Sink sink in source.sinks) {
  833. // Don't draw the connection to a sink if we are dragging it
  834. if (sink == this.drag_dock && source == sink.sources.last().nth_data(0))
  835. continue;
  836. Node? sink_node = this.get_node_from_gflow_node(sink.node);
  837. sink_node.get_allocation(out alloc);
  838. int sink_pos_x = 0, sink_pos_y = 0;
  839. if (!sink_node.node_renderer.get_dock_position(
  840. sink,
  841. sink_node.get_dock_renderers(),
  842. (int)sink_node.border_width,
  843. alloc, out sink_pos_x, out sink_pos_y )) {
  844. warning("No dock on position. Ommiting connector");
  845. continue;
  846. }
  847. Gdk.Point sink_pos = {sink_pos_x,sink_pos_y};
  848. int w = sink_pos.x - source_pos.x;
  849. int h = sink_pos.y - source_pos.y;
  850. cr.save();
  851. if (source != null && source.val != null) {
  852. double r=0, g=0, b=0;
  853. this.hex2col(color_calculation(source.val),out r, out g, out b);
  854. cr.set_source_rgba(r,g,b,1.0);
  855. }
  856. cr.move_to(source_pos.x, source_pos.y);
  857. cr.rel_curve_to(w,0,0,h,w,h);
  858. cr.stroke();
  859. cr.restore();
  860. }
  861. }
  862. }
  863. // Draw temporary connector if any
  864. if (this.temp_connector != null) {
  865. int w = this.temp_connector.width;
  866. int h = this.temp_connector.height;
  867. cr.move_to(this.temp_connector.x, this.temp_connector.y);
  868. cr.rel_curve_to(w,0,0,h,w,h);
  869. cr.stroke();
  870. }
  871. // Draw rubberband
  872. if (this.rubber_alloc != null) {
  873. draw_rubberband(this, cr,
  874. this.rubber_alloc.x, this.rubber_alloc.y,
  875. Gtk.StateFlags.NORMAL,
  876. &this.rubber_alloc.width, &this.rubber_alloc.height);
  877. }
  878. return true;
  879. }
  880. private void hex2col(string hex, out double r, out double g, out double b) {
  881. string hexdigits ="0123456789abcdef";
  882. r = col_h2f(hexdigits.index_of_char(hex[0]) * 16 + hexdigits.index_of_char(hex[1]));
  883. g = col_h2f(hexdigits.index_of_char(hex[2]) * 16 + hexdigits.index_of_char(hex[3]));
  884. b = col_h2f(hexdigits.index_of_char(hex[4]) * 16 + hexdigits.index_of_char(hex[5]));
  885. }
  886. private double col_h2f(uint col) {
  887. return col/255.0f;
  888. }
  889. private uint col_f2h(double col) {
  890. return (uint)int.parse( (col*255.0d).to_string() ).abs();
  891. }
  892. /**
  893. * Internal method to initialize this NodeView as a {@link Gtk.Widget}
  894. */
  895. public override void realize() {
  896. Gtk.Allocation alloc;
  897. this.get_allocation(out alloc);
  898. var attr = Gdk.WindowAttr();
  899. attr.window_type = Gdk.WindowType.CHILD;
  900. attr.x = alloc.x;
  901. attr.y = alloc.y;
  902. attr.width = alloc.width;
  903. attr.height = alloc.height;
  904. attr.visual = this.get_visual();
  905. attr.event_mask = this.get_events()
  906. | Gdk.EventMask.POINTER_MOTION_MASK
  907. | Gdk.EventMask.BUTTON_PRESS_MASK
  908. | Gdk.EventMask.BUTTON_RELEASE_MASK
  909. | Gdk.EventMask.LEAVE_NOTIFY_MASK;
  910. Gdk.WindowAttributesType mask = Gdk.WindowAttributesType.X
  911. | Gdk.WindowAttributesType.X
  912. | Gdk.WindowAttributesType.VISUAL;
  913. var window = new Gdk.Window(this.get_parent_window(), attr, mask);
  914. this.set_window(window);
  915. this.register_window(window);
  916. this.set_realized(true);
  917. }
  918. /**
  919. * Internal method so that GtkInspector can see child widgets
  920. */
  921. public override void forall_internal(bool include_internal, Gtk.Callback cb) {
  922. nodes.foreach(n => cb(n));
  923. }
  924. }
  925. /**
  926. * Draw radiobutton.
  927. * Implemented in drawinghelper.c
  928. */
  929. private extern void draw_radio(Gtk.Widget widget,
  930. Cairo.Context cr,
  931. int x,
  932. int y,
  933. Gtk.StateFlags state,
  934. int* width,
  935. int* height);
  936. /**
  937. * Draw rubberband selection.
  938. * Implemented in drawinghelper.c
  939. */
  940. private extern void draw_rubberband(Gtk.Widget widget,
  941. Cairo.Context cr,
  942. int x,
  943. int y,
  944. Gtk.StateFlags state,
  945. int* width,
  946. int* height);
  947. }