1#include "gl-app.h"
2
3#include <string.h>
4#include <stdio.h>
5
6static char *find_string_location(char *s, char *e, float w, float x)
7{
8 int c;
9 while (s < e)
10 {
11 int n = fz_chartorune(&c, s);
12 float cw = ui_measure_character(c);
13 if (w + (cw / 2) >= x)
14 return s;
15 w += cw;
16 s += n;
17 }
18 return e;
19}
20
21static char *find_input_location(struct line *lines, int n, float left, float top, float x, float y)
22{
23 int i = 0;
24 if (y > top) i = (y - top) / ui.lineheight;
25 if (i >= n) i = n - 1;
26 return find_string_location(lines[i].a, lines[i].b, left, x);
27}
28
29static inline int myisalnum(char *s)
30{
31 int cat, c;
32 fz_chartorune(&c, s);
33 cat = ucdn_get_general_category(c);
34 if (cat >= UCDN_GENERAL_CATEGORY_LL && cat <= UCDN_GENERAL_CATEGORY_LU)
35 return 1;
36 if (cat >= UCDN_GENERAL_CATEGORY_ND && cat <= UCDN_GENERAL_CATEGORY_NO)
37 return 1;
38 return 0;
39}
40
41static char *home_line(char *p, char *start)
42{
43 while (p > start)
44 {
45 if (p[-1] == '\n' || p[-1] == '\r')
46 return p;
47 --p;
48 }
49 return p;
50}
51
52static char *end_line(char *p, char *end)
53{
54 while (p < end)
55 {
56 if (p[0] == '\n' || p[0] == '\r')
57 return p;
58 ++p;
59 }
60 return p;
61}
62
63static char *up_line(char *p, char *start)
64{
65 while (p > start)
66 {
67 --p;
68 if (*p == '\n' || *p == '\r')
69 return p;
70 }
71 return p;
72}
73
74static char *down_line(char *p, char *end)
75{
76 while (p < end)
77 {
78 if (*p == '\n' || *p == '\r')
79 return p+1;
80 ++p;
81 }
82 return p;
83}
84
85static char *prev_char(char *p, char *start)
86{
87 --p;
88 while ((*p & 0xC0) == 0x80 && p > start) /* skip middle and final multibytes */
89 --p;
90 return p;
91}
92
93static char *next_char(char *p)
94{
95 ++p;
96 while ((*p & 0xC0) == 0x80) /* skip middle and final multibytes */
97 ++p;
98 return p;
99}
100
101static char *prev_word(char *p, char *start)
102{
103 while (p > start && !myisalnum(prev_char(p, start))) p = prev_char(p, start);
104 while (p > start && myisalnum(prev_char(p, start))) p = prev_char(p, start);
105 return p;
106}
107
108static char *next_word(char *p, char *end)
109{
110 while (p < end && !myisalnum(p)) p = next_char(p);
111 while (p < end && myisalnum(p)) p = next_char(p);
112 return p;
113}
114
115static void ui_input_delete_selection(struct input *input)
116{
117 char *p = input->p < input->q ? input->p : input->q;
118 char *q = input->p > input->q ? input->p : input->q;
119 memmove(p, q, input->end - q);
120 input->end -= q - p;
121 *input->end = 0;
122 input->p = input->q = p;
123}
124
125static void ui_input_paste(struct input *input, const char *buf, int n)
126{
127 if (input->p != input->q)
128 ui_input_delete_selection(input);
129 if (input->end + n + 1 < input->text + sizeof(input->text))
130 {
131 memmove(input->p + n, input->p, input->end - input->p);
132 memmove(input->p, buf, n);
133 input->p += n;
134 input->end += n;
135 *input->end = 0;
136 }
137 input->q = input->p;
138}
139
140static int ui_input_key(struct input *input, int multiline)
141{
142 switch (ui.key)
143 {
144 case 0:
145 return UI_INPUT_NONE;
146 case KEY_LEFT:
147 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
148 {
149 input->q = prev_word(input->q, input->text);
150 }
151 else if (ui.mod == GLUT_ACTIVE_CTRL)
152 {
153 if (input->p != input->q)
154 input->p = input->q = input->p < input->q ? input->p : input->q;
155 else
156 input->p = input->q = prev_word(input->q, input->text);
157 }
158 else if (ui.mod == GLUT_ACTIVE_SHIFT)
159 {
160 if (input->q > input->text)
161 input->q = prev_char(input->q, input->text);
162 }
163 else if (ui.mod == 0)
164 {
165 if (input->p != input->q)
166 input->p = input->q = input->p < input->q ? input->p : input->q;
167 else if (input->q > input->text)
168 input->p = input->q = prev_char(input->q, input->text);
169 }
170 break;
171 case KEY_RIGHT:
172 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
173 {
174 input->q = next_word(input->q, input->end);
175 }
176 else if (ui.mod == GLUT_ACTIVE_CTRL)
177 {
178 if (input->p != input->q)
179 input->p = input->q = input->p > input->q ? input->p : input->q;
180 else
181 input->p = input->q = next_word(input->q, input->end);
182 }
183 else if (ui.mod == GLUT_ACTIVE_SHIFT)
184 {
185 if (input->q < input->end)
186 input->q = next_char(input->q);
187 }
188 else if (ui.mod == 0)
189 {
190 if (input->p != input->q)
191 input->p = input->q = input->p > input->q ? input->p : input->q;
192 else if (input->q < input->end)
193 input->p = input->q = next_char(input->q);
194 }
195 break;
196 case KEY_UP:
197 if (ui.mod & GLUT_ACTIVE_SHIFT)
198 input->q = up_line(input->q, input->text);
199 else
200 input->p = input->q = up_line(input->p, input->text);
201 break;
202 case KEY_DOWN:
203 if (ui.mod & GLUT_ACTIVE_SHIFT)
204 input->q = down_line(input->q, input->end);
205 else
206 input->p = input->q = down_line(input->q, input->end);
207 break;
208 case KEY_HOME:
209 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
210 input->q = input->text;
211 else if (ui.mod == GLUT_ACTIVE_SHIFT)
212 input->q = home_line(input->q, input->text);
213 else if (ui.mod == GLUT_ACTIVE_CTRL)
214 input->p = input->q = input->text;
215 else if (ui.mod == 0)
216 input->p = input->q = home_line(input->p, input->text);
217 break;
218 case KEY_END:
219 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
220 input->q = input->end;
221 else if (ui.mod == GLUT_ACTIVE_SHIFT)
222 input->q = end_line(input->q, input->end);
223 else if (ui.mod == GLUT_ACTIVE_CTRL)
224 input->p = input->q = input->end;
225 else if (ui.mod == 0)
226 input->p = input->q = end_line(input->p, input->end);
227 break;
228 case KEY_DELETE:
229 if (input->p != input->q)
230 ui_input_delete_selection(input);
231 else if (input->p < input->end)
232 {
233 char *np = next_char(input->p);
234 memmove(input->p, np, input->end - np);
235 input->end -= np - input->p;
236 *input->end = 0;
237 input->q = input->p;
238 }
239 break;
240 case KEY_ESCAPE:
241 ui.focus = NULL;
242 return UI_INPUT_NONE;
243 case KEY_ENTER:
244 if (!multiline)
245 {
246 ui.focus = NULL;
247 return UI_INPUT_ACCEPT;
248 }
249 ui_input_paste(input, "\n", 1);
250 break;
251 case KEY_BACKSPACE:
252 if (input->p != input->q)
253 ui_input_delete_selection(input);
254 else if (input->p > input->text)
255 {
256 char *pp = prev_char(input->p, input->text);
257 memmove(pp, input->p, input->end - input->p);
258 input->end -= input->p - pp;
259 *input->end = 0;
260 input->q = input->p = pp;
261 }
262 break;
263 case KEY_CTL_A:
264 input->p = input->q = input->text;
265 break;
266 case KEY_CTL_E:
267 input->p = input->q = input->end;
268 break;
269 case KEY_CTL_W:
270 if (input->p != input->q)
271 ui_input_delete_selection(input);
272 else
273 {
274 input->p = prev_word(input->p, input->text);
275 ui_input_delete_selection(input);
276 }
277 break;
278 case KEY_CTL_U:
279 input->p = input->q = input->end = input->text;
280 *input->end = 0;
281 break;
282 case KEY_CTL_C:
283 case KEY_CTL_X:
284 if (input->p != input->q)
285 {
286 char buf[sizeof input->text];
287 char *p = input->p < input->q ? input->p : input->q;
288 char *q = input->p > input->q ? input->p : input->q;
289 memmove(buf, p, q - p);
290 buf[q-p] = 0;
291 ui_set_clipboard(buf);
292 if (ui.key == KEY_CTL_X)
293 ui_input_delete_selection(input);
294 }
295 break;
296 case KEY_CTL_V:
297 {
298 const char *buf = ui_get_clipboard();
299 if (buf)
300 ui_input_paste(input, buf, (int)strlen(buf));
301 }
302 break;
303 default:
304 if (ui.key >= 32 && ui.plain)
305 {
306 int cat = ucdn_get_general_category(ui.key);
307 if (ui.key == ' ' || (cat >= UCDN_GENERAL_CATEGORY_LL && cat < UCDN_GENERAL_CATEGORY_ZL))
308 {
309 char buf[8];
310 int n = fz_runetochar(buf, ui.key);
311 ui_input_paste(input, buf, n);
312 }
313 }
314 break;
315 }
316 return UI_INPUT_EDIT;
317}
318
319void ui_input_init(struct input *input, const char *text)
320{
321 fz_strlcpy(input->text, text, sizeof input->text);
322 input->end = input->text + strlen(input->text);
323 input->p = input->text;
324 input->q = input->end;
325 input->scroll = 0;
326}
327
328int ui_input(struct input *input, int width, int height)
329{
330 struct line lines[500];
331 fz_irect area;
332 float ax, bx;
333 int ay, sy;
334 char *p, *q;
335 int state;
336 int i, n;
337
338 if (ui.focus == input)
339 state = ui_input_key(input, height > 1);
340 else
341 state = UI_INPUT_NONE;
342
343 area = ui_pack(width, ui.lineheight * height + 6);
344 ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
345 area = fz_expand_irect(area, -2);
346
347 if (height > 1)
348 area.x1 -= ui.lineheight;
349
350 n = ui_break_lines(input->text, lines, nelem(lines), area.x1-area.x0-2, NULL);
351
352 if (height > 1)
353 ui_scrollbar(area.x1, area.y0, area.x1+ui.lineheight, area.y1, &input->scroll, 1, fz_maxi(0, n-height)+1);
354 else
355 input->scroll = 0;
356
357 ax = area.x0 + 2;
358 bx = area.x1 - 2;
359 ay = area.y0 + 1;
360 sy = input->scroll * ui.lineheight;
361
362 if (ui_mouse_inside(area))
363 {
364 ui.hot = input;
365 if (!ui.active || ui.active == input)
366 ui.cursor = GLUT_CURSOR_TEXT;
367 if (!ui.active && ui.down)
368 {
369 input->p = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y);
370 ui.active = input;
371 }
372 }
373
374 if (ui.active == input)
375 {
376 input->q = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y);
377 ui.focus = input;
378 }
379
380 p = input->p < input->q ? input->p : input->q;
381 q = input->p > input->q ? input->p : input->q;
382
383 for (i = input->scroll; i < n && i < input->scroll+height; ++i)
384 {
385 char *a = lines[i].a, *b = lines[i].b;
386 if (ui.focus == input)
387 {
388 if (p >= a && p <= b && q >= a && q <= b)
389 {
390 float px = ax + ui_measure_string_part(a, p);
391 float qx = px + ui_measure_string_part(p, q);
392 glColorHex(UI_COLOR_TEXT_SEL_BG);
393 glRectf(px, ay, qx+1, ay + ui.lineheight);
394 glColorHex(UI_COLOR_TEXT_FG);
395 ui_draw_string_part(ax, ay, a, p);
396 glColorHex(UI_COLOR_TEXT_SEL_FG);
397 ui_draw_string_part(px, ay, p, q);
398 glColorHex(UI_COLOR_TEXT_FG);
399 ui_draw_string_part(qx, ay, q, b);
400 }
401 else if (p < a && q >= a && q <= b)
402 {
403 float qx = ax + ui_measure_string_part(a, q);
404 glColorHex(UI_COLOR_TEXT_SEL_BG);
405 glRectf(ax, ay, qx+1, ay + ui.lineheight);
406 glColorHex(UI_COLOR_TEXT_SEL_FG);
407 ui_draw_string_part(ax, ay, a, q);
408 glColorHex(UI_COLOR_TEXT_FG);
409 ui_draw_string_part(qx, ay, q, b);
410 }
411 else if (p >= a && p <= b && q > b)
412 {
413 float px = ax + ui_measure_string_part(a, p);
414 glColorHex(UI_COLOR_TEXT_SEL_BG);
415 glRectf(px, ay, bx, ay + ui.lineheight);
416 glColorHex(UI_COLOR_TEXT_FG);
417 ui_draw_string_part(ax, ay, a, p);
418 glColorHex(UI_COLOR_TEXT_SEL_FG);
419 ui_draw_string_part(px, ay, p, b);
420 }
421 else if (p < a && q > b)
422 {
423 glColorHex(UI_COLOR_TEXT_SEL_BG);
424 glRectf(ax, ay, bx, ay + ui.lineheight);
425 glColorHex(UI_COLOR_TEXT_SEL_FG);
426 ui_draw_string_part(ax, ay, a, b);
427 }
428 else
429 {
430 glColorHex(UI_COLOR_TEXT_FG);
431 ui_draw_string_part(ax, ay, a, b);
432 }
433 }
434 else
435 {
436 glColorHex(UI_COLOR_TEXT_FG);
437 ui_draw_string_part(ax, ay, a, b);
438 }
439 ay += ui.lineheight;
440 }
441
442 return state;
443}
444