1 #include "gl-app.h"
2
3 #include <string.h>
4 #include <stdio.h>
5
find_string_location(char * s,char * e,float w,float x)6 static 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
find_input_location(struct line * lines,int n,float left,float top,float x,float y)21 static 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
myisalnum(char * s)29 static 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
home_line(char * p,char * start)41 static 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
end_line(char * p,char * end)52 static 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
up_line(char * p,char * start)63 static 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
down_line(char * p,char * end)74 static 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
prev_char(char * p,char * start)85 static 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
next_char(char * p)93 static 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
prev_word(char * p,char * start)101 static 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
next_word(char * p,char * end)108 static 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
ui_input_delete_selection(struct input * input)115 static 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
ui_input_paste(struct input * input,const char * buf,int n)125 static 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
ui_do_copy(struct input * input)140 static void ui_do_copy(struct input *input)
141 {
142 if (input->p != input->q)
143 {
144 char buf[sizeof input->text];
145 char *p = input->p < input->q ? input->p : input->q;
146 char *q = input->p > input->q ? input->p : input->q;
147 memmove(buf, p, q - p);
148 buf[q-p] = 0;
149 ui_set_clipboard(buf);
150 }
151 }
152
ui_do_cut(struct input * input)153 static void ui_do_cut(struct input *input)
154 {
155 if (input->p != input->q)
156 {
157 ui_do_copy(input);
158 ui_input_delete_selection(input);
159 }
160 }
161
ui_do_paste(struct input * input)162 static void ui_do_paste(struct input *input)
163 {
164 const char *buf = ui_get_clipboard();
165 if (buf)
166 ui_input_paste(input, buf, (int)strlen(buf));
167 }
168
ui_input_key(struct input * input,int multiline)169 static int ui_input_key(struct input *input, int multiline)
170 {
171 switch (ui.key)
172 {
173 case 0:
174 return UI_INPUT_NONE;
175 case KEY_LEFT:
176 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
177 {
178 input->q = prev_word(input->q, input->text);
179 }
180 else if (ui.mod == GLUT_ACTIVE_CTRL)
181 {
182 if (input->p != input->q)
183 input->p = input->q = input->p < input->q ? input->p : input->q;
184 else
185 input->p = input->q = prev_word(input->q, input->text);
186 }
187 else if (ui.mod == GLUT_ACTIVE_SHIFT)
188 {
189 if (input->q > input->text)
190 input->q = prev_char(input->q, input->text);
191 }
192 else if (ui.mod == 0)
193 {
194 if (input->p != input->q)
195 input->p = input->q = input->p < input->q ? input->p : input->q;
196 else if (input->q > input->text)
197 input->p = input->q = prev_char(input->q, input->text);
198 }
199 break;
200 case KEY_RIGHT:
201 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
202 {
203 input->q = next_word(input->q, input->end);
204 }
205 else if (ui.mod == GLUT_ACTIVE_CTRL)
206 {
207 if (input->p != input->q)
208 input->p = input->q = input->p > input->q ? input->p : input->q;
209 else
210 input->p = input->q = next_word(input->q, input->end);
211 }
212 else if (ui.mod == GLUT_ACTIVE_SHIFT)
213 {
214 if (input->q < input->end)
215 input->q = next_char(input->q);
216 }
217 else if (ui.mod == 0)
218 {
219 if (input->p != input->q)
220 input->p = input->q = input->p > input->q ? input->p : input->q;
221 else if (input->q < input->end)
222 input->p = input->q = next_char(input->q);
223 }
224 break;
225 case KEY_UP:
226 if (ui.mod & GLUT_ACTIVE_SHIFT)
227 input->q = up_line(input->q, input->text);
228 else
229 input->p = input->q = up_line(input->p, input->text);
230 break;
231 case KEY_DOWN:
232 if (ui.mod & GLUT_ACTIVE_SHIFT)
233 input->q = down_line(input->q, input->end);
234 else
235 input->p = input->q = down_line(input->q, input->end);
236 break;
237 case KEY_HOME:
238 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
239 input->q = input->text;
240 else if (ui.mod == GLUT_ACTIVE_SHIFT)
241 input->q = home_line(input->q, input->text);
242 else if (ui.mod == GLUT_ACTIVE_CTRL)
243 input->p = input->q = input->text;
244 else if (ui.mod == 0)
245 input->p = input->q = home_line(input->p, input->text);
246 break;
247 case KEY_END:
248 if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
249 input->q = input->end;
250 else if (ui.mod == GLUT_ACTIVE_SHIFT)
251 input->q = end_line(input->q, input->end);
252 else if (ui.mod == GLUT_ACTIVE_CTRL)
253 input->p = input->q = input->end;
254 else if (ui.mod == 0)
255 input->p = input->q = end_line(input->p, input->end);
256 break;
257 case KEY_DELETE:
258 if (ui.mod == GLUT_ACTIVE_SHIFT)
259 {
260 ui_do_cut(input);
261 }
262 else if (input->p != input->q)
263 {
264 ui_input_delete_selection(input);
265 }
266 else if (input->p < input->end)
267 {
268 char *np = next_char(input->p);
269 memmove(input->p, np, input->end - np);
270 input->end -= np - input->p;
271 *input->end = 0;
272 input->q = input->p;
273 }
274 break;
275 case KEY_ESCAPE:
276 ui.focus = NULL;
277 return UI_INPUT_NONE;
278 case KEY_ENTER:
279 if (!multiline)
280 {
281 ui.focus = NULL;
282 return UI_INPUT_ACCEPT;
283 }
284 ui_input_paste(input, "\n", 1);
285 break;
286 case KEY_BACKSPACE:
287 if (input->p != input->q)
288 ui_input_delete_selection(input);
289 else if (input->p > input->text)
290 {
291 char *pp = prev_char(input->p, input->text);
292 memmove(pp, input->p, input->end - input->p);
293 input->end -= input->p - pp;
294 *input->end = 0;
295 input->q = input->p = pp;
296 }
297 break;
298 case KEY_CTL_A:
299 input->p = input->q = input->text;
300 break;
301 case KEY_CTL_E:
302 input->p = input->q = input->end;
303 break;
304 case KEY_CTL_W:
305 if (input->p != input->q)
306 ui_input_delete_selection(input);
307 else
308 {
309 input->p = prev_word(input->p, input->text);
310 ui_input_delete_selection(input);
311 }
312 break;
313 case KEY_CTL_U:
314 input->p = input->q = input->end = input->text;
315 *input->end = 0;
316 break;
317 case KEY_CTL_C:
318 ui_do_copy(input);
319 break;
320 case KEY_CTL_X:
321 ui_do_cut(input);
322 break;
323 case KEY_CTL_V:
324 ui_do_paste(input);
325 break;
326 case KEY_INSERT:
327 if (ui.mod == GLUT_ACTIVE_CTRL)
328 ui_do_copy(input);
329 if (ui.mod == GLUT_ACTIVE_SHIFT)
330 ui_do_paste(input);
331 break;
332 default:
333 if (ui.key >= 32 && ui.plain)
334 {
335 int cat = ucdn_get_general_category(ui.key);
336 if (ui.key == ' ' || (cat >= UCDN_GENERAL_CATEGORY_LL && cat < UCDN_GENERAL_CATEGORY_ZL))
337 {
338 char buf[8];
339 int n = fz_runetochar(buf, ui.key);
340 ui_input_paste(input, buf, n);
341 }
342 }
343 break;
344 }
345 return UI_INPUT_EDIT;
346 }
347
ui_input_init(struct input * input,const char * text)348 void ui_input_init(struct input *input, const char *text)
349 {
350 fz_strlcpy(input->text, text, sizeof input->text);
351 input->end = input->text + strlen(input->text);
352 input->p = input->text;
353 input->q = input->end;
354 input->scroll = 0;
355 }
356
ui_input(struct input * input,int width,int height)357 int ui_input(struct input *input, int width, int height)
358 {
359 struct line lines[500];
360 fz_irect area;
361 float ax, bx;
362 int ay, sy;
363 char *p, *q;
364 int state;
365 int i, n;
366
367 if (ui.focus == input)
368 state = ui_input_key(input, height > 1);
369 else
370 state = UI_INPUT_NONE;
371
372 area = ui_pack(width, ui.lineheight * height + 6);
373 ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
374 area = fz_expand_irect(area, -2);
375
376 if (height > 1)
377 area.x1 -= ui.lineheight;
378
379 n = ui_break_lines(input->text, lines, nelem(lines), area.x1-area.x0-2, NULL);
380
381 if (height > 1)
382 ui_scrollbar(area.x1, area.y0, area.x1+ui.lineheight, area.y1, &input->scroll, 1, fz_maxi(0, n-height)+1);
383 else
384 input->scroll = 0;
385
386 ax = area.x0 + 2;
387 bx = area.x1 - 2;
388 ay = area.y0 + 1;
389 sy = input->scroll * ui.lineheight;
390
391 if (ui_mouse_inside(area))
392 {
393 ui.hot = input;
394 if (!ui.active || ui.active == input)
395 ui.cursor = GLUT_CURSOR_TEXT;
396 if (!ui.active && ui.down)
397 {
398 input->p = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y);
399 ui.active = input;
400 }
401 }
402
403 if (ui.active == input)
404 {
405 input->q = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y);
406 ui.focus = input;
407 }
408
409 p = input->p < input->q ? input->p : input->q;
410 q = input->p > input->q ? input->p : input->q;
411
412 for (i = input->scroll; i < n && i < input->scroll+height; ++i)
413 {
414 char *a = lines[i].a, *b = lines[i].b;
415 if (ui.focus == input)
416 {
417 if (p >= a && p <= b && q >= a && q <= b)
418 {
419 float px = ax + ui_measure_string_part(a, p);
420 float qx = px + ui_measure_string_part(p, q);
421 glColorHex(UI_COLOR_TEXT_SEL_BG);
422 glRectf(px, ay, qx+1, ay + ui.lineheight);
423 glColorHex(UI_COLOR_TEXT_FG);
424 ui_draw_string_part(ax, ay, a, p);
425 glColorHex(UI_COLOR_TEXT_SEL_FG);
426 ui_draw_string_part(px, ay, p, q);
427 glColorHex(UI_COLOR_TEXT_FG);
428 ui_draw_string_part(qx, ay, q, b);
429 }
430 else if (p < a && q >= a && q <= b)
431 {
432 float qx = ax + ui_measure_string_part(a, q);
433 glColorHex(UI_COLOR_TEXT_SEL_BG);
434 glRectf(ax, ay, qx+1, ay + ui.lineheight);
435 glColorHex(UI_COLOR_TEXT_SEL_FG);
436 ui_draw_string_part(ax, ay, a, q);
437 glColorHex(UI_COLOR_TEXT_FG);
438 ui_draw_string_part(qx, ay, q, b);
439 }
440 else if (p >= a && p <= b && q > b)
441 {
442 float px = ax + ui_measure_string_part(a, p);
443 glColorHex(UI_COLOR_TEXT_SEL_BG);
444 glRectf(px, ay, bx, ay + ui.lineheight);
445 glColorHex(UI_COLOR_TEXT_FG);
446 ui_draw_string_part(ax, ay, a, p);
447 glColorHex(UI_COLOR_TEXT_SEL_FG);
448 ui_draw_string_part(px, ay, p, b);
449 }
450 else if (p < a && q > b)
451 {
452 glColorHex(UI_COLOR_TEXT_SEL_BG);
453 glRectf(ax, ay, bx, ay + ui.lineheight);
454 glColorHex(UI_COLOR_TEXT_SEL_FG);
455 ui_draw_string_part(ax, ay, a, b);
456 }
457 else
458 {
459 glColorHex(UI_COLOR_TEXT_FG);
460 ui_draw_string_part(ax, ay, a, b);
461 }
462 }
463 else
464 {
465 glColorHex(UI_COLOR_TEXT_FG);
466 ui_draw_string_part(ax, ay, a, b);
467 }
468 ay += ui.lineheight;
469 }
470
471 return state;
472 }
473