dockline_node_renderer.vala 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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. public class DocklineNodeRenderer : NodeRenderer {
  23. private Pango.Layout layout;
  24. internal DocklineNodeRenderer(Node n) {
  25. this.layout = (new Gtk.Label("")).create_pango_layout("");
  26. }
  27. public override void update_name_layout(string name) {
  28. this.layout.set_markup("<b>%s</b>".printf(name),-1);
  29. this.size_changed();
  30. }
  31. private uint get_title_line_height() {
  32. // FIXME: this is a bad solution. it should not happen in the first place
  33. // probably related to the remaining Pango-CRITICALs. but it works.
  34. if (this.layout == null)
  35. return 25;
  36. int width, height;
  37. this.layout.get_pixel_size(out width, out height);
  38. return (uint)Math.fmax(height, delete_btn_size) + title_spacing;
  39. }
  40. /**
  41. * Returns the minimum height this node has to have
  42. */
  43. public override uint get_min_height(List<DockRenderer> dock_renderers,
  44. List<Gtk.Widget> children,
  45. int border_width) {
  46. uint mh = border_width*2;
  47. mh += this.get_title_line_height();
  48. uint source_height = 0;
  49. uint sink_height = 0;
  50. foreach (DockRenderer dock_renderer in dock_renderers) {
  51. if (dock_renderer.get_dock() is GFlow.Source)
  52. source_height += dock_renderer.get_min_height();
  53. if (dock_renderer.get_dock() is GFlow.Sink)
  54. sink_height += dock_renderer.get_min_height();
  55. }
  56. mh += uint.max(sink_height, source_height);
  57. Gtk.Widget child = children.nth_data(0);
  58. if (child != null) {
  59. int child_height, _;
  60. child.get_preferred_height(out child_height, out _);
  61. mh += child_height;
  62. }
  63. return mh;
  64. }
  65. /**
  66. * Returns the minimum width this node has to have
  67. */
  68. public override uint get_min_width(List<DockRenderer> dock_renderers,
  69. List<Gtk.Widget> children,
  70. int border_width) {
  71. uint mw = 0;
  72. int t = 0;
  73. if (this.layout.get_text() != "") {
  74. int width, height;
  75. this.layout.get_pixel_size(out width, out height);
  76. mw = width + title_spacing + delete_btn_size;
  77. }
  78. uint mw_sources = 0;
  79. uint mw_sinks = 0;
  80. foreach (DockRenderer dr in dock_renderers) {
  81. t = dr.get_min_width();
  82. if (dr.get_dock() is GFlow.Sink)
  83. mw_sinks = uint.max(t, mw_sinks);
  84. if (dr.get_dock() is GFlow.Source)
  85. mw_sources = uint.max(t, mw_sources);
  86. }
  87. mw = mw_sinks + mw_sources;
  88. Gtk.Widget child = children.nth_data(0);
  89. if (child != null) {
  90. int child_width, _;
  91. child.get_preferred_width(out child_width, out _);
  92. if (child_width > mw)
  93. mw = child_width;
  94. }
  95. return mw + border_width*2;
  96. }
  97. /**
  98. * Returns true if the point is on the close-button of the node
  99. */
  100. public override bool is_on_closebutton(Gdk.Point p,
  101. Gtk.Allocation alloc,
  102. uint border_width) {
  103. int x = p.x;
  104. int y = p.y;
  105. int x_left, x_right, y_top, y_bot;
  106. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  107. x_left = alloc.x + alloc.width - delete_btn_size
  108. - (int)border_width;
  109. x_right = x_left + delete_btn_size;
  110. y_top = alloc.y + (int)border_width;
  111. y_bot = y_top + delete_btn_size;
  112. } else {
  113. x_left = alloc.x + (int)border_width;
  114. x_right = x_left + delete_btn_size;
  115. y_top = alloc.y + (int)border_width;
  116. y_bot = y_top + delete_btn_size;
  117. }
  118. return x > x_left && x < x_right && y > y_top && y < y_bot;
  119. }
  120. /**
  121. * Returns true if the point is in the resize-drag area
  122. */
  123. public override bool is_on_resize_handle(Gdk.Point p,
  124. Gtk.Allocation alloc,
  125. uint border_width) {
  126. int x = p.x;
  127. int y = p.y;
  128. int x_right, x_left, y_bot, y_top;
  129. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  130. x_right = alloc.x + alloc.width;
  131. x_left = x_right - resize_handle_size;
  132. y_bot = alloc.y + alloc.height;
  133. y_top = y_bot - resize_handle_size;
  134. } else {
  135. x_left = alloc.x;
  136. x_right = x_left + resize_handle_size;
  137. y_bot = alloc.y + alloc.height;
  138. y_top = y_bot - resize_handle_size;
  139. }
  140. return x > x_left && x < x_right && y > y_top && y < y_bot;
  141. }
  142. /**
  143. * Returns the position of the given dock.
  144. * This is obviously bullshit. GFlow.Docks should be able to know
  145. * their own position
  146. */
  147. public override bool get_dock_position(GFlow.Dock d,
  148. List<DockRenderer> dock_renderers,
  149. int border_width,
  150. Gtk.Allocation alloc,
  151. out int x,
  152. out int y) {
  153. int i = 0;
  154. x = y = 0;
  155. uint title_offset = this.get_title_line_height();
  156. foreach(DockRenderer dock_renderer in dock_renderers) {
  157. GFlow.Dock s = dock_renderer.get_dock();
  158. if (s == d) {
  159. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  160. if (s is GFlow.Sink) {
  161. x += alloc.x + border_width
  162. + dock_renderer.dockpoint_height/2;
  163. y += alloc.y + border_width + (int)title_offset
  164. + dock_renderer.dockpoint_height/2 + i
  165. * dock_renderer.get_min_height();
  166. return true;
  167. } else if (s is GFlow.Source) {
  168. x += alloc.x - border_width
  169. + alloc.width - dock_renderer.dockpoint_height/2;
  170. y += alloc.y + border_width + (int)title_offset
  171. + dock_renderer.dockpoint_height/2 + i
  172. * dock_renderer.get_min_height();
  173. return true;
  174. }
  175. } else {
  176. if (s is GFlow.Sink) {
  177. x += alloc.x - border_width
  178. + alloc.width - dock_renderer.dockpoint_height/2;
  179. y += alloc.y + border_width + (int)title_offset
  180. + dock_renderer.dockpoint_height/2 + i
  181. * dock_renderer.get_min_height();
  182. return true;
  183. } else if (s is GFlow.Source) {
  184. x += alloc.x + border_width
  185. + dock_renderer.dockpoint_height/2;
  186. y += alloc.y + border_width + (int)title_offset
  187. + dock_renderer.dockpoint_height/2 + i
  188. * dock_renderer.get_min_height();
  189. return true;
  190. }
  191. }
  192. }
  193. if (s.get_type() == d.get_type()) {
  194. i++;
  195. }
  196. }
  197. return false;
  198. }
  199. /**
  200. * Determines whether the mousepointer is hovering over a dock on this node
  201. */
  202. public override GFlow.Dock? get_dock_on_position(Gdk.Point p,
  203. List<DockRenderer> dock_renderers,
  204. uint border_width,
  205. Gtk.Allocation alloc ) {
  206. int x = p.x;
  207. int y = p.y;
  208. int source_count = 0;
  209. int sink_count = 0;
  210. int dock_x, dock_y, mh;
  211. uint title_offset;
  212. title_offset = this.get_title_line_height();
  213. foreach (DockRenderer dock_renderer in dock_renderers) {
  214. GFlow.Dock s = dock_renderer.get_dock();
  215. mh = dock_renderer.get_min_height();
  216. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  217. if (s is GFlow.Sink) {
  218. dock_x = alloc.x + (int)border_width;
  219. dock_y = alloc.y + (int)border_width + (int)title_offset
  220. + sink_count * mh;
  221. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  222. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  223. return s;
  224. sink_count++;
  225. } else if (s is GFlow.Source) {
  226. dock_x = alloc.x + alloc.width
  227. - (int)border_width
  228. - dock_renderer.dockpoint_height;
  229. dock_y = alloc.y + (int)border_width + (int)title_offset
  230. + source_count * mh;
  231. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  232. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  233. return s;
  234. source_count++;
  235. }
  236. } else {
  237. if (s is GFlow.Sink) {
  238. dock_x = alloc.x + alloc.width
  239. - (int)border_width
  240. - dock_renderer.dockpoint_height;
  241. dock_y = alloc.y + (int)border_width + (int)title_offset
  242. + sink_count * mh;
  243. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  244. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  245. return s;
  246. sink_count++;
  247. } else if (s is GFlow.Source) {
  248. dock_x = alloc.x + (int)border_width;
  249. dock_y = alloc.y + (int)border_width + (int)title_offset
  250. + source_count * mh;
  251. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  252. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  253. return s;
  254. source_count++;
  255. }
  256. }
  257. }
  258. return null;
  259. }
  260. /**
  261. * Returns a Gtk.StyleContext matching a given selector
  262. */
  263. private Gtk.StyleContext get_style() {
  264. var b = new Gtk.Button();
  265. return b.get_style_context();
  266. }
  267. /**
  268. * Draw this node on the given cairo context
  269. */
  270. public override void draw_node(Gtk.Widget w, Cairo.Context cr,
  271. Gtk.Allocation alloc,
  272. List<DockRenderer> dock_renderers,
  273. List<Gtk.Widget> children,
  274. int border_width,
  275. NodeProperties node_properties) {
  276. bool editable = node_properties.editable;
  277. bool deletable = node_properties.deletable;
  278. bool resizable = node_properties.resizable;
  279. bool selected = node_properties.selected;
  280. var sc = this.get_style();
  281. sc.save();
  282. sc.render_background(cr, alloc.x, alloc.y, alloc.width, alloc.height);
  283. sc.render_frame(cr, alloc.x, alloc.y, alloc.width, alloc.height);
  284. sc.restore();
  285. int y_offset = 0;
  286. if (this.layout.get_text() != "") {
  287. sc.save();
  288. cr.save();
  289. sc.add_class(Gtk.STYLE_CLASS_BUTTON);
  290. Gdk.RGBA col = sc.get_color(Gtk.StateFlags.NORMAL);
  291. cr.set_source_rgba(col.red,col.green,col.blue,col.alpha);
  292. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  293. cr.move_to(alloc.x + border_width,
  294. alloc.y + (int) border_width + y_offset);
  295. } else {
  296. cr.move_to(alloc.x + 2*border_width + delete_btn_size,
  297. alloc.y + (int) border_width + y_offset);
  298. }
  299. Pango.cairo_show_layout(cr, this.layout);
  300. cr.restore();
  301. sc.restore();
  302. }
  303. if (editable && deletable) {
  304. Gtk.IconTheme it = Gtk.IconTheme.get_default();
  305. try {
  306. cr.save();
  307. Gdk.Pixbuf icon_pix = it.load_icon("edit-delete", delete_btn_size, 0);
  308. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  309. Gdk.cairo_set_source_pixbuf(
  310. cr, icon_pix,
  311. alloc.x+alloc.width-delete_btn_size-border_width,
  312. alloc.y+border_width
  313. );
  314. } else {
  315. Gdk.cairo_set_source_pixbuf(
  316. cr, icon_pix,
  317. alloc.x+border_width,
  318. alloc.y+border_width
  319. );
  320. }
  321. cr.paint();
  322. } catch (GLib.Error e) {
  323. warning("Could not load close-node-icon 'edit-delete'");
  324. } finally {
  325. cr.restore();
  326. }
  327. }
  328. y_offset += (int)this.get_title_line_height();
  329. int y_offset_sources = y_offset;
  330. int x_offset_sources = 0;
  331. foreach (DockRenderer dock_renderer in dock_renderers) {
  332. if (dock_renderer.get_dock() is GFlow.Sink) {
  333. x_offset_sources = int.max(x_offset_sources, dock_renderer.get_min_width());
  334. }
  335. }
  336. foreach (DockRenderer dock_renderer in dock_renderers) {
  337. if (dock_renderer.get_dock() is GFlow.Sink) {
  338. dock_renderer.draw_dock(w, cr, sc, alloc.x + (int)border_width,
  339. alloc.y+y_offset + (int) border_width, alloc.width);
  340. y_offset += dock_renderer.get_min_height();
  341. } else if (dock_renderer.get_dock() is GFlow.Source) {
  342. dock_renderer.draw_dock(w, cr, sc, alloc.x-(int)border_width,
  343. alloc.y+y_offset_sources + (int) border_width, alloc.width);
  344. y_offset_sources += dock_renderer.get_min_height();
  345. }
  346. }
  347. y_offset = int.max(y_offset, y_offset_sources);
  348. Gtk.Widget child = children.nth_data(0);
  349. if (child != null) {
  350. Gtk.Allocation child_alloc = {0,0,0,0};
  351. child_alloc.x = (int)border_width;
  352. child_alloc.y = (int)border_width + y_offset;
  353. child_alloc.width = alloc.width - 2 * (int)border_width;
  354. child_alloc.height = alloc.height - 2 * (int)border_width - y_offset;
  355. child.size_allocate(child_alloc);
  356. this.child_redraw(child);
  357. }
  358. // Draw resize handle
  359. if (resizable) {
  360. sc.save();
  361. cr.save();
  362. cr.set_source_rgba(0.5,0.5,0.5,0.5);
  363. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  364. cr.move_to(alloc.x + alloc.width,
  365. alloc.y + alloc.height);
  366. cr.line_to(alloc.x + alloc.width - resize_handle_size,
  367. alloc.y + alloc.height);
  368. cr.line_to(alloc.x + alloc.width,
  369. alloc.y + alloc.height - resize_handle_size);
  370. } else {
  371. cr.move_to(alloc.x,
  372. alloc.y + alloc.height);
  373. cr.line_to(alloc.x + resize_handle_size,
  374. alloc.y + alloc.height);
  375. cr.line_to(alloc.x,
  376. alloc.y + alloc.height - resize_handle_size);
  377. }
  378. cr.fill();
  379. cr.stroke();
  380. cr.restore();
  381. sc.restore();
  382. }
  383. if (selected) {
  384. draw_rubberband(w, cr, alloc.x, alloc.y, Gtk.StateFlags.NORMAL, &alloc.width, &alloc.height);
  385. }
  386. }
  387. }
  388. }