nodeview.vala 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  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. bool cbp = n.node_renderer.is_on_closebutton(
  476. pos, alloc,
  477. n.border_width
  478. );
  479. if (!cbp)
  480. this.close_button_pressed = false;
  481. // Update cursor if we are on the resize area
  482. bool on_resize = n.gnode.resizable && n.node_renderer.is_on_resize_handle(
  483. pos, alloc,
  484. n.border_width
  485. );
  486. if (on_resize)
  487. this.get_window().set_cursor(this.get_resize_cursor());
  488. else if (this.resize_node == null)
  489. this.get_window().set_cursor(null);
  490. targeted_dock = n.node_renderer.get_dock_on_position(
  491. pos, n.get_dock_renderers(),
  492. n.border_width, alloc
  493. );
  494. if (this.drag_dock == null && targeted_dock != this.hovered_dock) {
  495. this.set_hovered_dock(targeted_dock);
  496. }
  497. else if (this.drag_dock != null && targeted_dock != null
  498. && targeted_dock != this.hovered_dock
  499. && this.is_suitable_target(this.drag_dock, targeted_dock)) {
  500. this.set_hovered_dock(targeted_dock);
  501. }
  502. } else {
  503. // If we are leaving the node we will also have to
  504. // un-highlight the last hovered dock
  505. if (this.hovered_dock != null)
  506. this.hovered_dock.highlight = false;
  507. this.hovered_dock = null;
  508. this.queue_draw();
  509. // Update cursor to be default as we are guaranteed not on any
  510. // resize handle outside of any node.
  511. // The check for resize node is a cosmetical fix. If there is a
  512. // Node bing resized in haste, the cursor tends to flicker
  513. if (this.resize_node == null)
  514. this.get_window().set_cursor(null);
  515. }
  516. // Check if the cursor has been dragged a few pixels (defined by DRAG_THRESHOLD)
  517. // If yes, actually start dragging
  518. if ( ( this.drag_node != null || this.drag_dock != null || this.resize_node != null)
  519. && (Math.fabs(drag_start_x - e.x) > NodeView.DRAG_THRESHOLD
  520. || Math.fabs(drag_start_y - e.y) > NodeView.DRAG_THRESHOLD )) {
  521. this.drag_threshold_fulfilled = true;
  522. }
  523. // Actually something
  524. if (this.drag_threshold_fulfilled ) {
  525. Gtk.Allocation alloc;
  526. if (this.drag_node != null) {
  527. // Actually move the node(s)
  528. var nodes_to_drag = this.get_selected_nodes();
  529. Gtk.Allocation drag_node_alloc;
  530. this.drag_node.get_allocation(out drag_node_alloc);
  531. if (nodes_to_drag.length() == 0) {
  532. nodes_to_drag.append(this.drag_node);
  533. }
  534. Gtk.Allocation union = {0,0,0,0};
  535. bool first = true;
  536. Gdk.Point upperleft = {int.MAX,int.MAX};
  537. foreach (Node node in nodes_to_drag) {
  538. node.get_allocation(out alloc);
  539. upperleft.x = (int)Math.fmin(alloc.x, upperleft.x);
  540. upperleft.y = (int)Math.fmin(alloc.y, upperleft.y);
  541. alloc.x = (int)Math.fmax(0,(int)e.x - this.drag_diff_x);
  542. alloc.y = (int)Math.fmax(0,(int)e.y - this.drag_diff_y);
  543. if (first) {
  544. union = alloc;
  545. } else {
  546. Gdk.Rectangle tmp;
  547. union.union(alloc, out tmp);
  548. union = (Gtk.Allocation)tmp;
  549. }
  550. }
  551. foreach (Node node in nodes_to_drag) {
  552. node.get_allocation(out alloc);
  553. int dn_diff_x = alloc.x - drag_node_alloc.x;
  554. int dn_diff_y = alloc.y - drag_node_alloc.y;
  555. int ul_diff_x = alloc.x - upperleft.x;
  556. int ul_diff_y = alloc.y - upperleft.y;
  557. alloc.x = (int)Math.fmax(ul_diff_x,(int)e.x - this.drag_diff_x + dn_diff_x);
  558. alloc.y = (int)Math.fmax(ul_diff_y,(int)e.y - this.drag_diff_y + dn_diff_y);
  559. node.size_allocate(alloc);
  560. }
  561. }
  562. if (this.drag_dock != null) {
  563. // Manipulate the temporary connector
  564. this.temp_connector.width = (int)e.x-this.temp_connector.x;
  565. this.temp_connector.height = (int)e.y-this.temp_connector.y;
  566. if (targeted_dock == null) {
  567. this.set_drop_dock(null);
  568. }
  569. else if (this.is_suitable_target(this.drag_dock, targeted_dock))
  570. this.set_drop_dock(targeted_dock);
  571. }
  572. if (this.resize_node != null) {
  573. // resize the node
  574. this.resize_node.get_allocation(out alloc);
  575. if ((this.get_style_context().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  576. alloc.width = resize_start_x + (int)e.x - (int)this.drag_start_x;
  577. alloc.height = resize_start_y + (int)e.y - (int)this.drag_start_y;
  578. } else {
  579. //FIXME: far from perfect. strange behaviour when resizing
  580. alloc.x = (int)e.x;
  581. alloc.width = alloc.width + ((int)this.drag_start_x - (int)e.x);
  582. alloc.height = resize_start_y + (int)e.y - (int)this.drag_start_y;
  583. }
  584. this.resize_node.size_allocate(alloc);
  585. }
  586. this.allocate_minimum();
  587. this.queue_draw();
  588. }
  589. if (this.rubber_alloc != null) {
  590. if (e.x >= this.rubber_start_x) {
  591. this.rubber_alloc.x = (int)this.rubber_start_x;
  592. this.rubber_alloc.width = (int)e.x - this.rubber_alloc.x;
  593. } else {
  594. this.rubber_alloc.x = (int)e.x;
  595. this.rubber_alloc.width = this.rubber_start_x - (int)e.x;
  596. }
  597. if (e.y >= this.rubber_start_y) {
  598. this.rubber_alloc.y = (int)this.rubber_start_y;
  599. this.rubber_alloc.height = (int)e.y - this.rubber_alloc.y;
  600. } else {
  601. this.rubber_alloc.y = (int)e.y;
  602. this.rubber_alloc.height = this.rubber_start_y - (int)e.y;
  603. }
  604. var selected_nodes = this.get_nodes_in_rect(this.rubber_alloc);
  605. foreach (Node node in this.nodes) {
  606. node.selected = selected_nodes.index(node) != -1;
  607. }
  608. this.allocate_minimum();
  609. this.queue_draw();
  610. }
  611. return false;
  612. }
  613. /**
  614. * Allocates the minimum size needed to draw the
  615. * contained nodes at their respective positions
  616. * with their respective sizes to this NodeView
  617. * If the minimunsize is smaller than the containing
  618. * parent (which ideally should be a scrolledwindow
  619. * scrolledwindow) the parent's size will be allocated
  620. * in order to prevent the nodeview from collapsing
  621. * to a size of 0x0 px thus being inable to receive
  622. * mouse-events
  623. */
  624. private void allocate_minimum() {
  625. int minwidth = 0, minheight = 0, _ = 0;
  626. this.get_preferred_width(out minwidth, out _);
  627. this.get_preferred_height(out minheight, out _);
  628. this.set_size_request(minwidth, minheight);
  629. Gtk.Allocation nv_alloc;
  630. this.get_allocation(out nv_alloc);
  631. nv_alloc.width = minwidth;
  632. nv_alloc.height = minheight;
  633. if (this.parent != null) {
  634. Gtk.Allocation palloc;
  635. this.parent.get_allocation(out palloc);
  636. nv_alloc.width = int.max(minwidth, palloc.width);
  637. nv_alloc.height = int.max(minheight, palloc.height);
  638. }
  639. this.size_allocate(nv_alloc);
  640. }
  641. /**
  642. * Calculates the NodeView's minimum and preferred widths
  643. */
  644. public new void get_preferred_width(out int minimum_width, out int natural_width) {
  645. double x_min = 0, x_max = 0;
  646. Gtk.Allocation alloc;
  647. foreach (Node n in this.nodes) {
  648. n.get_allocation(out alloc);
  649. x_min = Math.fmin(x_min, alloc.x);
  650. x_max = Math.fmax(x_max, alloc.x+alloc.width);
  651. }
  652. if (this.rubber_alloc != null) {
  653. x_min = Math.fmin(x_min, rubber_alloc.x);
  654. x_max = Math.fmax(x_max, rubber_alloc.x + rubber_alloc.width);
  655. }
  656. if (this.temp_connector != null) {
  657. x_min = Math.fmin(x_min, temp_connector.x);
  658. x_max = Math.fmax(x_max, temp_connector.x + temp_connector.width);
  659. }
  660. x_min = Math.fmax(0, x_min);
  661. minimum_width = natural_width = (int)x_max - (int)x_min;
  662. }
  663. /**
  664. * Calculates the NodeView's minimum and preferred heights
  665. */
  666. public new void get_preferred_height(out int minimum_height, out int natural_height) {
  667. double y_min = 0, y_max = 0;
  668. Gtk.Allocation alloc;
  669. foreach (Node n in this.nodes) {
  670. n.get_allocation(out alloc);
  671. y_min = Math.fmin(y_min, alloc.y);
  672. y_max = Math.fmax(y_max, alloc.y+alloc.height);
  673. }
  674. if (this.rubber_alloc != null) {
  675. y_min = Math.fmin(y_min, rubber_alloc.y);
  676. y_max = Math.fmax(y_max, rubber_alloc.y + rubber_alloc.height);
  677. }
  678. if (this.temp_connector != null) {
  679. y_min = Math.fmin(y_min, temp_connector.y);
  680. y_max = Math.fmax(y_max, temp_connector.y + temp_connector.height);
  681. }
  682. y_min = Math.fmax(0, y_min);
  683. minimum_height = natural_height = (int)y_max - (int)y_min;
  684. }
  685. /**
  686. * Determines wheter one dock can be dropped on another
  687. */
  688. private bool is_suitable_target (GFlow.Dock from, GFlow.Dock to) {
  689. // Check whether the docks have the same type
  690. if (!from.has_same_type(to))
  691. return false;
  692. // Check if the target would lead to a recursion
  693. // If yes, return the value of allow_recursion. If this
  694. // value is set to true, it's completely fine to have
  695. // a recursive graph
  696. if (to is GFlow.Source && from is GFlow.Sink) {
  697. if (!this.allow_recursion)
  698. if (from.node.is_recursive_forward(to.node) ||
  699. to.node.is_recursive_backward(from.node))
  700. return false;
  701. }
  702. if (to is GFlow.Sink && from is GFlow.Source) {
  703. if (!this.allow_recursion)
  704. if (to.node.is_recursive_forward(from.node) ||
  705. from.node.is_recursive_backward(to.node))
  706. return false;
  707. }
  708. if (to is GFlow.Sink && from is GFlow.Sink) {
  709. GFlow.Source? s = (from as GFlow.Sink).sources.last().nth_data(0);
  710. if (s == null)
  711. return false;
  712. if (!this.allow_recursion)
  713. if (to.node.is_recursive_forward(s.node) ||
  714. s.node.is_recursive_backward(to.node))
  715. return false;
  716. }
  717. // If the from from-target is a sink, check if the
  718. // to target is either a source which does not belong to the own node
  719. // or if the to target is another sink (this is valid as we can
  720. // move a connection from one sink to another
  721. if (from is GFlow.Sink
  722. && ((to is GFlow.Sink
  723. && to != from)
  724. || (to is GFlow.Source
  725. && (!to.node.has_dock(from) || this.allow_recursion)))) {
  726. return true;
  727. }
  728. // Check if the from-target is a source. if yes, make sure the
  729. // to-target is a sink and it does not belong to the own node
  730. else if (from is GFlow.Source
  731. && to is GFlow.Sink
  732. && (!to.node.has_dock(from) || this.allow_recursion)) {
  733. return true;
  734. }
  735. return false;
  736. }
  737. /**
  738. * Sets the dock that is currently being hovered over to drop
  739. * a connector on
  740. */
  741. private void set_drop_dock(GFlow.Dock? d) {
  742. if (this.drop_dock != null)
  743. this.drop_dock.active = false;
  744. this.drop_dock = d;
  745. if (this.drop_dock != null)
  746. this.drop_dock.active = true;
  747. this.queue_draw();
  748. }
  749. /**
  750. * Sets the dock that is currently being hovered over
  751. */
  752. private void set_hovered_dock(GFlow.Dock? d) {
  753. if (this.hovered_dock != null)
  754. this.hovered_dock.highlight = false;
  755. this.hovered_dock = d;
  756. if (this.hovered_dock != null)
  757. this.hovered_dock.highlight = true;
  758. this.queue_draw();
  759. }
  760. /**
  761. * Manually set the position of the given {@link GFlow.Node} on this nodeview
  762. */
  763. public void set_node_position(GFlow.Node gn, int x, int y) {
  764. Node n = this.get_node_from_gflow_node(gn);
  765. n.set_position(x,y);
  766. this.allocate_minimum();
  767. this.queue_draw();
  768. }
  769. /**
  770. * Return the position of the given {@link GFlow.Node} on this nodeview
  771. */
  772. public unowned Gdk.Point get_node_position(GFlow.Node gn) {
  773. Node n = this.get_node_from_gflow_node(gn);
  774. return n.get_position();
  775. }
  776. /**
  777. * Returns the allocation of the given {@link GFlow.Node}
  778. */
  779. public unowned Gtk.Allocation get_node_allocation(GFlow.Node gn) {
  780. Gtk.Allocation alloc;
  781. Node n = this.get_node_from_gflow_node(gn);
  782. n.get_allocation(out alloc);
  783. return alloc;
  784. }
  785. private bool do_draw(Cairo.Context cr) {
  786. Gtk.StyleContext sc = this.get_style_context();
  787. Gdk.RGBA bg = sc.get_background_color(Gtk.StateFlags.NORMAL);
  788. cr.set_source_rgba(bg.red, bg.green, bg.blue, bg.alpha);
  789. cr.paint();
  790. // Draw nodes
  791. this.nodes.reverse();
  792. foreach (Node n in this.nodes) {
  793. n.current_cairo_ctx = cr;
  794. Gtk.Allocation alloc;
  795. n.get_allocation(out alloc);
  796. var node_properties = NodeProperties();
  797. node_properties.editable = this.editable;
  798. node_properties.deletable = n.gnode.deletable;
  799. node_properties.resizable = n.gnode.resizable;
  800. node_properties.selected = n.selected;
  801. n.node_renderer.draw_node(
  802. this,
  803. cr,
  804. alloc,
  805. n.get_dock_renderers(),
  806. n.get_childlist(),
  807. (int)n.border_width,
  808. node_properties
  809. );
  810. n.current_cairo_ctx = null;
  811. }
  812. this.nodes.reverse();
  813. // Draw connectors
  814. foreach (Node n in this.nodes) {
  815. foreach(GFlow.Source source in n.gnode.get_sources()) {
  816. Gtk.Allocation alloc;
  817. n.get_allocation(out alloc);
  818. int source_pos_x = 0, source_pos_y = 0;
  819. if (!n.node_renderer.get_dock_position(
  820. source,
  821. n.get_dock_renderers(),
  822. (int)n.border_width,
  823. alloc, out source_pos_x, out source_pos_y)) {
  824. warning("No dock on position. Ommiting connector");
  825. continue;
  826. }
  827. Gdk.Point source_pos = {source_pos_x,source_pos_y};
  828. foreach(GFlow.Sink sink in source.sinks) {
  829. // Don't draw the connection to a sink if we are dragging it
  830. if (sink == this.drag_dock && source == sink.sources.last().nth_data(0))
  831. continue;
  832. Node? sink_node = this.get_node_from_gflow_node(sink.node);
  833. sink_node.get_allocation(out alloc);
  834. int sink_pos_x = 0, sink_pos_y = 0;
  835. if (!sink_node.node_renderer.get_dock_position(
  836. sink,
  837. sink_node.get_dock_renderers(),
  838. (int)sink_node.border_width,
  839. alloc, out sink_pos_x, out sink_pos_y )) {
  840. warning("No dock on position. Ommiting connector");
  841. continue;
  842. }
  843. Gdk.Point sink_pos = {sink_pos_x,sink_pos_y};
  844. int w = sink_pos.x - source_pos.x;
  845. int h = sink_pos.y - source_pos.y;
  846. cr.save();
  847. if (source != null && source.val != null) {
  848. double r=0, g=0, b=0;
  849. this.hex2col(color_calculation(source.val),out r, out g, out b);
  850. cr.set_source_rgba(r,g,b,1.0);
  851. }
  852. cr.move_to(source_pos.x, source_pos.y);
  853. cr.rel_curve_to(w,0,0,h,w,h);
  854. cr.stroke();
  855. cr.restore();
  856. }
  857. }
  858. }
  859. // Draw temporary connector if any
  860. if (this.temp_connector != null) {
  861. int w = this.temp_connector.width;
  862. int h = this.temp_connector.height;
  863. cr.move_to(this.temp_connector.x, this.temp_connector.y);
  864. cr.rel_curve_to(w,0,0,h,w,h);
  865. cr.stroke();
  866. }
  867. // Draw rubberband
  868. if (this.rubber_alloc != null) {
  869. draw_rubberband(this, cr,
  870. this.rubber_alloc.x, this.rubber_alloc.y,
  871. Gtk.StateFlags.NORMAL,
  872. &this.rubber_alloc.width, &this.rubber_alloc.height);
  873. }
  874. return true;
  875. }
  876. private void hex2col(string hex, out double r, out double g, out double b) {
  877. string hexdigits ="0123456789abcdef";
  878. r = col_h2f(hexdigits.index_of_char(hex[0]) * 16 + hexdigits.index_of_char(hex[1]));
  879. g = col_h2f(hexdigits.index_of_char(hex[2]) * 16 + hexdigits.index_of_char(hex[3]));
  880. b = col_h2f(hexdigits.index_of_char(hex[4]) * 16 + hexdigits.index_of_char(hex[5]));
  881. }
  882. private double col_h2f(uint col) {
  883. return col/255.0f;
  884. }
  885. private uint col_f2h(double col) {
  886. return (uint)int.parse( (col*255.0d).to_string() ).abs();
  887. }
  888. /**
  889. * Internal method to initialize this NodeView as a {@link Gtk.Widget}
  890. */
  891. public override void realize() {
  892. Gtk.Allocation alloc;
  893. this.get_allocation(out alloc);
  894. var attr = Gdk.WindowAttr();
  895. attr.window_type = Gdk.WindowType.CHILD;
  896. attr.x = alloc.x;
  897. attr.y = alloc.y;
  898. attr.width = alloc.width;
  899. attr.height = alloc.height;
  900. attr.visual = this.get_visual();
  901. attr.event_mask = this.get_events()
  902. | Gdk.EventMask.POINTER_MOTION_MASK
  903. | Gdk.EventMask.BUTTON_PRESS_MASK
  904. | Gdk.EventMask.BUTTON_RELEASE_MASK
  905. | Gdk.EventMask.LEAVE_NOTIFY_MASK;
  906. Gdk.WindowAttributesType mask = Gdk.WindowAttributesType.X
  907. | Gdk.WindowAttributesType.X
  908. | Gdk.WindowAttributesType.VISUAL;
  909. var window = new Gdk.Window(this.get_parent_window(), attr, mask);
  910. this.set_window(window);
  911. this.register_window(window);
  912. this.set_realized(true);
  913. }
  914. /**
  915. * Internal method so that GtkInspector can see child widgets
  916. */
  917. public override void forall_internal(bool include_internal, Gtk.Callback cb) {
  918. nodes.foreach(n => cb(n));
  919. }
  920. }
  921. /**
  922. * Draw radiobutton.
  923. * Implemented in drawinghelper.c
  924. */
  925. private extern void draw_radio(Gtk.Widget widget,
  926. Cairo.Context cr,
  927. int x,
  928. int y,
  929. Gtk.StateFlags state,
  930. int* width,
  931. int* height);
  932. /**
  933. * Draw rubberband selection.
  934. * Implemented in drawinghelper.c
  935. */
  936. private extern void draw_rubberband(Gtk.Widget widget,
  937. Cairo.Context cr,
  938. int x,
  939. int y,
  940. Gtk.StateFlags state,
  941. int* width,
  942. int* height);
  943. }