Line data Source code
1 : /* File: gui_sketch_result_list.c; Copyright and License: see below */
2 :
3 : #include "sketch/gui_sketch_result_list.h"
4 : #include "geometry/geometry_rectangle.h"
5 : #include "utf8stringbuf/utf8stringbuf.h"
6 : #include "u8/u8_trace.h"
7 : #include "u8/u8_log.h"
8 : #include "u8/u8_i32.h"
9 : #include "gui_gdk.h"
10 :
11 : static const int GUI_SKETCH_RESULT_LIST_PANGO_AUTO_DETECT_LENGTH = -1; /*!< pango automatically determines the string length */
12 : static const int GUI_SKETCH_RESULT_LIST_PANGO_UNLIMITED_WIDTH = -1;
13 : static const int OBJ_GAP = 4;
14 :
15 0 : void gui_sketch_result_list_init( gui_sketch_result_list_t *this_,
16 : gui_resources_t *resources,
17 : gui_sketch_texture_t *texture_downloader )
18 : {
19 0 : U8_TRACE_BEGIN();
20 0 : assert( resources != NULL );
21 0 : assert( texture_downloader != NULL );
22 :
23 : /* layouting information */
24 0 : (*this_).visible = false;
25 0 : shape_int_rectangle_init( &((*this_).bounds), 0, 0, 0, 0 );
26 :
27 : /* data and layouting information of search results */
28 0 : (*this_).requested_page = pos_scroll_page_new( 0, false /* backwards */ );
29 0 : pos_search_result_page_init( &((*this_).page), 0 /* buffer_start */ );
30 :
31 : /* helper classes to perform drawing */
32 0 : gui_sketch_style_init( &((*this_).sketch_style) );
33 0 : gui_sketch_marker_init( &((*this_).sketch_marker), true );
34 0 : (*this_).resources = resources;
35 0 : gui_type_resource_list_init( &((*this_).selector), resources );
36 0 : (*this_).texture_downloader = texture_downloader;
37 :
38 0 : U8_TRACE_END();
39 0 : }
40 :
41 0 : void gui_sketch_result_list_destroy( gui_sketch_result_list_t *this_ )
42 : {
43 0 : U8_TRACE_BEGIN();
44 :
45 : /* helper classes to perform drawing */
46 0 : (*this_).texture_downloader = NULL;
47 0 : gui_type_resource_list_destroy( &((*this_).selector) );
48 0 : (*this_).resources = NULL;
49 0 : gui_sketch_marker_destroy( &((*this_).sketch_marker) );
50 0 : gui_sketch_style_destroy( &((*this_).sketch_style) );
51 :
52 : /* data and layouting information of search results */
53 0 : pos_search_result_page_destroy( &((*this_).page) );
54 :
55 : /* layouting information */
56 0 : shape_int_rectangle_destroy( &((*this_).bounds) );
57 :
58 0 : U8_TRACE_END();
59 0 : }
60 :
61 0 : void gui_sketch_result_list_do_layout( gui_sketch_result_list_t *this_, cairo_t *cr )
62 : {
63 0 : U8_TRACE_BEGIN();
64 :
65 : /* bounds */
66 0 : int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
67 0 : uint_fast32_t width = shape_int_rectangle_get_width( &((*this_).bounds) );
68 0 : int_fast32_t top = shape_int_rectangle_get_top( &((*this_).bounds) );
69 0 : uint_fast32_t height = shape_int_rectangle_get_height( &((*this_).bounds) );
70 :
71 : /* create the font_layout */
72 : PangoLayout *font_layout;
73 : {
74 0 : font_layout = pango_cairo_create_layout (cr);
75 : const PangoFontDescription *const std_font
76 0 : = gui_sketch_style_get_standard_font_description( &((*this_).sketch_style ) );
77 0 : pango_layout_set_font_description( font_layout, std_font );
78 : }
79 :
80 : /* measure a previous page button heights */
81 0 : GdkTexture *prev_gray_icon = gui_resources_get_sketch_page_up_gray( (*this_).resources );
82 0 : const uint_fast32_t prev_icon_height = gdk_texture_get_height( prev_gray_icon );
83 0 : const uint_fast32_t prev_icon_width = gdk_texture_get_width( prev_gray_icon );
84 0 : const shape_int_rectangle_t prev_icon_box = (shape_int_rectangle_t) {
85 0 : .left = left + ( width - prev_icon_width ) / 2,
86 : .top = top,
87 : .width = prev_icon_width,
88 : .height = prev_icon_height };
89 0 : pos_search_result_page_set_button_prev_box( &((*this_).page), &prev_icon_box );
90 :
91 : /* measure a next page button heights */
92 0 : GdkTexture *next_gray_icon = gui_resources_get_sketch_page_down_gray( (*this_).resources );
93 0 : const uint_fast32_t next_icon_height = gdk_texture_get_height( next_gray_icon );
94 0 : const uint_fast32_t next_icon_width = gdk_texture_get_width( next_gray_icon );
95 0 : const shape_int_rectangle_t next_icon_box = (shape_int_rectangle_t) {
96 0 : .left = left + ( width - next_icon_width ) / 2,
97 0 : .top = top + height - next_icon_height,
98 : .width = next_icon_width,
99 : .height = next_icon_height };
100 0 : pos_search_result_page_set_button_next_box( &((*this_).page), &next_icon_box );
101 :
102 : /* initialize a possible result - in case the buffer is empty and the for loop later is not executed */
103 0 : const int32_t buffer_start = pos_search_result_page_get_buffer_start( &((*this_).page) );
104 0 : const uint32_t buffer_length = pos_search_result_page_get_buffer_length( &((*this_).page) );
105 0 : assert( buffer_length <= POS_SEARCH_RESULT_PAGE_MAX_PAGE_SIZE );
106 0 : const bool buffer_more_after = pos_search_result_page_get_buffer_more_after( &((*this_).page) );
107 0 : pos_search_result_page_set_page_start( &((*this_).page), buffer_start ); /* default value for case of empty buffer */
108 0 : pos_search_result_page_set_page_length( &((*this_).page), buffer_length ); /* default value for case of empty buffer */
109 0 : pos_search_result_page_set_has_prev_page( &((*this_).page), ( buffer_start != 0 ) ); /* default value for case of empty buffer */
110 :
111 : /* do not fully trust the consistency between (*this_).requested_page and (*this_).page. */
112 : /* These information come from different sources. */
113 0 : pos_scroll_page_trace( &((*this_).requested_page) );
114 0 : const int_fast32_t requested_anchor_idx = pos_scroll_page_get_anchor_index( &((*this_).requested_page) );
115 0 : const bool valid_anchor
116 0 : = (( buffer_start <= requested_anchor_idx )&&( requested_anchor_idx < ( buffer_start + buffer_length )));
117 0 : assert( valid_anchor || ( buffer_length == 0 ) );
118 0 : const int_fast32_t anchor_idx = valid_anchor ? requested_anchor_idx : buffer_start;
119 0 : const bool requested_backwards = pos_scroll_page_get_backwards( &((*this_).requested_page) );
120 0 : const bool backwards = valid_anchor ? requested_backwards : false;
121 :
122 : /* each visible element, do layout */
123 0 : if ( backwards )
124 : {
125 : /* y positions */
126 0 : int_fast32_t y_pos = shape_int_rectangle_get_bottom( &((*this_).bounds) ) - next_icon_height - OBJ_GAP;
127 0 : const int_fast32_t top_border = shape_int_rectangle_get_top( &((*this_).bounds) ) + prev_icon_height + OBJ_GAP;
128 0 : bool page_full = false;
129 0 : for ( int32_t index = anchor_idx; ( index >= buffer_start ) && ( ! page_full ); index -- )
130 : {
131 : pos_search_result_t *const search_result
132 0 : = pos_search_result_page_get_search_result_layout_ptr( &((*this_).page), index );
133 0 : gui_sketch_result_list_private_layout_element( this_, search_result, &y_pos, backwards, font_layout );
134 0 : if ( y_pos < top_border )
135 : {
136 0 : page_full = true;
137 : }
138 : else
139 : {
140 : /* define the range of visible elements */
141 0 : pos_search_result_page_set_page_start( &((*this_).page), index );
142 0 : pos_search_result_page_set_page_length( &((*this_).page), anchor_idx - index + 1 );
143 0 : pos_search_result_page_set_has_prev_page( &((*this_).page), ( index != 0 ) );
144 : }
145 : }
146 0 : const bool more_after_anchor = ( anchor_idx + 1 ) < ( buffer_start + buffer_length );
147 0 : pos_search_result_page_set_has_next_page( &((*this_).page), buffer_more_after || more_after_anchor );
148 : }
149 : else
150 : {
151 : /* y positions */
152 0 : int_fast32_t y_pos = shape_int_rectangle_get_top( &((*this_).bounds) ) + prev_icon_height + OBJ_GAP;
153 0 : const int_fast32_t bottom_border = shape_int_rectangle_get_bottom( &((*this_).bounds) ) - next_icon_height - OBJ_GAP;
154 0 : bool page_full = false;
155 0 : for ( uint32_t buffer_idx = 0; ( buffer_idx < buffer_length ) && ( ! page_full ); buffer_idx ++ )
156 : {
157 0 : const uint32_t index = buffer_start + buffer_idx;
158 : pos_search_result_t *const search_result
159 0 : = pos_search_result_page_get_search_result_layout_ptr( &((*this_).page), index );
160 0 : gui_sketch_result_list_private_layout_element( this_, search_result, &y_pos, backwards, font_layout );
161 0 : if ( y_pos > bottom_border )
162 : {
163 0 : page_full = true;
164 : }
165 : else
166 : {
167 : /* define the range of visible elements */
168 0 : pos_search_result_page_set_page_start( &((*this_).page), buffer_start );
169 0 : pos_search_result_page_set_page_length( &((*this_).page), index - buffer_start + 1 );
170 : }
171 : }
172 0 : pos_search_result_page_set_has_next_page( &((*this_).page), buffer_more_after || page_full );
173 : }
174 :
175 : /* trace available buffer and visible page */
176 0 : U8_TRACE_INFO_INT_INT( "gui_sketch_result_list_do_layout: available buffer (start,len):", buffer_start, buffer_length );
177 0 : U8_TRACE_INFO_INT_INT( "gui_sketch_result_list_do_layout: visible page (start,len):",
178 : pos_search_result_page_get_page_start( &((*this_).page) ),
179 : pos_search_result_page_get_page_length( &((*this_).page) )
180 : );
181 :
182 : /* release the font_layout */
183 0 : g_object_unref(font_layout);
184 :
185 0 : U8_TRACE_END();
186 0 : }
187 :
188 0 : void gui_sketch_result_list_private_layout_element ( gui_sketch_result_list_t *this_,
189 : pos_search_result_t *element,
190 : int_fast32_t *io_y_pos,
191 : bool upwards,
192 : PangoLayout *font_layout )
193 : {
194 0 : U8_TRACE_BEGIN();
195 0 : assert( NULL != element );
196 0 : assert( NULL != io_y_pos );
197 0 : assert( NULL != font_layout );
198 :
199 0 : int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
200 0 : uint_fast32_t width = shape_int_rectangle_get_width( &((*this_).bounds) );
201 0 : const data_search_result_t *result = pos_search_result_get_data_const( element );
202 :
203 : /* determine icon dimensions */
204 0 : const data_type_t result_type = data_search_result_get_match_type( result );
205 : gui_type_resource_t *const type_data
206 0 : = gui_type_resource_list_get_type ( &((*this_).selector), result_type );
207 0 : GdkTexture *const icon = gui_type_resource_get_icon( type_data );
208 0 : const uint_fast32_t icon_width = gdk_texture_get_width( icon );
209 0 : const uint_fast32_t icon_height = gdk_texture_get_height( icon );
210 :
211 : /* determine label dimensions */
212 0 : int_fast32_t proposed_pango_width = width - icon_width - (4*OBJ_GAP);
213 0 : pango_layout_set_text( font_layout,
214 : data_search_result_get_match_name_const( result ),
215 : GUI_SKETCH_RESULT_LIST_PANGO_AUTO_DETECT_LENGTH
216 : );
217 0 : pango_layout_set_width(font_layout, proposed_pango_width * PANGO_SCALE );
218 : int text_width;
219 : int text_height;
220 0 : pango_layout_get_pixel_size(font_layout, &text_width, &text_height);
221 :
222 : /* calculate element bounds */
223 0 : const uint_fast32_t element_height = u8_i32_max2( icon_height, text_height );
224 0 : const int_fast32_t element_top = (*io_y_pos) - ( upwards ? ( OBJ_GAP + element_height ) : (-OBJ_GAP) );
225 :
226 : /* set icon dimensions */
227 0 : const shape_int_rectangle_t icon_box = (shape_int_rectangle_t) {
228 0 : .left = left + OBJ_GAP,
229 : .top = element_top,
230 : .width = icon_width,
231 : .height = icon_height };
232 0 : pos_search_result_set_icon_box( element, &icon_box );
233 :
234 : /* set label dimensions */
235 0 : int_fast32_t x_pos = shape_int_rectangle_get_right( &icon_box );
236 0 : const shape_int_rectangle_t label_box = (shape_int_rectangle_t) {
237 0 : .left = x_pos + OBJ_GAP,
238 : .top = element_top,
239 : .width = text_width,
240 : .height = text_height };
241 0 : pos_search_result_set_label_box( element, &label_box );
242 :
243 : /* update y_pos */
244 0 : *io_y_pos = ( upwards ? element_top : ( element_top + element_height ) );
245 :
246 0 : U8_TRACE_END();
247 0 : }
248 :
249 : static const double GREY_R = 0.8;
250 : static const double GREY_G = 0.8;
251 : static const double GREY_B = 0.8;
252 : static const double GREY_A = 1.0;
253 :
254 0 : void gui_sketch_result_list_draw ( gui_sketch_result_list_t *this_, const gui_marked_set_t *marker, cairo_t *cr )
255 : {
256 0 : U8_TRACE_BEGIN();
257 0 : assert( NULL != marker );
258 0 : assert( NULL != cr );
259 :
260 0 : if ( (*this_).visible )
261 : {
262 0 : PangoLayout *font_layout = pango_cairo_create_layout (cr);
263 : {
264 : const PangoFontDescription *const std_font
265 0 : = gui_sketch_style_get_standard_font_description( &((*this_).sketch_style ) );
266 0 : pango_layout_set_font_description ( font_layout, std_font );
267 : }
268 :
269 : /* draw background */
270 : {
271 0 : const int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
272 0 : const int_fast32_t top = shape_int_rectangle_get_top( &((*this_).bounds) );
273 0 : const uint_fast32_t width = shape_int_rectangle_get_width( &((*this_).bounds) );
274 0 : const uint_fast32_t height = shape_int_rectangle_get_height( &((*this_).bounds) );
275 :
276 0 : cairo_set_source_rgba( cr, GREY_R, GREY_G, GREY_B, GREY_A );
277 0 : cairo_rectangle ( cr, left, top, width, height );
278 0 : cairo_fill (cr);
279 : }
280 :
281 : /* draw icons and text */
282 0 : const uint32_t page_start = pos_search_result_page_get_page_start( &((*this_).page) );
283 0 : const uint32_t page_length = pos_search_result_page_get_page_length( &((*this_).page) );
284 0 : assert( page_length <= POS_SEARCH_RESULT_PAGE_MAX_PAGE_SIZE );
285 0 : if ( ( page_start + page_length ) == 0 )
286 : {
287 0 : const int_fast32_t left = shape_int_rectangle_get_left( &((*this_).bounds) );
288 0 : const int_fast32_t top = shape_int_rectangle_get_top( &((*this_).bounds) );
289 0 : GdkTexture *undef_icon = gui_resources_get_type_undef( (*this_).resources );
290 0 : const uint_fast32_t icon_width = gdk_texture_get_width ( undef_icon );
291 :
292 : /* draw text first, use the above set color and font */
293 0 : const GdkRGBA std_color = gui_sketch_style_get_standard_color( &((*this_).sketch_style) );
294 0 : cairo_set_source_rgba( cr, std_color.red, std_color.green, std_color.blue, std_color.alpha );
295 0 : cairo_move_to( cr, left+OBJ_GAP+icon_width, top+OBJ_GAP );
296 0 : pango_layout_set_text( font_layout, "no results", GUI_SKETCH_RESULT_LIST_PANGO_AUTO_DETECT_LENGTH );
297 0 : pango_layout_set_width(font_layout, GUI_SKETCH_RESULT_LIST_PANGO_UNLIMITED_WIDTH );
298 0 : pango_cairo_show_layout( cr, font_layout );
299 :
300 : /* draw the icon */
301 0 : const int_fast32_t x = left + OBJ_GAP;
302 0 : const int_fast32_t y = top + OBJ_GAP;
303 0 : gui_sketch_texture_draw( (*this_).texture_downloader, undef_icon, x, y, cr );
304 : }
305 : else
306 : {
307 : /* draw search results */
308 0 : for ( uint32_t idx = 0; idx < page_length; idx ++ )
309 : {
310 : const pos_search_result_t *const element
311 0 : = pos_search_result_page_get_search_result_layout_const( &((*this_).page), page_start + idx );
312 0 : gui_sketch_result_list_private_draw_element( this_, element, marker, font_layout, cr );
313 : }
314 :
315 : /* draw prev and next page buttons */
316 0 : const gui_sketch_action_t btn_act = gui_marked_set_get_highlighted_button( marker );
317 0 : if ( pos_search_result_page_has_prev_page( &((*this_).page) ) )
318 : {
319 : const shape_int_rectangle_t *const prev_box
320 0 : = pos_search_result_page_get_button_prev_box_const( &((*this_).page) );
321 0 : GdkTexture *const prev_icon
322 : = ( btn_act == GUI_SKETCH_ACTION_PREVIOUS_PAGE )
323 0 : ? gui_resources_get_sketch_page_up_bold( (*this_).resources )
324 0 : : gui_resources_get_sketch_page_up_gray( (*this_).resources );
325 0 : gui_sketch_texture_draw( (*this_).texture_downloader,
326 : prev_icon,
327 0 : shape_int_rectangle_get_left( prev_box ),
328 0 : shape_int_rectangle_get_top( prev_box ),
329 : cr
330 : );
331 : }
332 0 : if ( pos_search_result_page_has_next_page( &((*this_).page) ) )
333 : {
334 : const shape_int_rectangle_t *const next_box
335 0 : = pos_search_result_page_get_button_next_box_const( &((*this_).page) );
336 0 : GdkTexture *const next_icon
337 : = ( btn_act == GUI_SKETCH_ACTION_NEXT_PAGE )
338 0 : ? gui_resources_get_sketch_page_down_bold( (*this_).resources )
339 0 : : gui_resources_get_sketch_page_down_gray( (*this_).resources );
340 0 : gui_sketch_texture_draw( (*this_).texture_downloader,
341 : next_icon,
342 0 : shape_int_rectangle_get_left( next_box ),
343 0 : shape_int_rectangle_get_top( next_box ),
344 : cr
345 : );
346 : }
347 : }
348 :
349 0 : g_object_unref(font_layout);
350 : }
351 :
352 0 : U8_TRACE_END();
353 0 : }
354 :
355 0 : void gui_sketch_result_list_private_draw_element( gui_sketch_result_list_t *this_,
356 : const pos_search_result_t *element,
357 : const gui_marked_set_t *marker,
358 : PangoLayout *font_layout,
359 : cairo_t *cr )
360 : {
361 0 : U8_TRACE_BEGIN();
362 0 : assert( NULL != cr );
363 0 : assert( NULL != element );
364 0 : assert( NULL != marker );
365 0 : assert( NULL != font_layout );
366 :
367 0 : const data_search_result_t *const result = pos_search_result_get_data_const( element );
368 :
369 : /* draw marker and set color */
370 : {
371 : shape_int_rectangle_t destination_rect;
372 0 : shape_int_rectangle_init_by_bounds( &destination_rect,
373 : pos_search_result_get_icon_box_const(element),
374 : pos_search_result_get_label_box_const(element)
375 : );
376 :
377 0 : const data_id_t highlighted = gui_marked_set_get_highlighted( marker );
378 0 : gui_sketch_marker_prepare_draw( &((*this_).sketch_marker),
379 : data_search_result_get_match_id( result ),
380 : marker,
381 : destination_rect,
382 : cr
383 : );
384 0 : if ( data_id_equals( &highlighted, data_search_result_get_diagram_id_const( result ) ) )
385 : {
386 0 : const GdkRGBA high_color = gui_sketch_style_get_highlight_color( &((*this_).sketch_style) );
387 0 : cairo_set_source_rgba( cr, high_color.red, high_color.green, high_color.blue, high_color.alpha );
388 : }
389 :
390 0 : shape_int_rectangle_destroy( &destination_rect );
391 : }
392 :
393 : /* draw text first, use the above set color and font */
394 : {
395 : /* what to draw */
396 0 : const char *const label = data_search_result_get_match_name_const( result );
397 :
398 : /* where to draw to */
399 : const shape_int_rectangle_t *const label_box
400 0 : = pos_search_result_get_label_box_const( element );
401 :
402 : /* do draw */
403 0 : cairo_move_to( cr, shape_int_rectangle_get_left(label_box), shape_int_rectangle_get_top(label_box) );
404 0 : pango_layout_set_text( font_layout, label, GUI_SKETCH_RESULT_LIST_PANGO_AUTO_DETECT_LENGTH );
405 0 : const unsigned int text_width
406 0 : = shape_int_rectangle_get_width(label_box)
407 0 : +(2.0*OBJ_GAP); /* add gap to avoid line breaks by rounding errors and whitespace character widths */
408 0 : pango_layout_set_width(font_layout, text_width * PANGO_SCALE );
409 0 : pango_cairo_show_layout( cr, font_layout );
410 : }
411 :
412 : /* draw the icon */
413 : {
414 : /* what to draw */
415 0 : const data_type_t result_type = data_search_result_get_match_type( result );
416 : gui_type_resource_t *const type_data
417 0 : = gui_type_resource_list_get_type ( &((*this_).selector), result_type );
418 0 : GdkTexture *const icon = gui_type_resource_get_icon( type_data );
419 :
420 : /* where to draw to */
421 : const shape_int_rectangle_t *const icon_box
422 0 : = pos_search_result_get_icon_box_const( element );
423 0 : const int x = shape_int_rectangle_get_left(icon_box);
424 0 : const int y = shape_int_rectangle_get_top(icon_box);
425 : /* double icon_width = gdk_texture_get_width ( icon ); */
426 : /* double icon_height = gdk_texture_get_width ( icon ); */
427 :
428 : /* do draw */
429 0 : gui_sketch_texture_draw( (*this_).texture_downloader, icon, x, y, cr );
430 : }
431 :
432 0 : U8_TRACE_END();
433 0 : }
434 :
435 :
436 : /*
437 : Copyright 2018-2025 Andreas Warnke
438 :
439 : Licensed under the Apache License, Version 2.0 (the "License");
440 : you may not use this file except in compliance with the License.
441 : You may obtain a copy of the License at
442 :
443 : http://www.apache.org/licenses/LICENSE-2.0
444 :
445 : Unless required by applicable law or agreed to in writing, software
446 : distributed under the License is distributed on an "AS IS" BASIS,
447 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
448 : See the License for the specific language governing permissions and
449 : limitations under the License.
450 : */
|