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