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