1 /*
2 * Copyright (c) 2002-2012 Hypertriton, Inc. <http://hypertriton.com/>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 /*
27 * Keyboard input processing for AG_Editable(3).
28 */
29
30 #include <agar/core/core.h>
31 #include <agar/gui/widget.h>
32 #include <agar/gui/window.h>
33 #include <agar/gui/editable.h>
34 #include <agar/gui/keymap.h>
35 #include <agar/gui/text.h>
36 #include <agar/gui/gui_math.h>
37
38 #include <ctype.h>
39 #include <string.h>
40
41 /* Insert a new character at current cursor position. */
42 static int
Insert(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 ch)43 Insert(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 ch)
44 {
45 Uint32 ins[3];
46 int i, nIns;
47 Uint32 uch = ch;
48
49 if (keysym == 0)
50 return (0);
51 #ifdef __APPLE__
52 if ((keymod & AG_KEYMOD_LMETA) ||
53 (keymod & AG_KEYMOD_RMETA))
54 return (0);
55 #endif
56
57 if (!(ed->flags & AG_EDITABLE_NOLATIN1)) {
58 for (i = 0; ; i++) {
59 const struct ag_key_mapping *km = &agKeymapLATIN1[i];
60
61 if (keysym == km->key) {
62 if (((keymod & AG_KEYMOD_ALT) &&
63 (keymod & AG_KEYMOD_SHIFT) &&
64 (km->modmask == (AG_KEYMOD_ALT|AG_KEYMOD_SHIFT)))) {
65 uch = km->unicode;
66 break;
67 } else if (keymod & AG_KEYMOD_ALT &&
68 km->modmask == AG_KEYMOD_ALT) {
69 uch = km->unicode;
70 break;
71 }
72 } else if (km->key == AG_KEY_LAST) {
73 break;
74 }
75 }
76 }
77
78 if (uch == 0) { return (0); }
79 if (uch == '\r') { uch = '\n'; }
80
81 if (Strcasecmp(ed->encoding, "US-ASCII") == 0 &&
82 !isascii((int)uch))
83 return (0);
84
85 if (agTextComposition) {
86 if ((nIns = AG_KeyInputCompose(ed, uch, ins)) == 0)
87 return (0);
88 } else {
89 ins[0] = uch;
90 nIns = 1;
91 }
92 ins[nIns] = '\0';
93
94 if (ed->sel != 0) {
95 AG_EditableDelete(ed, buf);
96 }
97 if (AG_EditableGrowBuffer(ed, buf, ins, (size_t)nIns) == -1) {
98 Verbose("Insert Failed: %s\n", AG_GetError());
99 return (0);
100 }
101
102 if (ed->pos == buf->len) { /* Append */
103 for (i = 0; i < nIns; i++)
104 buf->s[buf->len + i] = ins[i];
105 } else { /* Insert */
106 memmove(&buf->s[ed->pos + nIns], &buf->s[ed->pos],
107 (buf->len - ed->pos)*sizeof(Uint32));
108 for (i = 0; i < nIns; i++)
109 buf->s[ed->pos + i] = ins[i];
110 }
111 buf->len += nIns;
112 buf->s[buf->len] = '\0';
113 ed->pos += nIns;
114
115 if (!(ed->flags & AG_EDITABLE_MULTILINE)) { /* Optimize case */
116 int wIns;
117 AG_TextSizeUCS4(ins, &wIns, NULL);
118 ed->xScrollPx += wIns;
119 } else {
120 ed->xScrollTo = &ed->xCurs;
121 ed->yScrollTo = &ed->yCurs;
122 }
123 ed->flags |= AG_EDITABLE_BLINK_ON;
124 return (1);
125 }
126
127 /* Delete the character at cursor, or the active selection. */
128 static int
Delete(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 unicode)129 Delete(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 unicode)
130 {
131 Uint32 *c;
132 int wDel;
133
134 if (buf->len == 0)
135 return (0);
136
137 if (ed->sel != 0) {
138 AG_EditableDelete(ed, buf);
139 return (1);
140 }
141 if (keysym == AG_KEY_BACKSPACE && ed->pos == 0) {
142 return (0);
143 }
144 if (ed->pos == buf->len) {
145 ed->pos--;
146 buf->s[--buf->len] = '\0';
147
148 if (ed->flags & AG_EDITABLE_MULTILINE) {
149 ed->xScrollTo = &ed->xCurs;
150 ed->yScrollTo = &ed->yCurs;
151 } else {
152 AG_TextSizeUCS4(&buf->s[buf->len-1], &wDel, NULL);
153 if (ed->x > 0) { ed->x -= wDel; }
154 }
155 return (1);
156 }
157 if (keysym == AG_KEY_BACKSPACE)
158 ed->pos--;
159
160 if (ed->flags & AG_EDITABLE_MULTILINE) {
161 ed->xScrollTo = &ed->xCurs;
162 ed->yScrollTo = &ed->yCurs;
163 } else {
164 Uint32 cDel[2];
165
166 cDel[0] = buf->s[ed->pos];
167 cDel[1] = '\0';
168 AG_TextSizeUCS4(cDel, &wDel, NULL);
169 if (ed->x > 0) { ed->x -= wDel; }
170 }
171
172 for (c = &buf->s[ed->pos];
173 c < &buf->s[buf->len + 1];
174 c++) {
175 *c = c[1];
176 if (*c == '\0')
177 break;
178 }
179 buf->len--;
180 return (1);
181 }
182
183 /* Copy the selection to clipboard. */
184 static int
Copy(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)185 Copy(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
186 {
187 AG_EditableCopy(ed, buf, &agEditableClipbrd);
188 return (0);
189 }
190
191 /* Copy selection to clipboard and subsequently delete it. */
192 static int
Cut(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)193 Cut(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
194 {
195 return AG_EditableCut(ed, buf, &agEditableClipbrd);
196 }
197
198 /* Paste clipboard contents to current cursor position. */
199 static int
Paste(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)200 Paste(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
201 {
202 return AG_EditablePaste(ed, buf, &agEditableClipbrd);
203 }
204
205 /*
206 * Kill the current selection; if there is no selection, cut the
207 * characters up to the end of the line (Emacs-style).
208 */
209 static int
Kill(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)210 Kill(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
211 {
212 Uint32 *c;
213
214 if (ed->sel != 0) {
215 AG_EditableValidateSelection(ed, buf);
216 if (ed->sel < 0) {
217 ed->pos += ed->sel;
218 ed->sel = -(ed->sel);
219 }
220 } else {
221 for (c = &buf->s[ed->pos]; c < &buf->s[buf->len]; c++) {
222 if (*c == '\n') {
223 break;
224 }
225 ed->sel++;
226 }
227 if (ed->sel == 0)
228 return (0);
229 }
230
231 AG_EditableCopyChunk(ed, &agEditableKillring, &buf->s[ed->pos], ed->sel);
232 AG_EditableDelete(ed, buf);
233 return (1);
234 }
235
236 /* Paste the contents of the Emacs-style kill ring at cursor position. */
237 static int
Yank(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)238 Yank(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
239 {
240 return AG_EditablePaste(ed, buf, &agEditableKillring);
241 }
242
243 /* Seek one word backwards. */
244 static int
WordBack(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)245 WordBack(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
246 {
247 int newPos = ed->pos;
248 Uint32 *c;
249
250 /* XXX: handle other types of spaces */
251 if (ed->pos > 1 && buf->s[newPos-1] == ' ') {
252 newPos -= 2;
253 }
254 for (c = &buf->s[newPos];
255 c > &buf->s[0] && *c != ' ';
256 c--, newPos--)
257 ;;
258 if (*c == ' ')
259 newPos++;
260
261 if (keymod & AG_KEYMOD_SHIFT) {
262 ed->sel += (ed->pos - newPos);
263 } else {
264 ed->sel = 0;
265 }
266 ed->pos = newPos;
267
268 ed->flags |= AG_EDITABLE_MARKPREF;
269 ed->flags |= AG_EDITABLE_BLINK_ON;
270 ed->xScrollTo = &ed->xCurs;
271 ed->yScrollTo = &ed->yCurs;
272 AG_Redraw(ed);
273 return (0);
274 }
275
276 /* Seek one word forward. */
277 static int
WordForw(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)278 WordForw(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
279 {
280 int newPos = ed->pos;
281 Uint32 *c;
282
283 if (newPos == buf->len) {
284 return (0);
285 }
286 if (buf->len > 1 && buf->s[newPos] == ' ') {
287 newPos++;
288 }
289 for (c = &buf->s[newPos];
290 *c != '\0' && *c != ' ';
291 c++, newPos++)
292 ;;
293
294 if (keymod & AG_KEYMOD_SHIFT) {
295 ed->sel += (ed->pos - newPos);
296 } else {
297 ed->sel = 0;
298 }
299 ed->pos = newPos;
300
301 ed->flags |= AG_EDITABLE_MARKPREF;
302 ed->flags |= AG_EDITABLE_BLINK_ON;
303 ed->xScrollTo = &ed->xCurs;
304 ed->yScrollTo = &ed->yCurs;
305 AG_Redraw(ed);
306 return (0);
307 }
308
309 /* Select all. */
310 static int
SelectAll(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)311 SelectAll(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
312 {
313 AG_EditableSelectAll(ed, buf);
314 return (0);
315 }
316
317 /* Move cursor to beginning of line. */
318 static int
CursorHome(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)319 CursorHome(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
320 {
321 int newPos = ed->pos;
322 Uint32 *c;
323
324 if (ed->flags & AG_EDITABLE_MULTILINE) {
325 if (newPos == 0) {
326 return (0);
327 }
328 for (c = &buf->s[newPos - 1];
329 c >= &buf->s[0] && newPos >= 0;
330 c--, newPos--) {
331 if (*c == '\n')
332 break;
333 }
334 } else {
335 newPos = 0;
336 }
337
338 if (keymod & AG_KEYMOD_SHIFT) {
339 ed->sel += (ed->pos - newPos);
340 } else {
341 ed->sel = 0;
342 }
343 ed->pos = newPos;
344
345 ed->x = 0;
346 ed->flags |= AG_EDITABLE_MARKPREF;
347 AG_Redraw(ed);
348 return (0);
349 }
350
351 /* Move cursor to end of line. */
352 static int
CursorEnd(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)353 CursorEnd(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
354 {
355 int newPos = ed->pos;
356 Uint32 *c;
357
358 if (ed->flags & AG_EDITABLE_MULTILINE) {
359 if (newPos == buf->len || buf->s[newPos] == '\n') {
360 return (0);
361 }
362 for (c = &buf->s[newPos + 1];
363 c <= &buf->s[buf->len] && newPos <= buf->len;
364 c++, newPos++) {
365 if (*c == '\n') {
366 newPos++;
367 break;
368 }
369 }
370 if (newPos > buf->len)
371 newPos = buf->len;
372 } else {
373 newPos = buf->len;
374 }
375
376 if (keymod & AG_KEYMOD_SHIFT) {
377 ed->sel += (ed->pos - newPos);
378 } else {
379 ed->sel = 0;
380 }
381 ed->pos = newPos;
382
383 ed->flags |= AG_EDITABLE_MARKPREF;
384 ed->xScrollTo = &ed->xCurs;
385 ed->yScrollTo = &ed->yCurs;
386 AG_Redraw(ed);
387 return (0);
388 }
389
390 /* Move cursor left. */
391 static int
CursorLeft(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)392 CursorLeft(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
393 {
394 if ((ed->pos - 1) >= 0) {
395 ed->pos--;
396 if (keymod & AG_KEYMOD_SHIFT) {
397 ed->sel++;
398 } else {
399 ed->sel = 0;
400 }
401 } else {
402 ed->pos = 0;
403 }
404 ed->flags |= AG_EDITABLE_MARKPREF;
405 ed->flags |= AG_EDITABLE_BLINK_ON;
406 ed->xScrollTo = &ed->xCurs;
407 ed->yScrollTo = &ed->yCurs;
408 AG_Redraw(ed);
409 return (0);
410 }
411
412 /* Move cursor right. */
413 static int
CursorRight(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)414 CursorRight(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
415 {
416 if (ed->pos < buf->len) {
417 ed->pos++;
418 if (keymod & AG_KEYMOD_SHIFT) {
419 ed->sel--;
420 } else {
421 ed->sel = 0;
422 }
423 }
424 ed->flags |= AG_EDITABLE_MARKPREF;
425 ed->flags |= AG_EDITABLE_BLINK_ON;
426 ed->xScrollTo = &ed->xCurs;
427 ed->yScrollTo = &ed->yCurs;
428 AG_Redraw(ed);
429 return (0);
430 }
431
432 /* Move cursor up in a multi-line string. */
433 static int
CursorUp(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)434 CursorUp(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
435 {
436 int prevPos = ed->pos;
437 int prevSel = ed->sel;
438
439 if (!(ed->flags & AG_EDITABLE_MULTILINE))
440 return (0);
441
442 AG_EditableMoveCursor(ed, buf, ed->xCursPref,
443 (ed->yCurs - ed->y - 1)*agTextFontLineSkip + 1);
444
445 if (keymod & AG_KEYMOD_SHIFT) {
446 ed->sel = prevSel - (ed->pos - prevPos);
447 } else {
448 ed->sel = 0;
449 }
450
451 ed->flags |= AG_EDITABLE_BLINK_ON;
452 ed->xScrollTo = &ed->xCurs;
453 ed->yScrollTo = &ed->yCurs;
454 AG_Redraw(ed);
455 return (0);
456 }
457
458 /* Move cursor down in a multi-line string. */
459 static int
CursorDown(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)460 CursorDown(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
461 {
462 int prevPos = ed->pos;
463 int prevSel = ed->sel;
464
465 if (!(ed->flags & AG_EDITABLE_MULTILINE))
466 return (0);
467
468 AG_EditableMoveCursor(ed, buf, ed->xCursPref,
469 (ed->yCurs - ed->y + 1)*agTextFontLineSkip + 1);
470
471 if (keymod & AG_KEYMOD_SHIFT) {
472 ed->sel = prevSel - (ed->pos - prevPos);
473 } else {
474 ed->sel = 0;
475 }
476
477 ed->flags |= AG_EDITABLE_BLINK_ON;
478 ed->xScrollTo = &ed->xCurs;
479 ed->yScrollTo = &ed->yCurs;
480 AG_Redraw(ed);
481 return (0);
482 }
483
484 /* Move cursor one page up in a multi-line string. */
485 static int
PageUp(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)486 PageUp(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
487 {
488 int prevPos = ed->pos;
489 int prevSel = ed->sel;
490
491 if (!(ed->flags & AG_EDITABLE_MULTILINE))
492 return (0);
493
494 AG_EditableMoveCursor(ed, buf, ed->xCurs,
495 (ed->yCurs - ed->y - ed->yVis)*agTextFontLineSkip + 1);
496
497 if (keymod & AG_KEYMOD_SHIFT) {
498 ed->sel = prevSel - (ed->pos - prevPos);
499 } else {
500 ed->sel = 0;
501 }
502
503 ed->xScrollTo = &ed->xCurs;
504 ed->yScrollTo = &ed->yCurs;
505 AG_Redraw(ed);
506 return (0);
507 }
508
509 /* Move cursor one page down in a multi-line string. */
510 static int
PageDown(AG_Editable * ed,AG_EditableBuffer * buf,AG_KeySym keysym,Uint keymod,Uint32 uch)511 PageDown(AG_Editable *ed, AG_EditableBuffer *buf, AG_KeySym keysym, Uint keymod, Uint32 uch)
512 {
513 int prevPos = ed->pos;
514 int prevSel = ed->sel;
515
516 if (!(ed->flags & AG_EDITABLE_MULTILINE))
517 return (0);
518
519 AG_EditableMoveCursor(ed, buf, ed->xCurs,
520 (ed->yCurs - ed->y + ed->yVis)*agTextFontLineSkip + 1);
521
522 if (keymod & AG_KEYMOD_SHIFT) {
523 ed->sel = prevSel - (ed->pos - prevPos);
524 } else {
525 ed->sel = 0;
526 }
527
528 ed->xScrollTo = &ed->xCurs;
529 ed->yScrollTo = &ed->yCurs;
530 AG_Redraw(ed);
531 return (0);
532 }
533
534 /*
535 * Map AG_KeySym(3) and AG_KeyMod(3) values to AG_Editable functions.
536 * Variants with modifier keys must appear first in the list. The
537 * modifiers string is passed to AG_CompareKeyMods(3).
538 *
539 * Available flags:
540 * "w" = Require a writeable buffer
541 * "e" = AG_EDITABLE_NOEMACS must be unset
542 */
543 const struct ag_keycode agKeymap[] = {
544 #ifdef __APPLE__
545 { AG_KEY_LEFT, "CM", CursorHome, "" },
546 { AG_KEY_RIGHT, "CM", CursorEnd, "" },
547 { AG_KEY_LEFT, "A", WordBack, "" },
548 { AG_KEY_RIGHT, "A", WordForw, "" },
549 { AG_KEY_A, "M", SelectAll, "" },
550 { AG_KEY_C, "M", Copy, "" },
551 { AG_KEY_X, "M", Cut, "w" },
552 { AG_KEY_V, "M", Paste, "w" },
553 { AG_KEY_K, "M", Kill, "we" },
554 { AG_KEY_Y, "M", Yank, "we" },
555 #else /* __APPLE__ */
556 { AG_KEY_LEFT, "C", WordBack, "" },
557 { AG_KEY_RIGHT, "C", WordForw, "" },
558 { AG_KEY_A, "C", SelectAll, "" },
559 { AG_KEY_C, "C", Copy, "" },
560 { AG_KEY_X, "C", Cut, "w" },
561 { AG_KEY_V, "C", Paste, "w" },
562 { AG_KEY_K, "C", Kill, "we" },
563 { AG_KEY_Y, "C", Yank, "we" },
564 #endif /* !__APPLE__ */
565 { AG_KEY_HOME, "", CursorHome, "" },
566 { AG_KEY_END, "", CursorEnd, "" },
567 { AG_KEY_LEFT, "", CursorLeft, "" },
568 { AG_KEY_RIGHT, "", CursorRight, "" },
569 { AG_KEY_UP, "", CursorUp, "" },
570 { AG_KEY_DOWN, "", CursorDown, "" },
571 { AG_KEY_PAGEUP, "", PageUp, "" },
572 { AG_KEY_PAGEDOWN, "", PageDown, "" },
573 { AG_KEY_BACKSPACE, "", Delete, "w" },
574 { AG_KEY_DELETE, "", Delete, "w" },
575 { AG_KEY_LAST, "", Insert, "w" },
576 };
577