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