LCOV - code coverage report
Current view: top level - gui/source/sketch - gui_sketch_nav_tree.c (source / functions) Coverage Total Hit
Test: crystal-facet-uml_v1.63.2_covts Lines: 65.2 % 374 244
Test Date: 2025-05-01 10:10:14 Functions: 63.6 % 11 7

            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 "gui_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-2025 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              : */
        

Generated by: LCOV version 2.0-1