Line data Source code
1 : /* File: gui_sketch_nav_tree.c; Copyright and License: see below */
2 :
3 : #include "sketch/gui_sketch_nav_tree.h"
4 : #include "geometry/geometry_rectangle.h"
5 : #include "u8/u8_trace.h"
6 : #include "u8/u8_log.h"
7 : #include "u8/u8_i32.h"
8 : #include <gdk/gdk.h>
9 :
10 : static const int GUI_SKETCH_NAV_TREE_INDENT = 12;
11 : static const int OBJ_GAP = 3;
12 : static const int GAP_HEIGHT = 2;
13 : static const int GUI_SKETCH_NAV_TREE_PANGO_AUTO_DETECT_LENGTH = -1; /*!< pango automatically determines the string length */
14 :
15 4 : void gui_sketch_nav_tree_init( gui_sketch_nav_tree_t *this_,
16 : gui_resources_t *resources,
17 : gui_sketch_texture_t *texture_downloader )
18 : {
19 4 : U8_TRACE_BEGIN();
20 4 : assert( resources != NULL );
21 4 : assert( texture_downloader != NULL );
22 :
23 4 : (*this_).ancestors_count = 0;
24 4 : (*this_).siblings_count = 0;
25 4 : (*this_).children_count = 0;
26 4 : (*this_).siblings_self_index = -1;
27 :
28 4 : (*this_).node_count = 0;
29 4 : (*this_).gap_count = 0;
30 :
31 4 : (*this_).visible = false;
32 4 : shape_int_rectangle_init( &((*this_).bounds), 0, 0, 0, 0 );
33 :
34 4 : gui_sketch_style_init( &((*this_).sketch_style) );
35 4 : gui_sketch_marker_init( &((*this_).sketch_marker), true );
36 4 : (*this_).resources = resources;
37 4 : (*this_).texture_downloader = texture_downloader;
38 :
39 4 : U8_TRACE_END();
40 4 : }
41 :
42 4 : void gui_sketch_nav_tree_destroy( gui_sketch_nav_tree_t *this_ )
43 : {
44 4 : U8_TRACE_BEGIN();
45 :
46 4 : (*this_).resources = NULL;
47 4 : (*this_).texture_downloader = NULL;
48 4 : gui_sketch_marker_destroy( &((*this_).sketch_marker) );
49 4 : gui_sketch_style_destroy( &((*this_).sketch_style) );
50 :
51 4 : gui_sketch_nav_tree_invalidate_data( this_ );
52 :
53 4 : shape_int_rectangle_destroy( &((*this_).bounds) );
54 :
55 4 : U8_TRACE_END();
56 4 : }
57 :
58 0 : void gui_sketch_nav_tree_load_data( gui_sketch_nav_tree_t *this_, data_row_id_t diagram_id, data_database_reader_t *db_reader )
59 : {
60 0 : U8_TRACE_BEGIN();
61 0 : assert( NULL != db_reader );
62 :
63 0 : gui_sketch_nav_tree_invalidate_data( this_ );
64 :
65 0 : u8_error_t db_err = U8_ERROR_NONE;
66 :
67 : /* get ancestors */
68 0 : bool finished = false;
69 0 : for ( unsigned int anc_index = 0; ( anc_index < GUI_SKETCH_NAV_TREE_CONST_MAX_ANCESTORS ) && ( db_err == U8_ERROR_NONE ) && ( ! finished ); anc_index ++ )
70 : {
71 : data_row_id_t id_to_load;
72 0 : if ( anc_index == 0 )
73 : {
74 0 : id_to_load = diagram_id;
75 : }
76 : else
77 : {
78 0 : id_to_load = data_diagram_get_parent_row_id( &((*this_).ancestor_diagrams[anc_index-1]) );
79 : }
80 :
81 0 : if ( id_to_load != DATA_ROW_ID_VOID )
82 : {
83 0 : db_err = data_database_reader_get_diagram_by_id ( db_reader, id_to_load, &((*this_).ancestor_diagrams[anc_index]) );
84 0 : if ( db_err == U8_ERROR_NONE )
85 : {
86 0 : (*this_).ancestors_count = anc_index+1;
87 : }
88 : }
89 : else
90 : {
91 0 : finished = true;
92 : }
93 : }
94 :
95 0 : if ( diagram_id != DATA_ROW_ID_VOID )
96 : {
97 0 : if ( (*this_).ancestors_count == 0 )
98 : {
99 0 : db_err = U8_ERROR_INVALID_REQUEST;
100 0 : U8_LOG_ANOMALY_INT( "gui_sketch_nav_tree_load_data cannot load diagram", diagram_id );
101 : }
102 : }
103 :
104 : /* get siblings */
105 0 : if ( db_err == U8_ERROR_NONE )
106 : {
107 0 : const data_row_id_t parent_id
108 0 : = ( (*this_).ancestors_count == 0 )
109 : ? DATA_ROW_ID_VOID
110 0 : : data_diagram_get_parent_row_id( &((*this_).ancestor_diagrams[0]) );
111 0 : db_err = data_database_reader_get_diagrams_by_parent_id ( db_reader,
112 : parent_id,
113 : GUI_SKETCH_NAV_TREE_CONST_MAX_SIBLINGS,
114 0 : &( (*this_).sibling_diagrams ),
115 : &( (*this_).siblings_count )
116 : );
117 :
118 : /* search self in list of siblings */
119 : {
120 0 : assert( (*this_).siblings_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_SIBLINGS );
121 :
122 0 : (*this_).siblings_self_index = -1;
123 0 : for ( int sib_index = 0; sib_index < (*this_).siblings_count; sib_index ++ )
124 : {
125 0 : if ( diagram_id == data_diagram_get_row_id( &((*this_).sibling_diagrams[sib_index]) ) )
126 : {
127 0 : (*this_).siblings_self_index = sib_index;
128 : }
129 : }
130 : }
131 : }
132 :
133 : /* get children */
134 0 : if ( db_err == U8_ERROR_NONE )
135 : {
136 0 : db_err = data_database_reader_get_diagrams_by_parent_id ( db_reader,
137 : diagram_id,
138 : GUI_SKETCH_NAV_TREE_CONST_MAX_CHILDREN,
139 0 : &( (*this_).child_diagrams ),
140 : &( (*this_).children_count )
141 : );
142 : }
143 :
144 : /* invalidate layout positions */
145 : {
146 0 : (*this_).node_count = 0;
147 0 : (*this_).gap_count = 0;
148 : }
149 :
150 0 : U8_TRACE_END();
151 0 : }
152 :
153 4 : void gui_sketch_nav_tree_do_layout( gui_sketch_nav_tree_t *this_, cairo_t *cr )
154 : {
155 4 : U8_TRACE_BEGIN();
156 4 : assert( (*this_).ancestors_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_ANCESTORS );
157 4 : assert( (*this_).siblings_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_SIBLINGS );
158 4 : assert( (*this_).children_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_CHILDREN );
159 :
160 : /* create the font_layout */
161 : PangoLayout *font_layout;
162 : {
163 4 : font_layout = pango_cairo_create_layout (cr);
164 : const PangoFontDescription *const std_font
165 4 : = gui_sketch_style_get_standard_font_description( &((*this_).sketch_style ) );
166 4 : pango_layout_set_font_description( font_layout, std_font );
167 : }
168 :
169 4 : int32_t y_pos = shape_int_rectangle_get_top( &((*this_).bounds) ) + OBJ_GAP;
170 4 : const int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
171 4 : const uint_fast32_t width = shape_int_rectangle_get_width( &((*this_).bounds) );
172 :
173 4 : (*this_).node_count = 0;
174 4 : (*this_).gap_count = 0;
175 :
176 4 : if ( (*this_).ancestors_count == 0 )
177 : {
178 1 : pos_nav_tree_node_t *const new_root_node = &((*this_).node_pos[0]);
179 1 : (*this_).node_count = 1;
180 :
181 : /* show only a new root button */
182 : assert( 1 <= GUI_SKETCH_NAV_TREE_CONST_MAX_NODES );
183 1 : pos_nav_tree_node_init( new_root_node, POS_NAV_TREE_NODE_TYPE_NEW_ROOT, NULL );
184 1 : gui_sketch_nav_tree_private_layout_node( this_, new_root_node, 0, &y_pos, font_layout );
185 :
186 : /* no gaps in this case */
187 1 : assert( 0 == (*this_).gap_count );
188 1 : assert( 1 == (*this_).node_count );
189 : }
190 : else
191 : {
192 : /* layout ancestors excluding self (at index 0 in ancestor_diagrams) */
193 3 : const unsigned int anc_count = (*this_).ancestors_count;
194 3 : assert( anc_count > 0 );
195 3 : assert( anc_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_ANCESTORS );
196 3 : assert( (*this_).node_count + (anc_count-1) <= GUI_SKETCH_NAV_TREE_CONST_MAX_NODES );
197 6 : for ( unsigned int anc_idx = 1; anc_idx < anc_count; anc_idx ++ )
198 : {
199 3 : const data_diagram_t *const anc_diag = &((*this_).ancestor_diagrams[anc_count-anc_idx]);
200 :
201 : /* layout upper-gap of ancestor */
202 : {
203 3 : pos_nav_tree_gap_t *const upper_gap = &((*this_).gap_pos[(*this_).gap_count]);
204 3 : (*this_).gap_count ++;
205 :
206 3 : pos_nav_tree_gap_init( upper_gap, data_diagram_get_parent_data_id(anc_diag), 0 );
207 3 : const int indent = (anc_idx-1) * GUI_SKETCH_NAV_TREE_INDENT;
208 :
209 3 : pos_nav_tree_gap_set_gap_box_coords( upper_gap, left+indent, y_pos, width-indent, GAP_HEIGHT );
210 3 : y_pos += GAP_HEIGHT;
211 : }
212 :
213 : /* layout ancestor node */
214 : {
215 3 : pos_nav_tree_node_t *const anc_node = &((*this_).node_pos[(*this_).node_count]);
216 3 : (*this_).node_count ++;
217 :
218 3 : const pos_nav_tree_node_type_t n_type
219 3 : = ((anc_idx+1)==anc_count) ? POS_NAV_TREE_NODE_TYPE_OPEN : POS_NAV_TREE_NODE_TYPE_ANCESTOR;
220 3 : pos_nav_tree_node_init( anc_node, n_type, anc_diag );
221 3 : gui_sketch_nav_tree_private_layout_node( this_, anc_node, (anc_idx-1), &y_pos, font_layout );
222 : }
223 : }
224 :
225 3 : const unsigned int tree_depth = (*this_).ancestors_count-1;
226 :
227 : /* layout siblings including self */
228 3 : const unsigned int sibl_count = (*this_).siblings_count;
229 3 : assert( sibl_count > 0 );
230 3 : assert( sibl_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_SIBLINGS );
231 3 : const unsigned int child_count = (*this_).children_count;
232 3 : assert( child_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_CHILDREN );
233 3 : assert( (*this_).node_count + sibl_count + child_count + 2 <= GUI_SKETCH_NAV_TREE_CONST_MAX_NODES );
234 3 : int32_t previous_sibl_order = INT32_MIN;
235 8 : for ( unsigned int sibl_idx = 0; sibl_idx < sibl_count; sibl_idx ++ )
236 : {
237 5 : const data_diagram_t *const sibl_diag = &((*this_).sibling_diagrams[sibl_idx]);
238 5 : const bool is_self = ( sibl_idx == (*this_).siblings_self_index );
239 :
240 : /* layout upper-gap of sibling */
241 : {
242 5 : pos_nav_tree_gap_t *const upper_gap = &((*this_).gap_pos[(*this_).gap_count]);
243 5 : (*this_).gap_count ++;
244 :
245 5 : const int32_t gap_sibl_order
246 5 : = (previous_sibl_order/2)+(data_diagram_get_list_order( sibl_diag )/2); /* no overrun */
247 5 : pos_nav_tree_gap_init( upper_gap, data_diagram_get_parent_data_id(sibl_diag), gap_sibl_order );
248 5 : const int indent = tree_depth * GUI_SKETCH_NAV_TREE_INDENT;
249 5 : pos_nav_tree_gap_set_gap_box_coords( upper_gap, left+indent, y_pos, width-indent, GAP_HEIGHT );
250 5 : y_pos += GAP_HEIGHT;
251 : }
252 :
253 : /* layout sibling node */
254 : {
255 5 : pos_nav_tree_node_t *const sibl_node = &((*this_).node_pos[(*this_).node_count]);
256 5 : (*this_).node_count ++;
257 :
258 5 : const pos_nav_tree_node_type_t n_type = is_self ? POS_NAV_TREE_NODE_TYPE_OPEN : POS_NAV_TREE_NODE_TYPE_CLOSED;
259 5 : pos_nav_tree_node_init( sibl_node, n_type, sibl_diag );
260 5 : gui_sketch_nav_tree_private_layout_node( this_, sibl_node, tree_depth, &y_pos, font_layout );
261 : }
262 :
263 : /* layout children */
264 5 : if ( is_self )
265 : {
266 3 : int32_t prev_child_order = INT32_MIN;
267 4 : for ( unsigned int chld_idx = 0; chld_idx < child_count; chld_idx ++ )
268 : {
269 1 : const data_diagram_t *const chld_diag = &((*this_).child_diagrams[chld_idx]);
270 :
271 : /* layout upper-gap of child */
272 : {
273 1 : pos_nav_tree_gap_t *const upper_gap = &((*this_).gap_pos[(*this_).gap_count]);
274 1 : (*this_).gap_count ++;
275 :
276 1 : const int32_t gap_child_order
277 1 : = (prev_child_order/2)+(data_diagram_get_list_order( chld_diag )/2); /* no overrun */
278 1 : pos_nav_tree_gap_init( upper_gap, data_diagram_get_parent_data_id(chld_diag), gap_child_order );
279 1 : const int indent = (tree_depth+1) * GUI_SKETCH_NAV_TREE_INDENT;
280 1 : pos_nav_tree_gap_set_gap_box_coords( upper_gap, left+indent, y_pos, width-indent, GAP_HEIGHT );
281 1 : y_pos += GAP_HEIGHT;
282 : }
283 :
284 : /* layout child node */
285 : {
286 1 : pos_nav_tree_node_t *const chld_node = &((*this_).node_pos[(*this_).node_count]);
287 1 : (*this_).node_count ++;
288 :
289 1 : pos_nav_tree_node_init( chld_node, POS_NAV_TREE_NODE_TYPE_CLOSED, chld_diag );
290 1 : gui_sketch_nav_tree_private_layout_node( this_, chld_node, tree_depth+1, &y_pos, font_layout );
291 : }
292 :
293 1 : prev_child_order = data_diagram_get_list_order( sibl_diag );
294 : }
295 :
296 : /* layout lower-gap of child (if any) */
297 : {
298 3 : pos_nav_tree_gap_t *const lower_gap = &((*this_).gap_pos[(*this_).gap_count]);
299 3 : (*this_).gap_count ++;
300 :
301 3 : const int32_t gap_child_order = (prev_child_order/2)+(INT32_MAX/2); /* no overrun */
302 3 : pos_nav_tree_gap_init( lower_gap, data_diagram_get_data_id(sibl_diag), gap_child_order );
303 3 : const int indent = (tree_depth+1) * GUI_SKETCH_NAV_TREE_INDENT;
304 3 : pos_nav_tree_gap_set_gap_box_coords( lower_gap, left+indent, y_pos, width-indent, GAP_HEIGHT );
305 3 : y_pos += GAP_HEIGHT;
306 : }
307 :
308 : /* add a new child button */
309 : {
310 3 : pos_nav_tree_node_t *const new_chld_node = &((*this_).node_pos[(*this_).node_count]);
311 3 : (*this_).node_count ++;
312 :
313 3 : pos_nav_tree_node_init( new_chld_node, POS_NAV_TREE_NODE_TYPE_NEW_CHILD, NULL );
314 3 : gui_sketch_nav_tree_private_layout_node( this_, new_chld_node, tree_depth+1, &y_pos, font_layout );
315 : }
316 : }
317 :
318 5 : previous_sibl_order = data_diagram_get_list_order( sibl_diag );
319 : }
320 :
321 3 : const bool self_is_root = ( (*this_).ancestors_count == 1 );
322 3 : if ( ! self_is_root )
323 : {
324 : /* layout lower-gap of siblings */
325 2 : const data_diagram_t *const self_diag = &((*this_).ancestor_diagrams[0]);
326 : {
327 2 : pos_nav_tree_gap_t *const lower_gap = &((*this_).gap_pos[(*this_).gap_count]);
328 2 : (*this_).gap_count ++;
329 :
330 2 : const int32_t gap_sibl_order = (previous_sibl_order/2)+(INT32_MAX/2); /* no overrun */
331 2 : pos_nav_tree_gap_init( lower_gap, data_diagram_get_parent_data_id(self_diag), gap_sibl_order );
332 2 : const int indent = tree_depth * GUI_SKETCH_NAV_TREE_INDENT;
333 2 : pos_nav_tree_gap_set_gap_box_coords( lower_gap, left+indent, y_pos, width-indent, GAP_HEIGHT );
334 2 : y_pos += GAP_HEIGHT;
335 : }
336 :
337 : /* show a new sibling button unless root */
338 : {
339 2 : pos_nav_tree_node_t *const new_sibl_node = &((*this_).node_pos[(*this_).node_count]);
340 2 : (*this_).node_count ++;
341 :
342 2 : pos_nav_tree_node_init( new_sibl_node, POS_NAV_TREE_NODE_TYPE_NEW_SIBLING, NULL );
343 2 : gui_sketch_nav_tree_private_layout_node( this_, new_sibl_node, tree_depth, &y_pos, font_layout );
344 : }
345 : }
346 :
347 3 : assert( (*this_).gap_count == (*this_).node_count );
348 : }
349 :
350 : /* release the font_layout */
351 4 : g_object_unref(font_layout);
352 :
353 4 : U8_TRACE_END();
354 4 : }
355 :
356 15 : void gui_sketch_nav_tree_private_layout_node ( gui_sketch_nav_tree_t *this_,
357 : pos_nav_tree_node_t *node,
358 : uint32_t tree_depth,
359 : int32_t *io_y_pos,
360 : PangoLayout *font_layout )
361 : {
362 15 : U8_TRACE_BEGIN();
363 15 : assert( NULL != node );
364 15 : assert( NULL != io_y_pos );
365 15 : assert( NULL != font_layout );
366 :
367 15 : const int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
368 15 : const uint_fast32_t width = shape_int_rectangle_get_width( &((*this_).bounds) );
369 15 : const uint_fast32_t indent = tree_depth*GUI_SKETCH_NAV_TREE_INDENT;
370 15 : const data_diagram_t *data_or_null = pos_nav_tree_node_get_data_const( node );
371 :
372 : /* determine icon dimensions */
373 : {
374 15 : const pos_nav_tree_node_type_t node_type = pos_nav_tree_node_get_type( node );
375 15 : GdkTexture *icon = pos_nav_tree_node_type_get_icon( node_type, false, (*this_).resources );
376 15 : const double icon_width = gdk_texture_get_width( icon );
377 15 : const double icon_height = gdk_texture_get_height( icon );
378 :
379 15 : const shape_int_rectangle_t new_icon_box = (shape_int_rectangle_t) {
380 15 : .left=left+indent+OBJ_GAP,
381 15 : .top=(*io_y_pos)+OBJ_GAP,
382 15 : .width=icon_width+0.999,
383 15 : .height=icon_height+0.999 };
384 15 : pos_nav_tree_node_set_icon_box( node, &new_icon_box );
385 : }
386 :
387 : /* determine label dimensions */
388 15 : const shape_int_rectangle_t *const icon_box = pos_nav_tree_node_get_icon_box_const( node );
389 : shape_int_rectangle_t new_label_box;
390 15 : if ( data_or_null == NULL )
391 : {
392 6 : shape_int_rectangle_init( &new_label_box, shape_int_rectangle_get_right(icon_box), (*io_y_pos), 0, 0 );
393 : }
394 : else
395 : {
396 9 : int_fast32_t proposed_pango_width = width - indent - shape_int_rectangle_get_width(icon_box) - (4*OBJ_GAP);
397 9 : pango_layout_set_text( font_layout,
398 : data_diagram_get_name_const( data_or_null ),
399 : GUI_SKETCH_NAV_TREE_PANGO_AUTO_DETECT_LENGTH
400 : );
401 9 : pango_layout_set_width(font_layout, proposed_pango_width * PANGO_SCALE );
402 : int text_width;
403 : int text_height;
404 9 : pango_layout_get_pixel_size(font_layout, &text_width, &text_height);
405 :
406 9 : int_fast32_t x_pos = shape_int_rectangle_get_right(icon_box);
407 9 : shape_int_rectangle_init( &new_label_box, x_pos+OBJ_GAP, (*io_y_pos)+OBJ_GAP, text_width, text_height );
408 : }
409 15 : pos_nav_tree_node_set_label_box( node, &new_label_box );
410 :
411 : *io_y_pos
412 15 : = u8_i32_max2( shape_int_rectangle_get_bottom(icon_box), shape_int_rectangle_get_bottom(&new_label_box) )
413 15 : + OBJ_GAP;
414 :
415 15 : shape_int_rectangle_destroy( &new_label_box );
416 :
417 15 : U8_TRACE_END();
418 15 : }
419 :
420 4 : void gui_sketch_nav_tree_invalidate_data( gui_sketch_nav_tree_t *this_ )
421 : {
422 4 : U8_TRACE_BEGIN();
423 4 : assert( (*this_).ancestors_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_ANCESTORS );
424 4 : assert( (*this_).siblings_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_SIBLINGS );
425 4 : assert( (*this_).children_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_CHILDREN );
426 :
427 : /* clear data */
428 10 : for ( int anc_index = 0; anc_index < (*this_).ancestors_count; anc_index ++ )
429 : {
430 6 : data_diagram_destroy( &((*this_).ancestor_diagrams[anc_index]) );
431 : }
432 4 : (*this_).ancestors_count = 0;
433 :
434 9 : for ( int sib_index = 0; sib_index < (*this_).siblings_count; sib_index ++ )
435 : {
436 5 : data_diagram_destroy( &((*this_).sibling_diagrams[sib_index]) );
437 : }
438 4 : (*this_).siblings_count = 0;
439 4 : (*this_).siblings_self_index = -1;
440 :
441 5 : for ( int chi_index = 0; chi_index < (*this_).children_count; chi_index ++ )
442 : {
443 1 : data_diagram_destroy( &((*this_).child_diagrams[chi_index]) );
444 : }
445 4 : (*this_).children_count = 0;
446 :
447 : /* clear layout infos */
448 4 : assert( (*this_).node_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_NODES );
449 19 : for ( int node_index = 0; node_index < (*this_).node_count; node_index ++ )
450 : {
451 15 : pos_nav_tree_node_destroy( &((*this_).node_pos[node_index]) );
452 : }
453 4 : (*this_).node_count = 0;
454 :
455 4 : assert( (*this_).gap_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_GAPS );
456 18 : for ( int gap_index = 0; gap_index < (*this_).gap_count; gap_index ++ )
457 : {
458 14 : pos_nav_tree_gap_destroy( &((*this_).gap_pos[gap_index]) );
459 : }
460 4 : (*this_).gap_count = 0;
461 :
462 4 : U8_TRACE_END();
463 4 : }
464 :
465 18 : gui_error_t gui_sketch_nav_tree_get_gap_info_at_pos ( const gui_sketch_nav_tree_t *this_,
466 : int32_t x,
467 : int32_t y,
468 : data_id_t *out_parent_id,
469 : int32_t *out_list_order,
470 : shape_int_rectangle_t *out_gap_line )
471 : {
472 18 : U8_TRACE_BEGIN();
473 18 : assert ( NULL != out_parent_id );
474 18 : assert ( NULL != out_list_order );
475 18 : assert ( NULL != out_gap_line );
476 :
477 : gui_error_t ret_error;
478 :
479 : /* search closest gap */
480 18 : if ( shape_int_rectangle_contains( &((*this_).bounds), x, y ) )
481 : {
482 18 : const unsigned int gap_count = (*this_).gap_count;
483 18 : assert( gap_count <= GUI_SKETCH_NAV_TREE_CONST_MAX_GAPS );
484 18 : if ( gap_count == 0 )
485 : {
486 1 : ret_error = GUI_ERROR_OUT_OF_BOUNDS;
487 : }
488 : else
489 : {
490 17 : const pos_nav_tree_gap_t *closest = &((*this_).gap_pos[0]);
491 17 : int closest_dist = INT32_MAX;
492 109 : for ( unsigned int idx = 0; idx < gap_count; idx ++ )
493 : {
494 92 : const pos_nav_tree_gap_t *const current = &((*this_).gap_pos[idx]);
495 92 : const shape_int_rectangle_t *const current_box = pos_nav_tree_gap_get_gap_box_const( current );
496 92 : const int current_dist
497 92 : = ( y < shape_int_rectangle_get_top( current_box ) )
498 32 : ? ( shape_int_rectangle_get_top( current_box ) - y )
499 184 : : (( y > shape_int_rectangle_get_bottom( current_box ) )
500 60 : ? ( y - shape_int_rectangle_get_bottom( current_box ) )
501 120 : : 0 );
502 92 : if ( current_dist < closest_dist )
503 : {
504 60 : closest = current;
505 60 : closest_dist = current_dist;
506 : }
507 : }
508 17 : *out_parent_id = pos_nav_tree_gap_get_parent_id( closest );
509 17 : *out_list_order = pos_nav_tree_gap_get_list_order( closest );
510 17 : shape_int_rectangle_replace( out_gap_line, pos_nav_tree_gap_get_gap_box_const( closest ) );
511 17 : ret_error = GUI_ERROR_NONE;
512 : }
513 : }
514 : else
515 : {
516 0 : ret_error = GUI_ERROR_OUT_OF_BOUNDS;
517 : }
518 :
519 18 : U8_TRACE_END_ERR( ret_error );
520 18 : return ret_error;
521 : }
522 :
523 15 : void gui_sketch_nav_tree_get_object_id_at_pos ( const gui_sketch_nav_tree_t *this_,
524 : int32_t x,
525 : int32_t y,
526 : data_id_t *out_selected_id )
527 : {
528 15 : U8_TRACE_BEGIN();
529 15 : assert ( NULL != out_selected_id );
530 :
531 : /* default in case no object found */
532 : {
533 15 : data_id_reinit_void( out_selected_id );
534 : }
535 :
536 : /* search object */
537 15 : if ( shape_int_rectangle_contains( &((*this_).bounds), x, y ) )
538 : {
539 15 : const unsigned int count = (*this_).node_count;
540 15 : assert( count <= GUI_SKETCH_NAV_TREE_CONST_MAX_NODES );
541 56 : for ( unsigned int idx = 0; idx < count; idx ++ )
542 : {
543 50 : const pos_nav_tree_node_t *const node = &((*this_).node_pos[idx]);
544 50 : const shape_int_rectangle_t *icon_box = pos_nav_tree_node_get_icon_box_const( node );
545 50 : const shape_int_rectangle_t *label_box = pos_nav_tree_node_get_label_box_const( node );
546 :
547 50 : if ( shape_int_rectangle_contains( icon_box, x, y ) || shape_int_rectangle_contains( label_box, x, y ) )
548 : {
549 : /* const pos_nav_tree_node_type_t node_type = pos_nav_tree_node_get_type( node ); */
550 15 : const data_diagram_t *const data_or_null = pos_nav_tree_node_get_data_const( node );
551 15 : if ( data_or_null != NULL )
552 : {
553 9 : *out_selected_id = data_diagram_get_data_id( data_or_null );
554 9 : break;
555 : }
556 : }
557 : }
558 : }
559 :
560 15 : U8_TRACE_END();
561 15 : }
562 :
563 : static const double GREY_R = 0.8;
564 : static const double GREY_G = 0.8;
565 : static const double GREY_B = 0.8;
566 : static const double GREY_A = 1.0;
567 :
568 0 : void gui_sketch_nav_tree_draw ( gui_sketch_nav_tree_t *this_, gui_marked_set_t *marker, cairo_t *cr )
569 : {
570 0 : U8_TRACE_BEGIN();
571 0 : assert( NULL != marker );
572 0 : assert( NULL != cr );
573 :
574 0 : if ( (*this_).visible )
575 : {
576 0 : PangoLayout *font_layout = pango_cairo_create_layout (cr);
577 : {
578 : const PangoFontDescription *const std_font
579 0 : = gui_sketch_style_get_standard_font_description( &((*this_).sketch_style ) );
580 0 : pango_layout_set_font_description ( font_layout, std_font );
581 : }
582 :
583 : /* draw background */
584 : {
585 0 : const int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
586 0 : const int_fast32_t top = shape_int_rectangle_get_top( &((*this_).bounds) );
587 0 : const uint_fast32_t width = shape_int_rectangle_get_width( &((*this_).bounds) );
588 0 : const uint_fast32_t height = shape_int_rectangle_get_height( &((*this_).bounds) );
589 :
590 0 : cairo_set_source_rgba( cr, GREY_R, GREY_G, GREY_B, GREY_A );
591 0 : cairo_rectangle ( cr, left, top, width, height );
592 0 : cairo_fill (cr);
593 : }
594 :
595 : /* draw icons and text */
596 0 : const unsigned int count = (*this_).node_count;
597 0 : assert( count <= GUI_SKETCH_NAV_TREE_CONST_MAX_NODES );
598 0 : for ( unsigned int idx = 0; idx < count; idx ++ )
599 : {
600 0 : const pos_nav_tree_node_t *const node = &((*this_).node_pos[idx]);
601 0 : gui_sketch_nav_tree_private_draw_node( this_, node, marker, font_layout, cr );
602 : }
603 :
604 0 : g_object_unref(font_layout);
605 : }
606 :
607 0 : U8_TRACE_END();
608 0 : }
609 :
610 0 : void gui_sketch_nav_tree_private_draw_node( gui_sketch_nav_tree_t *this_,
611 : const pos_nav_tree_node_t *node,
612 : const gui_marked_set_t *marker,
613 : PangoLayout *font_layout,
614 : cairo_t *cr )
615 : {
616 0 : U8_TRACE_BEGIN();
617 0 : assert( NULL != cr );
618 0 : assert( NULL != node );
619 0 : assert( NULL != marker );
620 0 : assert( NULL != font_layout );
621 :
622 0 : const data_diagram_t *const diag_or_null = pos_nav_tree_node_get_data_const( node );
623 :
624 : /* draw marker and set color */
625 0 : if ( diag_or_null != NULL )
626 : {
627 : shape_int_rectangle_t destination_rect;
628 0 : shape_int_rectangle_init_by_bounds( &destination_rect,
629 : pos_nav_tree_node_get_icon_box_const(node),
630 : pos_nav_tree_node_get_label_box_const(node)
631 : );
632 :
633 0 : gui_sketch_marker_prepare_draw( &((*this_).sketch_marker),
634 : data_diagram_get_data_id( diag_or_null ),
635 : marker,
636 : destination_rect,
637 : cr
638 : );
639 :
640 0 : shape_int_rectangle_destroy( &destination_rect );
641 : }
642 : else
643 : {
644 0 : const GdkRGBA std_color = gui_sketch_style_get_standard_color( &((*this_).sketch_style) );
645 0 : cairo_set_source_rgba( cr, std_color.red, std_color.green, std_color.blue, std_color.alpha );
646 : }
647 :
648 : /* draw text first, use the above set color and font */
649 0 : if ( diag_or_null != NULL )
650 : {
651 : /* what to draw */
652 0 : const char *const label = data_diagram_get_name_const( diag_or_null );
653 :
654 : /* where to draw to */
655 : const shape_int_rectangle_t *const label_box
656 0 : = pos_nav_tree_node_get_label_box_const( node );
657 :
658 : /* do draw */
659 0 : cairo_move_to( cr, shape_int_rectangle_get_left(label_box), shape_int_rectangle_get_top(label_box) );
660 0 : pango_layout_set_text( font_layout, label, GUI_SKETCH_NAV_TREE_PANGO_AUTO_DETECT_LENGTH );
661 0 : const unsigned int text_width
662 0 : = shape_int_rectangle_get_width(label_box)
663 0 : +(2.0*OBJ_GAP); /* add gap to avoid line breaks by rounding errors and whitespace character widths */
664 0 : pango_layout_set_width(font_layout, text_width * PANGO_SCALE );
665 0 : pango_cairo_show_layout( cr, font_layout );
666 : }
667 :
668 : /* draw the icon */
669 : {
670 : /* what to draw */
671 0 : const pos_nav_tree_node_type_t node_type = pos_nav_tree_node_get_type( node );
672 0 : const gui_sketch_action_t btn_act = gui_marked_set_get_highlighted_button( marker );
673 0 : const bool highlight
674 0 : = (( node_type == POS_NAV_TREE_NODE_TYPE_NEW_ROOT )&&( btn_act == GUI_SKETCH_ACTION_NEW_ROOT_DIAGRAM ))
675 0 : || (( node_type == POS_NAV_TREE_NODE_TYPE_NEW_SIBLING )&&( btn_act == GUI_SKETCH_ACTION_NEW_SIBLING_DIAGRAM ))
676 0 : || (( node_type == POS_NAV_TREE_NODE_TYPE_NEW_CHILD )&&( btn_act == GUI_SKETCH_ACTION_NEW_CHILD_DIAGRAM ));
677 0 : GdkTexture *icon = pos_nav_tree_node_type_get_icon( node_type, highlight, (*this_).resources );
678 :
679 : /* where to draw to */
680 : const shape_int_rectangle_t *const icon_box
681 0 : = pos_nav_tree_node_get_icon_box_const( node );
682 0 : const int x = shape_int_rectangle_get_left(icon_box);
683 0 : const int y = shape_int_rectangle_get_top(icon_box);
684 :
685 : /* do draw */
686 0 : gui_sketch_texture_draw( (*this_).texture_downloader, icon, x, y, cr );
687 : }
688 :
689 0 : U8_TRACE_END();
690 0 : }
691 :
692 0 : void gui_sketch_nav_tree_draw_overlay( const gui_sketch_nav_tree_t *this_,
693 : const gui_sketch_drag_state_t *drag_state,
694 : cairo_t *cr )
695 : {
696 0 : U8_TRACE_BEGIN();
697 0 : assert( NULL != drag_state );
698 0 : assert( NULL != cr );
699 :
700 0 : if ( (*this_).visible && gui_sketch_drag_state_is_dragging( drag_state ) )
701 : {
702 0 : const int32_t to_x = gui_sketch_drag_state_get_to_x( drag_state );
703 0 : const int32_t to_y = gui_sketch_drag_state_get_to_y( drag_state );
704 : data_id_t out_parent_id;
705 : int32_t out_list_order;
706 : shape_int_rectangle_t out_gap_line;
707 : gui_error_t gap_err;
708 0 : gap_err = gui_sketch_nav_tree_get_gap_info_at_pos( this_,
709 : to_x,
710 : to_y,
711 : &out_parent_id,
712 : &out_list_order,
713 : &out_gap_line
714 : );
715 0 : if ( gap_err == GUI_ERROR_NONE )
716 : {
717 0 : const GdkRGBA high_color = gui_sketch_style_get_highlight_color( &((*this_).sketch_style) );
718 0 : cairo_set_source_rgba( cr, high_color.red, high_color.green, high_color.blue, high_color.alpha );
719 0 : cairo_rectangle( cr,
720 0 : shape_int_rectangle_get_left(&out_gap_line),
721 0 : shape_int_rectangle_get_top(&out_gap_line),
722 0 : shape_int_rectangle_get_width(&out_gap_line),
723 0 : shape_int_rectangle_get_height(&out_gap_line)
724 : );
725 0 : cairo_fill (cr);
726 : }
727 : else
728 : {
729 0 : U8_TRACE_INFO("dragging diagram outside nav_tree");
730 : }
731 : }
732 0 : U8_TRACE_END();
733 0 : }
734 :
735 :
736 : /*
737 : Copyright 2018-2024 Andreas Warnke
738 :
739 : Licensed under the Apache License, Version 2.0 (the "License");
740 : you may not use this file except in compliance with the License.
741 : You may obtain a copy of the License at
742 :
743 : http://www.apache.org/licenses/LICENSE-2.0
744 :
745 : Unless required by applicable law or agreed to in writing, software
746 : distributed under the License is distributed on an "AS IS" BASIS,
747 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
748 : See the License for the specific language governing permissions and
749 : limitations under the License.
750 : */
|