1 /***************************************************************************
2  * Copyright (c) 2024 Microsoft Corporation
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the MIT License which is available at
6  * https://opensource.org/licenses/MIT.
7  *
8  * SPDX-License-Identifier: MIT
9  **************************************************************************/
10 
11 
12 /**************************************************************************/
13 /**************************************************************************/
14 /**                                                                       */
15 /** GUIX Component                                                        */
16 /**                                                                       */
17 /**   Multi Line Text Input Management (Multi Line Text Input)            */
18 /**                                                                       */
19 /**************************************************************************/
20 
21 #define GX_SOURCE_CODE
22 
23 
24 /* Include necessary system files.  */
25 
26 #include "gx_api.h"
27 #include "gx_system.h"
28 #include "gx_window.h"
29 #include "gx_widget.h"
30 #include "gx_scrollbar.h"
31 #include "gx_multi_line_text_view.h"
32 #include "gx_multi_line_text_input.h"
33 #include "gx_utility.h"
34 
35 /**************************************************************************/
36 /*                                                                        */
37 /*  FUNCTION                                               RELEASE        */
38 /*                                                                        */
39 /*    _gx_multi_line_text_input_cursor_pos_calculate      PORTABLE C      */
40 /*                                                           6.1          */
41 /*  AUTHOR                                                                */
42 /*                                                                        */
43 /*    Kenneth Maxwell, Microsoft Corporation                              */
44 /*                                                                        */
45 /*  DESCRIPTION                                                           */
46 /*                                                                        */
47 /*    This function gets the position of the cursor according to click    */
48 /*    position.                                                           */
49 /*                                                                        */
50 /*  INPUT                                                                 */
51 /*                                                                        */
52 /*    text_input                            Multi line text input         */
53 /*                                            control block               */
54 /*    click_pos                             Click down position           */
55 /*                                                                        */
56 /*  OUTPUT                                                                */
57 /*                                                                        */
58 /*    None                                                                */
59 /*                                                                        */
60 /*  CALLS                                                                 */
61 /*                                                                        */
62 /*    _gx_wiget_font_get                    Retrieve font                 */
63 /*    _gx_window_client_width_get           Get the client width          */
64 /*    _gx_window_scrollbar_find             Find scrollbar for a window   */
65 /*    _gx_scrollbar_reset                   Reset scrollbar information   */
66 /*    _gx_utility_rectangle_resize          Increase/Shrink rectangle by  */
67 /*                                            specified value             */
68 /*    _gx_utility_utf8_string_character_get Parses utf8 string to         */
69 /*                                            multi-byte glyph            */
70 /*    _gx_system_string_width_get           Get the width of a string     */
71 /*    _gx_multi_line_text_view_string_total_rows_compute                  */
72 /*                                          Calculate total rows of input */
73 /*                                            string                      */
74 /*    _gx_multi_line_text_view_line_cache_update                          */
75 /*                                          Update line cache             */
76 /*                                                                        */
77 /*  CALLED BY                                                             */
78 /*                                                                        */
79 /*    GUIX Internal Code                                                  */
80 /*                                                                        */
81 /*  RELEASE HISTORY                                                       */
82 /*                                                                        */
83 /*    DATE              NAME                      DESCRIPTION             */
84 /*                                                                        */
85 /*  05-19-2020     Kenneth Maxwell          Initial Version 6.0           */
86 /*  09-30-2020     Kenneth Maxwell          Modified comment(s),          */
87 /*                                            resulting in version 6.1    */
88 /*                                                                        */
89 /**************************************************************************/
90 
_gx_multi_line_text_input_cursor_pos_calculate(GX_MULTI_LINE_TEXT_INPUT * text_input,GX_POINT click_pos)91 UINT _gx_multi_line_text_input_cursor_pos_calculate(GX_MULTI_LINE_TEXT_INPUT *text_input, GX_POINT click_pos)
92 {
93 GX_TEXT_INPUT_CURSOR *cursor_ptr = &text_input -> gx_multi_line_text_input_cursor_instance;
94 GX_FONT              *font;
95 INT                   line_height;
96 GX_RECTANGLE          client;
97 INT                   y_pos;
98 GX_POINT              cursor_pos;
99 UINT                  total_rows;
100 INT                   shift;
101 UINT                  cursor_line = 0;
102 UINT                  index = 0;
103 UINT                  end_index;
104 GX_STRING             string;
105 GX_VALUE              char_width;
106 GX_SCROLLBAR         *scroll;
107 UINT                  glyph_len = 1;
108 GX_VALUE              text_width;
109 GX_VALUE              client_width;
110 GX_VALUE              space_width;
111 
112     _gx_widget_font_get((GX_WIDGET *)text_input, text_input -> gx_multi_line_text_view_font_id, &font);
113 
114     if (!font)
115     {
116         return GX_FAILURE;
117     }
118 
119     line_height = font -> gx_font_line_height + text_input -> gx_multi_line_text_view_line_space;
120 
121     if (!line_height)
122     {
123         return GX_FAILURE;
124     }
125 
126     /* Initialize the x, y position where the text displayed from. */
127     client = text_input -> gx_window_client;
128 
129     /* Offset client area by length of whitespace.  */
130     _gx_utility_rectangle_resize(&client, (GX_VALUE)(-text_input -> gx_multi_line_text_view_whitespace));
131 
132     y_pos = (INT)client.gx_rectangle_top + text_input -> gx_multi_line_text_view_text_scroll_shift;
133 
134     total_rows = text_input -> gx_multi_line_text_view_text_total_rows;
135 
136     if ((click_pos.gx_point_y < y_pos + line_height) || (total_rows == 0))
137     {
138         cursor_line = 1;
139     }
140     else
141     {
142         cursor_line = (UINT)((click_pos.gx_point_y - y_pos) / line_height + 1);
143 
144         if (cursor_line > total_rows)
145         {
146             cursor_line = total_rows;
147         }
148     }
149 
150     /* get y coordinate of cursor postition */
151     shift = 0;
152     cursor_pos.gx_point_y = (GX_VALUE)(y_pos + (INT)(cursor_line - 1) * line_height);
153 
154     if (cursor_pos.gx_point_y + line_height - 1 > client.gx_rectangle_bottom)
155     {
156         shift = client.gx_rectangle_bottom - (cursor_pos.gx_point_y + line_height - 1);
157     }
158     else if (cursor_pos.gx_point_y < client.gx_rectangle_top)
159     {
160         shift = client.gx_rectangle_top - cursor_pos.gx_point_y;
161     }
162 
163     if (shift)
164     {
165         text_input -> gx_multi_line_text_view_text_scroll_shift += shift;
166         cursor_pos.gx_point_y = (GX_VALUE)(cursor_pos.gx_point_y + shift);
167 
168         _gx_window_scrollbar_find((GX_WINDOW *)text_input, GX_TYPE_VERTICAL_SCROLL, &scroll);
169         if (scroll)
170         {
171             /* Reset scrollbar.  */
172             _gx_scrollbar_reset(scroll, GX_NULL);
173         }
174         else
175         {
176 
177             if (text_input -> gx_multi_line_text_view_text_total_rows >
178                 text_input -> gx_multi_line_text_view_cache_size)
179             {
180                 _gx_multi_line_text_view_line_cache_update((GX_MULTI_LINE_TEXT_VIEW *)text_input);
181             }
182         }
183     }
184 
185     /* get x coordinate of cursor position. */
186     cursor_pos.gx_point_x = (GX_VALUE)(client.gx_rectangle_left + 1);
187     text_input -> gx_multi_line_text_input_text_cursor_line = cursor_line;
188 
189     cursor_line = (UINT)(cursor_line - 1);
190     cursor_line = (UINT)(cursor_line - text_input -> gx_multi_line_text_view_first_cache_line);
191 
192     index = (text_input -> gx_multi_line_text_view_line_index[cursor_line]);
193 
194     if (cursor_line >= (UINT)(text_input -> gx_multi_line_text_view_cache_size - 1))
195     {
196         end_index = text_input -> gx_multi_line_text_view_text.gx_string_length;
197     }
198     else
199     {
200         end_index = text_input -> gx_multi_line_text_view_line_index[cursor_line + 1];
201     }
202 
203     string.gx_string_ptr = " ";
204     string.gx_string_length = 1;
205 
206     _gx_system_string_width_get_ext(font, &string, &space_width);
207 
208     switch (text_input -> gx_widget_style & GX_STYLE_TEXT_ALIGNMENT_MASK)
209     {
210     case GX_STYLE_TEXT_RIGHT:
211         string.gx_string_ptr = text_input -> gx_multi_line_text_view_text.gx_string_ptr + index;
212         string.gx_string_length = end_index - index;
213         _gx_system_string_width_get_ext(font, &string, &text_width);
214         while (text_width > (client.gx_rectangle_right - client.gx_rectangle_left - 2))
215         {
216             text_width = (GX_VALUE)(text_width - space_width);
217         }
218         cursor_pos.gx_point_x = (GX_VALUE)(client.gx_rectangle_right - 1);
219         cursor_pos.gx_point_x = (GX_VALUE)(cursor_pos.gx_point_x - text_width);
220         break;
221 
222     case GX_STYLE_TEXT_LEFT:
223         cursor_pos.gx_point_x = (GX_VALUE)(client.gx_rectangle_left + 1);
224         break;
225 
226     case GX_STYLE_TEXT_CENTER:
227     default:
228         string.gx_string_ptr = text_input -> gx_multi_line_text_view_text.gx_string_ptr + index;
229         string.gx_string_length = end_index - index;
230         _gx_system_string_width_get_ext(font, &string, &text_width);
231         client_width = (GX_VALUE)(client.gx_rectangle_right - client.gx_rectangle_left + 1);
232         while (text_width > (client_width - 3))
233         {
234             text_width = (GX_VALUE)(text_width - space_width);
235         }
236         cursor_pos.gx_point_x = (GX_VALUE)(client.gx_rectangle_left + ((client_width - text_width) / 2));
237         break;
238     }
239 
240     while (index < end_index)
241     {
242         string.gx_string_ptr = text_input -> gx_multi_line_text_view_text.gx_string_ptr + index;
243         string.gx_string_length = end_index - index;
244         if ((string.gx_string_ptr[0] == GX_KEY_CARRIAGE_RETURN) || (string.gx_string_ptr[0] == GX_KEY_LINE_FEED))
245         {
246             break;
247         }
248 #ifdef GX_UTF8_SUPPORT
249 
250         _gx_utility_utf8_string_character_get(&string, GX_NULL, &glyph_len);
251 #else
252         string.gx_string_ptr += glyph_len;
253 #endif
254         string.gx_string_ptr -= glyph_len;
255         string.gx_string_length = glyph_len;
256         _gx_system_string_width_get_ext(font, &string, &char_width);
257         if ((cursor_pos.gx_point_x + char_width / 2) > click_pos.gx_point_x)
258         {
259             while (cursor_pos.gx_point_x > client.gx_rectangle_right - 1)
260             {
261                 cursor_pos.gx_point_x = (GX_VALUE)(cursor_pos.gx_point_x - space_width);
262             }
263             break;
264         }
265         cursor_pos.gx_point_x = (GX_VALUE)(cursor_pos.gx_point_x + char_width);
266         index += glyph_len;
267     }
268 
269     cursor_ptr -> gx_text_input_cursor_pos = cursor_pos;
270     cursor_ptr -> gx_text_input_cursor_pos.gx_point_y = (GX_VALUE)(cursor_ptr -> gx_text_input_cursor_pos.gx_point_y + (line_height >> 1));
271 
272     /* record text insert position. */
273     text_input -> gx_multi_line_text_input_text_insert_position = index;
274 
275     return GX_SUCCESS;
276 }
277 
278