1 /*
2 * Copyright (c) 2002-2015 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 * Low-level single/multi-line text input widget. This is the base widget
28 * used by other widgets such as AG_Textbox(3).
29 */
30
31 #include <agar/core/core.h>
32
33 #include <agar/gui/editable.h>
34 #include <agar/gui/text.h>
35 #include <agar/gui/window.h>
36 #include <agar/gui/ttf.h>
37 #include <agar/gui/keymap.h>
38 #include <agar/gui/primitive.h>
39 #include <agar/gui/cursors.h>
40 #include <agar/gui/menu.h>
41 #include <agar/gui/icons.h>
42 #include <agar/gui/gui_math.h>
43
44 #include <string.h>
45 #include <stdarg.h>
46 #include <ctype.h>
47
48 #include <agar/config/have_freetype.h>
49
50 AG_EditableClipboard agEditableClipbrd; /* For Copy/Cut/Paste */
51 AG_EditableClipboard agEditableKillring; /* For Emacs-style Kill/Yank */
52
53 /*
54 * Return a new working buffer. The variable is returned locked; the caller
55 * should invoke ReleaseBuffer() after use.
56 */
57 static __inline__ AG_EditableBuffer *
GetBuffer(AG_Editable * ed)58 GetBuffer(AG_Editable *ed)
59 {
60 AG_EditableBuffer *buf;
61
62 if (ed->flags & AG_EDITABLE_EXCL) {
63 buf = &ed->sBuf;
64 } else {
65 if ((buf = TryMalloc(sizeof(AG_EditableBuffer))) == NULL) {
66 return (NULL);
67 }
68 buf->s = NULL;
69 buf->len = 0;
70 buf->maxLen = 0;
71 }
72 if (AG_Defined(ed, "text")) { /* AG_Text element */
73 AG_Text *txt;
74
75 buf->var = AG_GetVariable(ed, "text", &txt);
76 buf->reallocable = 1;
77
78 AG_MutexLock(&txt->lock);
79
80 if ((ed->flags & AG_EDITABLE_EXCL) == 0 ||
81 buf->s == NULL) {
82 AG_TextEnt *te = &txt->ent[ed->lang];
83
84 if (te->buf != NULL) {
85 buf->s = AG_ImportUnicode("UTF-8", te->buf,
86 &buf->len, &buf->maxLen);
87 } else {
88 if ((buf->s = TryMalloc(sizeof(Uint32))) != NULL) {
89 buf->s[0] = (Uint32)'\0';
90 }
91 buf->len = 0;
92 }
93 if (buf->s == NULL) {
94 AG_MutexUnlock(&txt->lock);
95 AG_UnlockVariable(buf->var);
96 buf->var = NULL;
97 goto fail;
98 }
99 }
100 } else { /* Fixed-size buffer */
101 char *s;
102
103 buf->var = AG_GetVariable(ed, "string", &s);
104 buf->reallocable = 0;
105
106 if ((ed->flags & AG_EDITABLE_EXCL) == 0 ||
107 buf->s == NULL) {
108 buf->s = AG_ImportUnicode(ed->encoding, s, &buf->len,
109 &buf->maxLen);
110 if (buf->s == NULL) {
111 AG_UnlockVariable(buf->var);
112 goto fail;
113 }
114 }
115 }
116 return (buf);
117 fail:
118 if ((ed->flags & AG_EDITABLE_EXCL) == 0) { Free(buf); }
119 return (NULL);
120 }
121
122 /* Clear a working buffer. */
123 static __inline__ void
ClearBuffer(AG_EditableBuffer * buf)124 ClearBuffer(AG_EditableBuffer *buf)
125 {
126 AG_Free(buf->s);
127 buf->s = NULL;
128 buf->len = 0;
129 buf->maxLen = 0;
130 }
131
132 /* Commit changes to the working buffer. */
133 static void
CommitBuffer(AG_Editable * ed,AG_EditableBuffer * buf)134 CommitBuffer(AG_Editable *ed, AG_EditableBuffer *buf)
135 {
136 if (AG_Defined(ed, "text")) { /* AG_Text binding */
137 AG_Text *txt = buf->var->data.p;
138 AG_TextEnt *te = &txt->ent[ed->lang];
139 size_t lenEnc;
140
141 if (AG_LengthUTF8FromUCS4(buf->s, &lenEnc) == -1) {
142 goto fail;
143 }
144 lenEnc++;
145
146 if (lenEnc > te->maxLen &&
147 AG_TextRealloc(te, lenEnc) == -1) {
148 goto fail;
149 }
150 if (AG_ExportUnicode(ed->encoding, te->buf, buf->s,
151 te->maxLen+1) == -1) {
152 goto fail;
153 }
154 } else { /* C string binding */
155 if (AG_ExportUnicode(ed->encoding, buf->var->data.s, buf->s,
156 buf->var->info.size) == -1)
157 goto fail;
158 }
159 ed->flags |= AG_EDITABLE_MARKPREF;
160 AG_PostEvent(NULL, ed, "editable-postchg", NULL);
161 return;
162 fail:
163 Verbose("CommitBuffer: %s; ignoring\n", AG_GetError());
164 }
165
166 /* Release the working buffer. */
167 static __inline__ void
ReleaseBuffer(AG_Editable * ed,AG_EditableBuffer * buf)168 ReleaseBuffer(AG_Editable *ed, AG_EditableBuffer *buf)
169 {
170 if (AG_Defined(ed, "text")) {
171 AG_Text *txt = buf->var->data.p;
172 AG_MutexUnlock(&txt->lock);
173 }
174 if (buf->var != NULL) {
175 AG_UnlockVariable(buf->var);
176 buf->var = NULL;
177 }
178 if (!(ed->flags & AG_EDITABLE_EXCL)) {
179 ClearBuffer(buf);
180 Free(buf);
181 }
182 }
183
184 /* Return a working (locked) buffer handle. */
185 AG_EditableBuffer *
AG_EditableGetBuffer(AG_Editable * ed)186 AG_EditableGetBuffer(AG_Editable *ed)
187 {
188 AG_ObjectLock(ed);
189 return GetBuffer(ed);
190 }
191
192 /* Clear a working buffer. */
193 void
AG_EditableClearBuffer(AG_Editable * ed,AG_EditableBuffer * buf)194 AG_EditableClearBuffer(AG_Editable *ed, AG_EditableBuffer *buf)
195 {
196 ClearBuffer(buf);
197 }
198
199 /* Increase the working buffer size to accomodate new characters. */
200 int
AG_EditableGrowBuffer(AG_Editable * ed,AG_EditableBuffer * buf,Uint32 * ins,size_t nIns)201 AG_EditableGrowBuffer(AG_Editable *ed, AG_EditableBuffer *buf, Uint32 *ins,
202 size_t nIns)
203 {
204 size_t ucsSize; /* UCS-4 buffer size in bytes */
205 size_t convLen; /* Converted string length in bytes */
206 Uint32 *sNew;
207
208 ucsSize = (buf->len + nIns + 1)*sizeof(Uint32);
209
210 if (Strcasecmp(ed->encoding, "UTF-8") == 0) {
211 size_t sLen, insLen;
212
213 if (AG_LengthUTF8FromUCS4(buf->s, &sLen) == -1 ||
214 AG_LengthUTF8FromUCS4(ins, &insLen) == -1) {
215 return (-1);
216 }
217 convLen = sLen + insLen + 1;
218 } else if (Strcasecmp(ed->encoding, "US-ASCII") == 0) {
219 convLen = AG_LengthUCS4(buf->s) + nIns + 1;
220 } else {
221 /* TODO Proper estimates for other charsets */
222 convLen = ucsSize;
223 }
224
225 if (!buf->reallocable) {
226 if (convLen > buf->var->info.size) {
227 AG_SetError("%u > %u bytes", (Uint)convLen, (Uint)buf->var->info.size);
228 return (-1);
229 }
230 }
231 if (ucsSize > buf->maxLen) {
232 if ((sNew = TryRealloc(buf->s, ucsSize)) == NULL) {
233 return (-1);
234 }
235 buf->s = sNew;
236 buf->maxLen = ucsSize;
237 }
238 return (0);
239 }
240
241 /* Release a working buffer. */
242 void
AG_EditableReleaseBuffer(AG_Editable * ed,AG_EditableBuffer * buf)243 AG_EditableReleaseBuffer(AG_Editable *ed, AG_EditableBuffer *buf)
244 {
245 ReleaseBuffer(ed, buf);
246 AG_ObjectUnlock(ed);
247 }
248
249 AG_Editable *
AG_EditableNew(void * parent,Uint flags)250 AG_EditableNew(void *parent, Uint flags)
251 {
252 AG_Editable *ed;
253
254 ed = Malloc(sizeof(AG_Editable));
255 AG_ObjectInit(ed, &agEditableClass);
256
257 if (flags & AG_EDITABLE_HFILL)
258 AG_ExpandHoriz(ed);
259 if (flags & AG_EDITABLE_VFILL)
260 AG_ExpandVert(ed);
261 if (flags & AG_EDITABLE_CATCH_TAB)
262 WIDGET(ed)->flags |= AG_WIDGET_CATCH_TAB;
263
264 ed->flags |= flags;
265
266 /* Set exclusive mode if requested; also sets default redraw rate. */
267 AG_EditableSetExcl(ed, (flags & AG_EDITABLE_EXCL));
268
269 AG_ObjectAttach(parent, ed);
270 return (ed);
271 }
272
273 /* Bind to a C string containing UTF-8 encoded text. */
274 void
AG_EditableBindUTF8(AG_Editable * ed,char * buf,size_t bufSize)275 AG_EditableBindUTF8(AG_Editable *ed, char *buf, size_t bufSize)
276 {
277 AG_ObjectLock(ed);
278 AG_Unset(ed, "text");
279 AG_BindString(ed, "string", buf, bufSize);
280 ed->encoding = "UTF-8";
281 AG_ObjectUnlock(ed);
282 }
283
284 /* Bind to a C string containing ASCII text. */
285 void
AG_EditableBindASCII(AG_Editable * ed,char * buf,size_t bufSize)286 AG_EditableBindASCII(AG_Editable *ed, char *buf, size_t bufSize)
287 {
288 AG_ObjectLock(ed);
289 AG_Unset(ed, "text");
290 AG_BindString(ed, "string", buf, bufSize);
291 ed->encoding = "US-ASCII";
292 AG_ObjectUnlock(ed);
293 }
294
295 /* Bind to a C string containing text in specified encoding. */
296 void
AG_EditableBindEncoded(AG_Editable * ed,const char * encoding,char * buf,size_t bufSize)297 AG_EditableBindEncoded(AG_Editable *ed, const char *encoding, char *buf,
298 size_t bufSize)
299 {
300 AG_ObjectLock(ed);
301 AG_Unset(ed, "text");
302 AG_BindString(ed, "string", buf, bufSize);
303 ed->encoding = encoding;
304 AG_ObjectUnlock(ed);
305 }
306
307 /* Bind to an AG_Text element. */
308 void
AG_EditableBindText(AG_Editable * ed,AG_Text * txt)309 AG_EditableBindText(AG_Editable *ed, AG_Text *txt)
310 {
311 AG_ObjectLock(ed);
312 AG_Unset(ed, "string");
313 AG_BindPointer(ed, "text", (void *)txt);
314 ed->encoding = "UTF-8";
315 AG_ObjectUnlock(ed);
316 }
317
318 /* Set the current language (for AG_Text bindings). */
319 void
AG_EditableSetLang(AG_Editable * ed,enum ag_language lang)320 AG_EditableSetLang(AG_Editable *ed, enum ag_language lang)
321 {
322 AG_ObjectLock(ed);
323 ed->lang = lang;
324 ed->pos = 0;
325 ed->sel = 0;
326 AG_ObjectUnlock(ed);
327 AG_Redraw(ed);
328 }
329
330 /* Enable or disable password entry mode. */
331 void
AG_EditableSetPassword(AG_Editable * ed,int enable)332 AG_EditableSetPassword(AG_Editable *ed, int enable)
333 {
334 AG_ObjectLock(ed);
335 AG_SETFLAGS(ed->flags, AG_EDITABLE_PASSWORD, enable);
336 AG_ObjectUnlock(ed);
337 }
338
339 /* Enable or disable word wrapping. */
340 void
AG_EditableSetWordWrap(AG_Editable * ed,int enable)341 AG_EditableSetWordWrap(AG_Editable *ed, int enable)
342 {
343 AG_ObjectLock(ed);
344 ed->x = 0;
345 ed->y = 0;
346 ed->pos = 0;
347 ed->sel = 0;
348 AG_SETFLAGS(ed->flags, AG_EDITABLE_WORDWRAP, enable);
349 AG_ObjectUnlock(ed);
350 }
351
352 /*
353 * Specify whether the buffer is accessed exclusively by the widget.
354 * We can disable periodic redraws in exclusive mode.
355 */
356 void
AG_EditableSetExcl(AG_Editable * ed,int enable)357 AG_EditableSetExcl(AG_Editable *ed, int enable)
358 {
359 AG_ObjectLock(ed);
360 ClearBuffer(&ed->sBuf);
361
362 if (enable) {
363 ed->flags |= AG_EDITABLE_EXCL;
364 AG_RedrawOnTick(ed, -1);
365 } else {
366 ed->flags &= ~(AG_EDITABLE_EXCL);
367 AG_RedrawOnTick(ed, 1000);
368 }
369 AG_ObjectUnlock(ed);
370 }
371
372 /* Toggle floating-point only input */
373 void
AG_EditableSetFltOnly(AG_Editable * ed,int enable)374 AG_EditableSetFltOnly(AG_Editable *ed, int enable)
375 {
376 AG_ObjectLock(ed);
377 if (enable) {
378 ed->flags |= AG_EDITABLE_FLT_ONLY;
379 ed->flags &= ~(AG_EDITABLE_INT_ONLY);
380 } else {
381 ed->flags &= ~(AG_EDITABLE_FLT_ONLY);
382 }
383 AG_ObjectUnlock(ed);
384 }
385
386 /* Toggle integer only input */
387 void
AG_EditableSetIntOnly(AG_Editable * ed,int enable)388 AG_EditableSetIntOnly(AG_Editable *ed, int enable)
389 {
390 AG_ObjectLock(ed);
391 if (enable) {
392 ed->flags |= AG_EDITABLE_INT_ONLY;
393 ed->flags &= ~(AG_EDITABLE_FLT_ONLY);
394 } else {
395 ed->flags &= ~(AG_EDITABLE_INT_ONLY);
396 }
397 AG_ObjectUnlock(ed);
398 }
399
400 /* Evaluate if a character is acceptable in integer-only mode. */
401 static __inline__ int
CharIsIntOnly(Uint32 c)402 CharIsIntOnly(Uint32 c)
403 {
404 return (c == '-' || c == '+' || isdigit((int)c));
405 }
406
407 /* Evaluate if a character is acceptable in float-only mode. */
408 static __inline__ int
CharIsFltOnly(Uint32 c)409 CharIsFltOnly(Uint32 c)
410 {
411 return (c == '+' || c == '-' || c == '.' || c == 'e' ||
412 c == 'i' || c == 'n' || c == 'f' || c == 'a' ||
413 c == 0x221e || /* Infinity */
414 isdigit((int)c));
415 }
416
417 /*
418 * Process a keystroke. May be invoked from the repeat timeout routine or
419 * the keydown handler. If we return 1, the current delay/repeat cycle will
420 * be maintained, otherwise it will be cancelled.
421 */
422 static int
ProcessKey(AG_Editable * ed,AG_KeySym ks,AG_KeyMod kmod,Uint32 unicode)423 ProcessKey(AG_Editable *ed, AG_KeySym ks, AG_KeyMod kmod, Uint32 unicode)
424 {
425 AG_EditableBuffer *buf;
426 int i, rv = 0;
427
428 if (ks == AG_KEY_ESCAPE) {
429 return (0);
430 }
431 if ((ks == AG_KEY_RETURN || ks == AG_KEY_KP_ENTER) &&
432 (ed->flags & AG_EDITABLE_MULTILINE) == 0)
433 return (0);
434
435 if (kmod == AG_KEYMOD_NONE &&
436 isascii((int)ks) &&
437 isprint((int)ks)) {
438 if ((ed->flags & AG_EDITABLE_INT_ONLY) &&
439 !CharIsIntOnly((Uint32)ks)) {
440 return (0);
441 } else if ((ed->flags & AG_EDITABLE_FLT_ONLY) &&
442 !CharIsFltOnly((Uint32)ks)) {
443 return (0);
444 }
445 }
446
447 if ((buf = GetBuffer(ed)) == NULL)
448 return (0);
449
450 if (ed->pos < 0) { ed->pos = 0; }
451 if (ed->pos > buf->len) { ed->pos = buf->len; }
452
453 for (i = 0; ; i++) {
454 const struct ag_keycode *kc = &agKeymap[i];
455 const char *flag;
456
457 if ((kc->key != AG_KEY_LAST) &&
458 (kc->key != ks || (kc->modFlags[0] != '\0' &&
459 !AG_CompareKeyMods(kmod, kc->modFlags)))) {
460 continue;
461 }
462 for (flag = &kc->flags[0]; *flag != '\0'; flag++) {
463 switch (*flag) {
464 case 'w':
465 if (AG_EditableReadOnly(ed)) {
466 rv = 0;
467 goto out;
468 }
469 break;
470 case 'e':
471 if (ed->flags & AG_EDITABLE_NOEMACS) {
472 rv = 0;
473 goto out;
474 }
475 break;
476 }
477 }
478 AG_PostEvent(NULL, ed, "editable-prechg", NULL);
479 rv = kc->func(ed, buf, ks, kmod, unicode);
480 break;
481 }
482 out:
483 if (rv == 1) {
484 CommitBuffer(ed, buf);
485 }
486 ReleaseBuffer(ed, buf);
487 return (1);
488 }
489
490 /* Timer callback for handling key repeat */
491 static Uint32
KeyRepeatTimeout(AG_Timer * to,AG_Event * event)492 KeyRepeatTimeout(AG_Timer *to, AG_Event *event)
493 {
494 AG_Editable *ed = AG_SELF();
495 int keysym = AG_INT(1);
496 int keymod = AG_INT(2);
497 Uint32 unicode = AG_ULONG(3);
498
499 if (ProcessKey(ed, keysym, keymod, unicode) == 0) {
500 return (0);
501 }
502 ed->flags |= AG_EDITABLE_BLINK_ON;
503 AG_Redraw(ed);
504 return (agKbdRepeat);
505 }
506
507 /* Timer callback for blinking cursor */
508 static Uint32
BlinkTimeout(AG_Timer * to,AG_Event * event)509 BlinkTimeout(AG_Timer *to, AG_Event *event)
510 {
511 AG_Editable *ed = AG_SELF();
512
513 if ((ed->flags & AG_EDITABLE_CURSOR_MOVING) == 0) {
514 AG_INVFLAGS(ed->flags, AG_EDITABLE_BLINK_ON);
515 AG_Redraw(ed);
516 }
517 return (to->ival);
518 }
519
520 static void
OnFocusGain(AG_Event * event)521 OnFocusGain(AG_Event *event)
522 {
523 AG_Editable *ed = AG_SELF();
524
525 AG_LockTimers(ed);
526 AG_DelTimer(ed, &ed->toRepeat);
527 AG_AddTimer(ed, &ed->toCursorBlink, agTextBlinkRate, BlinkTimeout, NULL);
528 ed->flags |= AG_EDITABLE_BLINK_ON;
529 AG_UnlockTimers(ed);
530
531 AG_Redraw(ed);
532 }
533
534 static void
OnFocusLoss(AG_Event * event)535 OnFocusLoss(AG_Event *event)
536 {
537 AG_Editable *ed = AG_SELF();
538
539 AG_LockTimers(ed);
540 AG_DelTimer(ed, &ed->toRepeat);
541 AG_DelTimer(ed, &ed->toCursorBlink);
542 AG_DelTimer(ed, &ed->toDblClick);
543 ed->flags &= ~(AG_EDITABLE_BLINK_ON|AG_EDITABLE_CURSOR_MOVING);
544 AG_UnlockTimers(ed);
545
546 AG_Redraw(ed);
547 }
548
549 static void
OnHide(AG_Event * event)550 OnHide(AG_Event *event)
551 {
552 AG_Editable *ed = AG_SELF();
553
554 if (ed->pm != NULL) {
555 AG_PopupHide(ed->pm);
556 }
557 OnFocusLoss(event);
558 }
559
560 static void
OnFontChange(AG_Event * event)561 OnFontChange(AG_Event *event)
562 {
563 AG_Editable *ed = AG_SELF();
564 AG_Font *font = WIDGET(ed)->font;
565
566 ed->lineSkip = font->lineskip;
567 ed->fontMaxHeight = font->lineskip;
568 ed->yVis = WIDGET(ed)->h / ed->lineSkip;
569 }
570
571 /*
572 * Evaluate whether the given character should be considered
573 * a space for word wrapping and word selection.
574 */
575 static __inline__ int
IsSpaceUCS4(Uint32 c)576 IsSpaceUCS4(Uint32 c)
577 {
578 switch (c) {
579 case ' ': /* SPACE */
580 case '\t': /* TAB */
581 case 0x00a0: /* NO-BREAK SPACE */
582 case 0x1680: /* OGHAM SPACE MARK */
583 case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
584 case 0x202f: /* NARROW NO-BREAK SPACE */
585 case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
586 case 0x3000: /* IDEOGRAPHIC SPACE */
587 case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
588 return (1);
589 }
590 if (c >= 0x2000 && c <= 0x200b) {
591 /* EN/EM SPACES */
592 return (1);
593 }
594 return (0);
595 }
596
597 /* Evaluate word wrapping at given character. */
598 static __inline__ int
WrapAtChar(AG_Editable * ed,int x,Uint32 * s)599 WrapAtChar(AG_Editable *ed, int x, Uint32 *s)
600 {
601 AG_Driver *drv = WIDGET(ed)->drv;
602 AG_Glyph *gl;
603 Uint32 *t;
604 int x2;
605
606 if (!(ed->flags & AG_EDITABLE_WORDWRAP) ||
607 x == 0 || !IsSpaceUCS4(*s)) {
608 return (0);
609 }
610 for (t = &s[1], x2 = x;
611 *t != '\0';
612 t++) {
613 gl = AG_TextRenderGlyph(drv, *t);
614 x2 += gl->advance;
615 if (IsSpaceUCS4(*t) || *t == '\n') {
616 if (x2 > WIDTH(ed)) {
617 return (1);
618 } else {
619 break;
620 }
621 }
622 }
623 return (0);
624 }
625
626 /*
627 * Map mouse coordinates to a position within the buffer.
628 */
629 #define ON_LINE(my,y) ((my) >= (y) && (my) <= (y)+ed->lineSkip)
630 #define ON_CHAR(mx,x,glyph) ((mx) >= (x) && (mx) <= (x)+(glyph)->advance)
631 int
AG_EditableMapPosition(AG_Editable * ed,AG_EditableBuffer * buf,int mx,int my,int * pos)632 AG_EditableMapPosition(AG_Editable *ed, AG_EditableBuffer *buf, int mx, int my,
633 int *pos)
634 {
635 AG_Driver *drv = WIDGET(ed)->drv;
636 AG_Font *font = WIDGET(ed)->font;
637 Uint32 ch;
638 int i, x, y, line = 0;
639 int nLines = 1;
640 int yMouse;
641
642 AG_ObjectLock(ed);
643
644 yMouse = my + ed->y*ed->lineSkip;
645 if (yMouse < 0) {
646 *pos = 0;
647 goto out;
648 }
649
650 x = 0;
651 y = 0;
652 for (i = 0; i < buf->len; i++) {
653 Uint32 ch = buf->s[i];
654
655 if (WrapAtChar(ed, x, &buf->s[i])) {
656 x = 0;
657 nLines++;
658 }
659 if (ch == '\n') {
660 x = 0;
661 nLines++;
662 } else if (ch == '\t') {
663 x += agTextTabWidth;
664 } else {
665 switch (font->spec.type) {
666 #ifdef HAVE_FREETYPE
667 case AG_FONT_VECTOR:
668 {
669 AG_TTFFont *ttf = font->ttf;
670 AG_TTFGlyph *glyph;
671
672 if (AG_TTFFindGlyph(ttf, ch,
673 TTF_CACHED_METRICS|TTF_CACHED_BITMAP) != 0) {
674 continue;
675 }
676 glyph = ttf->current;
677 x += glyph->advance;
678 }
679 break;
680 #endif /* HAVE_FREETYPE */
681 case AG_FONT_BITMAP:
682 {
683 AG_Glyph *gl;
684
685 gl = AG_TextRenderGlyph(drv, ch);
686 x += gl->su->w;
687 }
688 break;
689 }
690 }
691 }
692
693 x = 0;
694 for (i = 0; i < buf->len; i++) {
695 ch = buf->s[i];
696 if (mx <= 0 && ON_LINE(yMouse,y)) {
697 *pos = i;
698 goto out;
699 }
700 if (WrapAtChar(ed, x, &buf->s[i])) {
701 if (ON_LINE(yMouse,y) && mx > x) {
702 *pos = i;
703 goto out;
704 }
705 y += ed->lineSkip;
706 x = 0;
707 line++;
708 }
709 if (ch == '\n') {
710 if (ON_LINE(yMouse,y) && mx > x) {
711 *pos = i;
712 goto out;
713 }
714 y += ed->lineSkip;
715 x = 0;
716 line++;
717 continue;
718 } else if (ch == '\t') {
719 if (ON_LINE(yMouse,y) &&
720 mx >= x && mx <= x+agTextTabWidth) {
721 *pos = (mx < x + agTextTabWidth/2) ? i : i+1;
722 goto out;
723 }
724 x += agTextTabWidth;
725 continue;
726 }
727
728 switch (font->spec.type) {
729 #ifdef HAVE_FREETYPE
730 case AG_FONT_VECTOR:
731 {
732 AG_TTFFont *ttf = font->ttf;
733 AG_TTFGlyph *glyph;
734
735 if (AG_TTFFindGlyph(ttf, ch,
736 TTF_CACHED_METRICS|TTF_CACHED_BITMAP) != 0) {
737 continue;
738 }
739 glyph = ttf->current;
740
741 if (ON_LINE(yMouse,y) && ON_CHAR(mx,x,glyph)) {
742 *pos = (mx < x+glyph->advance/2) ? i : i+1;
743 goto out;
744 }
745 x += glyph->advance;
746 }
747 break;
748 #endif /* HAVE_FREETYPE */
749 case AG_FONT_BITMAP:
750 {
751 AG_Glyph *gl;
752
753 gl = AG_TextRenderGlyph(drv, ch);
754 if (ON_LINE(yMouse,y) && mx >= x && mx <= x+gl->su->w) {
755 *pos = i;
756 goto out;
757 }
758 x += gl->su->w;
759 }
760 break;
761 default:
762 AG_FatalError("AG_Editable: Unknown font format");
763 }
764 }
765 *pos = buf->len;
766 out:
767 AG_ObjectUnlock(ed);
768 return (0);
769 }
770 #undef ON_LINE
771 #undef ON_CHAR
772
773 /* Move cursor to the given position in pixels. */
774 void
AG_EditableMoveCursor(AG_Editable * ed,AG_EditableBuffer * buf,int mx,int my)775 AG_EditableMoveCursor(AG_Editable *ed, AG_EditableBuffer *buf, int mx, int my)
776 {
777 AG_ObjectLock(ed);
778 if (AG_EditableMapPosition(ed, buf, mx, my, &ed->pos) == 0) {
779 ed->sel = 0;
780 AG_Redraw(ed);
781 }
782 AG_ObjectUnlock(ed);
783 }
784
785 /* Set the cursor position (-1 = end of the string) with bounds checking. */
786 int
AG_EditableSetCursorPos(AG_Editable * ed,AG_EditableBuffer * buf,int pos)787 AG_EditableSetCursorPos(AG_Editable *ed, AG_EditableBuffer *buf, int pos)
788 {
789 int rv;
790
791 AG_ObjectLock(ed);
792 ed->pos = pos;
793 ed->sel = 0;
794 if (ed->pos < 0) {
795 if (pos == -1 || ed->pos > buf->len)
796 ed->pos = buf->len;
797 }
798 rv = ed->pos;
799 ed->xScrollTo = &ed->xCurs;
800 ed->yScrollTo = &ed->yCurs;
801 AG_ObjectUnlock(ed);
802
803 AG_Redraw(ed);
804 return (rv);
805 }
806
807 /* Do whatever we can do to make the cursor visible. */
808 static void
MoveCursorToView(AG_Editable * ed,AG_EditableBuffer * buf)809 MoveCursorToView(AG_Editable *ed, AG_EditableBuffer *buf)
810 {
811 if (ed->yCurs < ed->y) {
812 if (ed->flags & AG_EDITABLE_MULTILINE) {
813 AG_EditableMoveCursor(ed, buf,
814 ed->xCursPref - ed->x,
815 1);
816 }
817 } else if (ed->yCurs > ed->y + ed->yVis - 1) {
818 if (ed->flags & AG_EDITABLE_MULTILINE) {
819 AG_EditableMoveCursor(ed, buf,
820 ed->xCursPref - ed->x,
821 ed->yVis*ed->lineSkip - 1);
822 }
823 } else if (ed->xCurs < ed->x+10) {
824 if (!(ed->flags & AG_EDITABLE_WORDWRAP)) {
825 AG_EditableMoveCursor(ed, buf,
826 ed->x+10,
827 (ed->yCurs - ed->y)*ed->lineSkip + 1);
828 }
829 } else if (ed->xCurs > ed->x+WIDTH(ed)-10) {
830 if (!(ed->flags & AG_EDITABLE_WORDWRAP)) {
831 AG_EditableMoveCursor(ed, buf,
832 ed->x+WIDTH(ed)-10,
833 (ed->yCurs - ed->y)*ed->lineSkip + 1);
834 }
835 }
836 }
837
838 static void
Draw(void * obj)839 Draw(void *obj)
840 {
841 AG_Editable *ed = obj;
842 AG_Driver *drv = WIDGET(ed)->drv;
843 AG_DriverClass *drvOps = WIDGET(ed)->drvOps;
844 AG_EditableBuffer *buf;
845 AG_Rect2 rClip;
846 int i, dx, dy, x, y;
847 int inSel = 0;
848
849 if ((buf = GetBuffer(ed)) == NULL) {
850 return;
851 }
852 AG_EditableValidateSelection(ed, buf);
853
854 rClip = WIDGET(ed)->rView;
855 rClip.x1 -= ed->fontMaxHeight*2;
856 rClip.y1 -= ed->lineSkip;
857 rClip.x2 += ed->fontMaxHeight*2;
858 rClip.y2 += ed->lineSkip;
859
860 AG_PushBlendingMode(ed, AG_ALPHA_SRC, AG_ALPHA_ONE_MINUS_SRC);
861 AG_PushClipRect(ed, ed->r);
862
863 x = 0;
864 y = -ed->y * ed->lineSkip;
865 ed->xMax = 10;
866 ed->yMax = 1;
867 for (i = 0; i <= buf->len; i++) {
868 AG_Glyph *gl;
869 Uint32 c = buf->s[i];
870
871 if (i == ed->pos) { /* At cursor */
872 if (ed->sel == 0 &&
873 (ed->flags & AG_EDITABLE_BLINK_ON) &&
874 (ed->y >= 0 && ed->y <= ed->yMax-1) &&
875 AG_WidgetIsFocused(ed)) {
876 AG_DrawLineV(ed,
877 x - ed->x, (y + 1),
878 (y + ed->lineSkip - 1),
879 WCOLOR(ed,TEXT_COLOR));
880 }
881 ed->xCurs = x;
882 if (ed->flags & AG_EDITABLE_MARKPREF) {
883 ed->flags &= ~(AG_EDITABLE_MARKPREF);
884 ed->xCursPref = x;
885 }
886 ed->yCurs = y/ed->lineSkip + ed->y;
887 }
888 if ((ed->sel > 0 && i >= ed->pos && i < ed->pos + ed->sel) ||
889 (ed->sel < 0 && i < ed->pos && i > ed->pos + ed->sel - 1)) {
890 if (!inSel) {
891 inSel = 1;
892 ed->xSelStart = x;
893 ed->ySelStart = y/ed->lineSkip + ed->y;
894 }
895 } else {
896 if (inSel) {
897 inSel = 0;
898 ed->xSelEnd = x;
899 ed->ySelEnd = y/ed->lineSkip + ed->y;
900 }
901 }
902 if (i == buf->len)
903 break;
904
905 if (WrapAtChar(ed, x, &buf->s[i])) {
906 y += ed->lineSkip;
907 ed->xMax = MAX(ed->xMax, x);
908 ed->yMax++;
909 x = 0;
910 }
911 if (c == '\n') {
912 y += ed->lineSkip;
913 ed->xMax = MAX(ed->xMax, x+10);
914 ed->yMax++;
915 x = 0;
916 continue;
917 } else if (c == '\t') {
918 if (inSel) {
919 AG_DrawRectFilled(ed,
920 AG_RECT(x - ed->x, y,
921 agTextTabWidth+1,
922 ed->lineSkip+1),
923 WCOLOR_SEL(ed,0));
924 }
925 x += agTextTabWidth;
926 continue;
927 }
928
929 c = (ed->flags & AG_EDITABLE_PASSWORD) ? '*' : c;
930 gl = AG_TextRenderGlyph(drv, c);
931 dx = WIDGET(ed)->rView.x1 + x - ed->x;
932 dy = WIDGET(ed)->rView.y1 + y;
933
934 if (!AG_RectInside2(&rClip, dx, dy)) {
935 x += gl->advance;
936 continue;
937 }
938 if (inSel) {
939 AG_DrawRectFilled(ed,
940 AG_RECT(x - ed->x, y, gl->su->w + 1, gl->su->h),
941 WCOLOR_SEL(ed,0));
942 }
943 drvOps->drawGlyph(drv, gl, dx,dy);
944 x += gl->advance;
945 }
946 if (ed->yMax == 1)
947 ed->xMax = x;
948
949 /* Process any scrolling requests. */
950 if (ed->flags & AG_EDITABLE_KEEPVISCURSOR) {
951 MoveCursorToView(ed, buf);
952 }
953 if (ed->xScrollTo != NULL) {
954 if ((*ed->xScrollTo - ed->x) < 0) {
955 ed->x += (*ed->xScrollTo - ed->x);
956 if (ed->x < 0) { ed->x = 0; }
957 }
958 if ((*ed->xScrollTo - ed->x) > WIDTH(ed) - 10) {
959 ed->x = *ed->xScrollTo - WIDTH(ed) + 10;
960 }
961 ed->xScrollTo = NULL;
962 WIDGET(ed)->window->dirty = 1; /* Redraw once */
963 }
964 if (ed->yScrollTo != NULL) {
965 if ((*ed->yScrollTo - ed->y) < 0) {
966 ed->y += (*ed->yScrollTo - ed->y);
967 if (ed->y < 0) { ed->y = 0; }
968 }
969 if ((*ed->yScrollTo - ed->y) > ed->yVis - 1) {
970 ed->y = *ed->yScrollTo - ed->yVis + 1;
971 }
972 ed->yScrollTo = NULL;
973 WIDGET(ed)->window->dirty = 1; /* Redraw once */
974 }
975 if (ed->xScrollPx != 0) {
976 if (ed->xCurs < ed->x - ed->xScrollPx ||
977 ed->xCurs > ed->x + WIDTH(ed) - ed->xScrollPx) {
978 ed->x += ed->xScrollPx;
979 }
980 ed->xScrollPx = 0;
981 WIDGET(ed)->window->dirty = 1; /* Redraw once */
982 }
983
984 AG_PopClipRect(ed);
985 AG_PopBlendingMode(ed);
986
987 ReleaseBuffer(ed, buf);
988 }
989
990 void
AG_EditableSizeHint(AG_Editable * ed,const char * text)991 AG_EditableSizeHint(AG_Editable *ed, const char *text)
992 {
993 int hPre;
994
995 AG_ObjectLock(ed);
996 AG_TextSize(text, &ed->wPre, &hPre);
997 ed->hPre = MIN(1, hPre/ed->lineSkip);
998 AG_ObjectUnlock(ed);
999 }
1000
1001 void
AG_EditableSizeHintPixels(AG_Editable * ed,Uint w,Uint h)1002 AG_EditableSizeHintPixels(AG_Editable *ed, Uint w, Uint h)
1003 {
1004 AG_ObjectLock(ed);
1005 ed->wPre = w;
1006 ed->hPre = MIN(1, h/ed->lineSkip);
1007 AG_ObjectUnlock(ed);
1008 }
1009
1010 void
AG_EditableSizeHintLines(AG_Editable * ed,Uint nLines)1011 AG_EditableSizeHintLines(AG_Editable *ed, Uint nLines)
1012 {
1013 AG_ObjectLock(ed);
1014 ed->hPre = nLines;
1015 AG_ObjectUnlock(ed);
1016 }
1017
1018 static void
SizeRequest(void * obj,AG_SizeReq * r)1019 SizeRequest(void *obj, AG_SizeReq *r)
1020 {
1021 AG_Editable *ed = obj;
1022
1023 r->w = ed->wPre;
1024 r->h = ed->hPre*ed->lineSkip + 2;
1025 }
1026
1027 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)1028 SizeAllocate(void *obj, const AG_SizeAlloc *a)
1029 {
1030 AG_Editable *ed = obj;
1031 AG_Rect r;
1032
1033 if (a->w < 2 || a->h < 2) {
1034 return (-1);
1035 }
1036 ed->yVis = a->h/ed->lineSkip;
1037 ed->r = AG_RECT(-1, -1, a->w-1, a->h-1);
1038
1039 /* Map cursor-change area */
1040 r = AG_RECT(0, 0, a->w, a->h);
1041 AG_SetStockCursor(ed, &ed->ca, r, AG_TEXT_CURSOR);
1042 return (0);
1043 }
1044
1045 static void
KeyDown(AG_Event * event)1046 KeyDown(AG_Event *event)
1047 {
1048 AG_Editable *ed = AG_SELF();
1049 int keysym = AG_INT(1);
1050 int keymod = AG_INT(2);
1051 Uint32 unicode = (Uint32)AG_ULONG(3);
1052
1053 switch (keysym) {
1054 case AG_KEY_LSHIFT:
1055 case AG_KEY_RSHIFT:
1056 case AG_KEY_LALT:
1057 case AG_KEY_RALT:
1058 case AG_KEY_LMETA:
1059 case AG_KEY_RMETA:
1060 case AG_KEY_LCTRL:
1061 case AG_KEY_RCTRL:
1062 return;
1063 case AG_KEY_TAB:
1064 if (!(WIDGET(ed)->flags & AG_WIDGET_CATCH_TAB)) {
1065 return;
1066 }
1067 break;
1068 }
1069
1070 ed->flags |= AG_EDITABLE_BLINK_ON;
1071
1072 if (ProcessKey(ed, keysym, keymod, unicode) == 1) {
1073 AG_AddTimer(ed, &ed->toRepeat, agKbdDelay,
1074 KeyRepeatTimeout, "%i,%i,%lu", keysym, keymod, unicode);
1075 } else {
1076 AG_DelTimer(ed, &ed->toRepeat);
1077 }
1078
1079 AG_Redraw(ed);
1080 }
1081
1082 static void
KeyUp(AG_Event * event)1083 KeyUp(AG_Event *event)
1084 {
1085 AG_Editable *ed = AG_SELF();
1086 int keysym = AG_INT(1);
1087
1088 AG_DelTimer(ed, &ed->toRepeat);
1089
1090 if ((keysym == AG_KEY_RETURN || keysym == AG_KEY_KP_ENTER) &&
1091 (ed->flags & AG_EDITABLE_MULTILINE) == 0) {
1092 if (ed->flags & AG_EDITABLE_ABANDON_FOCUS) {
1093 AG_WidgetUnfocus(ed);
1094 }
1095 AG_PostEvent(NULL, ed, "editable-return", NULL);
1096 }
1097 AG_Redraw(ed);
1098 }
1099
1100 static void
MouseDoubleClick(AG_Editable * ed)1101 MouseDoubleClick(AG_Editable *ed)
1102 {
1103 AG_EditableBuffer *buf;
1104 Uint32 *c;
1105
1106 AG_DelTimer(ed, &ed->toDblClick);
1107 ed->selDblClick = -1;
1108 ed->flags |= AG_EDITABLE_WORDSELECT;
1109
1110 if ((buf = GetBuffer(ed)) == NULL) {
1111 return;
1112 }
1113 if (ed->pos >= 0 && ed->pos < buf->len) {
1114 ed->sel = 0;
1115
1116 c = &buf->s[ed->pos];
1117 if (*c == (Uint32)('\n')) {
1118 goto out;
1119 }
1120 for (;
1121 ed->pos > 0;
1122 ed->pos--, c--) {
1123 if (IsSpaceUCS4(*c) && ed->pos < buf->len) {
1124 c++;
1125 ed->pos++;
1126 break;
1127 }
1128 }
1129 while ((ed->pos + ed->sel) < buf->len &&
1130 !IsSpaceUCS4(*c)) {
1131 c++;
1132 ed->sel++;
1133 }
1134 }
1135 out:
1136 ReleaseBuffer(ed, buf);
1137 }
1138
1139 /* Ensure selection offset is positive. */
1140 static __inline__ void
NormSelection(AG_Editable * ed)1141 NormSelection(AG_Editable *ed)
1142 {
1143 if (ed->sel < 0) {
1144 ed->pos += ed->sel;
1145 ed->sel = -(ed->sel);
1146 }
1147 }
1148
1149 /* Copy specified range to specified clipboard. */
1150 void
AG_EditableCopyChunk(AG_Editable * ed,AG_EditableClipboard * cb,Uint32 * s,size_t len)1151 AG_EditableCopyChunk(AG_Editable *ed, AG_EditableClipboard *cb,
1152 Uint32 *s, size_t len)
1153 {
1154 Uint32 *sNew;
1155
1156 AG_MutexLock(&cb->lock);
1157 sNew = TryRealloc(cb->s, (len+1)*sizeof(Uint32));
1158 if (sNew != NULL) {
1159 cb->s = sNew;
1160 memcpy(cb->s, s, len*sizeof(Uint32));
1161 cb->s[len] = '\0';
1162 cb->len = len;
1163 }
1164 Strlcpy(cb->encoding, ed->encoding, sizeof(cb->encoding));
1165 AG_MutexUnlock(&cb->lock);
1166 }
1167
1168 /* Perform Cut action on buffer. */
1169 int
AG_EditableCut(AG_Editable * ed,AG_EditableBuffer * buf,AG_EditableClipboard * cb)1170 AG_EditableCut(AG_Editable *ed, AG_EditableBuffer *buf, AG_EditableClipboard *cb)
1171 {
1172 if (AG_EditableReadOnly(ed) || ed->sel == 0) {
1173 return (0);
1174 }
1175 AG_EditableValidateSelection(ed, buf);
1176 NormSelection(ed);
1177 AG_EditableCopyChunk(ed, cb, &buf->s[ed->pos], ed->sel);
1178 AG_EditableDelete(ed, buf);
1179 return (1);
1180 }
1181
1182 /* Perform Copy action on standard clipboard. */
1183 int
AG_EditableCopy(AG_Editable * ed,AG_EditableBuffer * buf,AG_EditableClipboard * cb)1184 AG_EditableCopy(AG_Editable *ed, AG_EditableBuffer *buf, AG_EditableClipboard *cb)
1185 {
1186 if (ed->sel == 0) {
1187 return (0);
1188 }
1189 AG_EditableValidateSelection(ed, buf);
1190 NormSelection(ed);
1191 AG_EditableCopyChunk(ed, cb, &buf->s[ed->pos], ed->sel);
1192 return (1);
1193 }
1194
1195 /* Perform Paste action on buffer. */
1196 int
AG_EditablePaste(AG_Editable * ed,AG_EditableBuffer * buf,AG_EditableClipboard * cb)1197 AG_EditablePaste(AG_Editable *ed, AG_EditableBuffer *buf,
1198 AG_EditableClipboard *cb)
1199 {
1200 Uint32 *c;
1201
1202 if (AG_EditableReadOnly(ed))
1203 return (0);
1204
1205 if (ed->sel != 0)
1206 AG_EditableDelete(ed, buf);
1207
1208 AG_MutexLock(&cb->lock);
1209
1210 if (cb->s == NULL)
1211 goto out;
1212
1213 if (!(ed->flags & AG_EDITABLE_MULTILINE)) {
1214 for (c = &cb->s[0]; *c != '\0'; c++) {
1215 if (*c == '\n') {
1216 *c = '\0';
1217 break;
1218 }
1219 }
1220 }
1221 if (ed->flags & AG_EDITABLE_INT_ONLY) {
1222 for (c = &cb->s[0]; *c != '\0'; c++) {
1223 if (!CharIsIntOnly(*c)) {
1224 AG_SetError(_("Non-integer input near `%c'"), (char)*c);
1225 goto fail;
1226 }
1227 }
1228 } else if (ed->flags & AG_EDITABLE_FLT_ONLY) {
1229 for (c = &cb->s[0]; *c != '\0'; c++) {
1230 if (!CharIsFltOnly(*c)) {
1231 AG_SetError(_("Non-float input near `%c'"), (char)*c);
1232 goto fail;
1233 }
1234 }
1235 }
1236
1237 if (AG_EditableGrowBuffer(ed, buf, cb->s, cb->len) == -1) {
1238 goto fail;
1239 }
1240 if (ed->pos < buf->len) {
1241 memmove(&buf->s[ed->pos + cb->len], &buf->s[ed->pos],
1242 (buf->len - ed->pos)*sizeof(Uint32));
1243 }
1244 memcpy(&buf->s[ed->pos], cb->s, cb->len*sizeof(Uint32));
1245 buf->len += cb->len;
1246 buf->s[buf->len] = '\0';
1247 ed->pos += cb->len;
1248 ed->xScrollTo = &ed->xCurs;
1249 ed->yScrollTo = &ed->yCurs;
1250 out:
1251 AG_MutexUnlock(&cb->lock);
1252 return (1);
1253 fail:
1254 Verbose("Paste Failed: %s\n", AG_GetError());
1255 AG_MutexUnlock(&cb->lock);
1256 return (0);
1257 }
1258
1259 /* Delete the current selection. */
1260 int
AG_EditableDelete(AG_Editable * ed,AG_EditableBuffer * buf)1261 AG_EditableDelete(AG_Editable *ed, AG_EditableBuffer *buf)
1262 {
1263 if (AG_EditableReadOnly(ed) || ed->sel == 0)
1264 return (0);
1265
1266 AG_EditableValidateSelection(ed, buf);
1267 NormSelection(ed);
1268 if (ed->pos + ed->sel == buf->len) {
1269 buf->s[ed->pos] = '\0';
1270 } else {
1271 memmove(&buf->s[ed->pos], &buf->s[ed->pos + ed->sel],
1272 (buf->len - ed->sel + 1 - ed->pos)*sizeof(Uint32));
1273 }
1274 buf->len -= ed->sel;
1275 ed->sel = 0;
1276 ed->xScrollTo = &ed->xCurs;
1277 ed->yScrollTo = &ed->yCurs;
1278 return (1);
1279 }
1280
1281 /* Perform "Select All" on buffer. */
1282 void
AG_EditableSelectAll(AG_Editable * ed,AG_EditableBuffer * buf)1283 AG_EditableSelectAll(AG_Editable *ed, AG_EditableBuffer *buf)
1284 {
1285 ed->pos = 0;
1286 ed->sel = buf->len;
1287 AG_Redraw(ed);
1288 }
1289
1290 /*
1291 * Right-click popup menu actions.
1292 */
1293 static void
MenuCut(AG_Event * event)1294 MenuCut(AG_Event *event)
1295 {
1296 AG_Editable *ed = AG_PTR(1);
1297 AG_EditableBuffer *buf;
1298
1299 if ((buf = GetBuffer(ed)) != NULL) {
1300 if (AG_EditableCut(ed, buf, &agEditableClipbrd)) {
1301 CommitBuffer(ed, buf);
1302 }
1303 ReleaseBuffer(ed, buf);
1304 }
1305 }
1306 static int
MenuCutActive(AG_Event * event)1307 MenuCutActive(AG_Event *event)
1308 {
1309 AG_Editable *ed = AG_PTR(1);
1310 return (!AG_EditableReadOnly(ed) && ed->sel != 0);
1311 }
1312
1313 static void
MenuCopy(AG_Event * event)1314 MenuCopy(AG_Event *event)
1315 {
1316 AG_Editable *ed = AG_PTR(1);
1317 AG_EditableBuffer *buf;
1318
1319 if ((buf = GetBuffer(ed)) != NULL) {
1320 AG_EditableCopy(ed, buf, &agEditableClipbrd);
1321 ReleaseBuffer(ed, buf);
1322 }
1323 }
1324 static int
MenuCopyActive(AG_Event * event)1325 MenuCopyActive(AG_Event *event)
1326 {
1327 AG_Editable *ed = AG_PTR(1);
1328 return (ed->sel != 0);
1329 }
1330
1331 static void
MenuPaste(AG_Event * event)1332 MenuPaste(AG_Event *event)
1333 {
1334 AG_Editable *ed = AG_PTR(1);
1335 AG_EditableBuffer *buf;
1336
1337 if ((buf = GetBuffer(ed)) != NULL) {
1338 if (AG_EditablePaste(ed, buf, &agEditableClipbrd)) {
1339 CommitBuffer(ed, buf);
1340 }
1341 ReleaseBuffer(ed, buf);
1342 }
1343 }
1344 static int
MenuPasteActive(AG_Event * event)1345 MenuPasteActive(AG_Event *event)
1346 {
1347 AG_Editable *ed = AG_PTR(1);
1348 return !AG_EditableReadOnly(ed) && agEditableClipbrd.len > 0;
1349 }
1350
1351 static void
MenuDelete(AG_Event * event)1352 MenuDelete(AG_Event *event)
1353 {
1354 AG_Editable *ed = AG_PTR(1);
1355 AG_EditableBuffer *buf;
1356
1357 if ((buf = GetBuffer(ed)) != NULL) {
1358 if (AG_EditableDelete(ed, buf)) {
1359 CommitBuffer(ed, buf);
1360 }
1361 ReleaseBuffer(ed, buf);
1362 }
1363 }
1364 static int
MenuDeleteActive(AG_Event * event)1365 MenuDeleteActive(AG_Event *event)
1366 {
1367 AG_Editable *ed = AG_PTR(1);
1368 return (!AG_EditableReadOnly(ed) && ed->sel != 0);
1369 }
1370
1371 static void
MenuSelectAll(AG_Event * event)1372 MenuSelectAll(AG_Event *event)
1373 {
1374 AG_Editable *ed = AG_PTR(1);
1375 AG_EditableBuffer *buf;
1376
1377 if ((buf = GetBuffer(ed)) != NULL) {
1378 AG_EditableSelectAll(ed, buf);
1379 ReleaseBuffer(ed, buf);
1380 }
1381 }
1382 static void
MenuSetLang(AG_Event * event)1383 MenuSetLang(AG_Event *event)
1384 {
1385 AG_Editable *ed = AG_PTR(1);
1386 enum ag_language lang = (enum ag_language)AG_INT(2);
1387
1388 AG_EditableSetLang(ed, lang);
1389 }
1390 static AG_PopupMenu *
PopupMenu(AG_Editable * ed)1391 PopupMenu(AG_Editable *ed)
1392 {
1393 AG_PopupMenu *pm;
1394 AG_MenuItem *mi;
1395 AG_Variable *vText;
1396 AG_Text *txt;
1397
1398 if ((pm = AG_PopupNew(ed)) == NULL) {
1399 return (NULL);
1400 }
1401 mi = AG_MenuAction(pm->root, _("Cut"), NULL, MenuCut, "%p", ed);
1402 mi->stateFn = AG_SetIntFn(pm->menu, MenuCutActive, "%p", ed);
1403 mi = AG_MenuAction(pm->root, _("Copy"), NULL, MenuCopy, "%p", ed);
1404 mi->stateFn = AG_SetIntFn(pm->menu, MenuCopyActive, "%p", ed);
1405 mi = AG_MenuAction(pm->root, _("Paste"), NULL, MenuPaste, "%p", ed);
1406 mi->stateFn = AG_SetIntFn(pm->menu, MenuPasteActive, "%p", ed);
1407 mi = AG_MenuAction(pm->root, _("Delete"), NULL, MenuDelete, "%p", ed);
1408 mi->stateFn = AG_SetIntFn(pm->menu, MenuDeleteActive, "%p", ed);
1409
1410 AG_MenuSeparator(pm->root);
1411
1412 AG_MenuAction(pm->root, _("Select All"), NULL, MenuSelectAll, "%p", ed);
1413
1414 if ((ed->flags & AG_EDITABLE_MULTILINGUAL) &&
1415 AG_Defined(ed, "text") &&
1416 (vText = AG_GetVariable(ed, "text", &txt)) != NULL) {
1417 AG_MenuItem *mLang;
1418 int i;
1419
1420 AG_MenuSeparator(pm->root);
1421 mLang = AG_MenuNode(pm->root, _("Select Language"), NULL);
1422 for (i = 0; i < AG_LANG_LAST; i++) {
1423 AG_TextEnt *te = &txt->ent[i];
1424
1425 if (te->len == 0)
1426 continue;
1427
1428 AG_MenuAction(mLang, _(agLanguageNames[i]),
1429 NULL, MenuSetLang, "%p,%i", ed, i);
1430 }
1431 AG_UnlockVariable(vText);
1432 }
1433 return (pm);
1434 }
1435
1436 /* Timer for detecting double clicks. */
1437 static Uint32
DoubleClickTimeout(AG_Timer * to,AG_Event * event)1438 DoubleClickTimeout(AG_Timer *to, AG_Event *event)
1439 {
1440 AG_Editable *ed = AG_SELF();
1441
1442 ed->selDblClick = -1;
1443 return (0);
1444 }
1445
1446 static void
MouseButtonDown(AG_Event * event)1447 MouseButtonDown(AG_Event *event)
1448 {
1449 AG_Editable *ed = AG_SELF();
1450 int btn = AG_INT(1);
1451 int mx = AG_INT(2);
1452 int my = AG_INT(3);
1453 AG_EditableBuffer *buf;
1454
1455 if (!AG_WidgetIsFocused(ed))
1456 AG_WidgetFocus(ed);
1457
1458 switch (btn) {
1459 case AG_MOUSE_LEFT:
1460 if (ed->pm != NULL) {
1461 AG_PopupHide(ed->pm);
1462 }
1463 ed->flags |= AG_EDITABLE_CURSOR_MOVING|AG_EDITABLE_BLINK_ON;
1464 mx += ed->x;
1465 if ((buf = GetBuffer(ed)) == NULL) {
1466 return;
1467 }
1468 AG_EditableMoveCursor(ed, buf, mx, my);
1469 ReleaseBuffer(ed, buf);
1470 ed->flags |= AG_EDITABLE_MARKPREF;
1471
1472 if (ed->selDblClick != -1 &&
1473 Fabs(ed->selDblClick - ed->pos) <= 1) {
1474 MouseDoubleClick(ed);
1475 } else {
1476 ed->selDblClick = ed->pos;
1477 AG_AddTimer(ed, &ed->toDblClick, agMouseDblclickDelay,
1478 DoubleClickTimeout, NULL);
1479 }
1480 break;
1481 case AG_MOUSE_RIGHT:
1482 if ((ed->flags & AG_EDITABLE_NOPOPUP) == 0) {
1483 if (ed->pm != NULL) {
1484 AG_PopupShowAt(ed->pm, mx, my);
1485 } else {
1486 if ((ed->pm = PopupMenu(ed)) != NULL)
1487 AG_PopupShowAt(ed->pm, mx,my);
1488 }
1489 }
1490 break;
1491 case AG_MOUSE_WHEELUP:
1492 if (ed->flags & AG_EDITABLE_MULTILINE) {
1493 ed->y -= AG_WidgetScrollDelta(&ed->wheelTicks);
1494 if (ed->y < 0) { ed->y = 0; }
1495 }
1496 break;
1497 case AG_MOUSE_WHEELDOWN:
1498 if (ed->flags & AG_EDITABLE_MULTILINE) {
1499 ed->y += AG_WidgetScrollDelta(&ed->wheelTicks);
1500 ed->y = MIN(ed->y, ed->yMax - ed->yVis);
1501 }
1502 break;
1503 }
1504
1505 AG_Redraw(ed);
1506 }
1507
1508 static void
MouseButtonUp(AG_Event * event)1509 MouseButtonUp(AG_Event *event)
1510 {
1511 AG_Editable *ed = AG_SELF();
1512 int btn = AG_INT(1);
1513
1514 switch (btn) {
1515 case AG_MOUSE_LEFT:
1516 ed->flags &= ~(AG_EDITABLE_CURSOR_MOVING);
1517 ed->flags &= ~(AG_EDITABLE_WORDSELECT);
1518 AG_Redraw(ed);
1519 break;
1520 }
1521 }
1522
1523 static void
MouseMotion(AG_Event * event)1524 MouseMotion(AG_Event *event)
1525 {
1526 AG_Editable *ed = AG_SELF();
1527 AG_EditableBuffer *buf;
1528 int mx = AG_INT(1);
1529 int my = AG_INT(2);
1530 int newPos;
1531
1532 if (!AG_WidgetIsFocused(ed) ||
1533 (ed->flags & AG_EDITABLE_CURSOR_MOVING) == 0)
1534 return;
1535
1536 if ((buf = GetBuffer(ed)) == NULL)
1537 return;
1538 if (AG_EditableMapPosition(ed, buf, ed->x + mx, my, &newPos) == -1)
1539 goto out;
1540
1541 if (ed->flags & AG_EDITABLE_WORDSELECT) {
1542 Uint32 *c;
1543
1544 c = &buf->s[newPos];
1545 if (*c == (Uint32)('\n')) {
1546 goto out;
1547 }
1548 if (newPos > ed->pos) {
1549 if (ed->sel < 0) {
1550 ed->pos += ed->sel;
1551 ed->sel = -(ed->sel);
1552 }
1553 ed->sel = newPos - ed->pos;
1554 while (c < &buf->s[buf->len] &&
1555 !IsSpaceUCS4(*c) && *c != (Uint32)'\n') {
1556 c++;
1557 ed->sel++;
1558 }
1559 ed->xScrollTo = &ed->xSelEnd;
1560 ed->yScrollTo = &ed->ySelEnd;
1561 } else if (newPos < ed->pos) {
1562 if (ed->sel > 0) {
1563 ed->pos += ed->sel;
1564 ed->sel = -(ed->sel);
1565 }
1566 ed->sel = newPos - ed->pos;
1567 while (c > &buf->s[0] &&
1568 !IsSpaceUCS4(*c) && *c != (Uint32)'\n') {
1569 c--;
1570 ed->sel--;
1571 }
1572 if (IsSpaceUCS4(buf->s[ed->pos + ed->sel])) {
1573 ed->sel++;
1574 }
1575 ed->xScrollTo = &ed->xSelStart;
1576 ed->yScrollTo = &ed->ySelStart;
1577 }
1578 AG_Redraw(ed);
1579 } else {
1580 ed->sel = newPos - ed->pos;
1581 if (ed->sel > 0) {
1582 ed->xScrollTo = &ed->xSelEnd;
1583 ed->yScrollTo = &ed->ySelEnd;
1584 } else if (ed->sel < 0) {
1585 ed->xScrollTo = &ed->xSelStart;
1586 ed->yScrollTo = &ed->ySelStart;
1587 }
1588 AG_Redraw(ed);
1589 }
1590 out:
1591 ReleaseBuffer(ed, buf);
1592 }
1593
1594 /*
1595 * Overwrite the contents of the text buffer with the given string
1596 * (supplied in UTF-8).
1597 */
1598 void
AG_EditableSetString(AG_Editable * ed,const char * text)1599 AG_EditableSetString(AG_Editable *ed, const char *text)
1600 {
1601 AG_EditableBuffer *buf;
1602
1603 AG_ObjectLock(ed);
1604 if ((buf = GetBuffer(ed)) == NULL) {
1605 goto out;
1606 }
1607 if (text != NULL) {
1608 Free(buf->s);
1609 buf->s = AG_ImportUnicode("UTF-8", text, &buf->len, &buf->maxLen);
1610 if (buf->s != NULL) {
1611 ed->pos = buf->len;
1612 } else {
1613 buf->len = 0;
1614 }
1615 } else {
1616 if (buf->maxLen >= sizeof(Uint32)) {
1617 buf->s[0] = '\0';
1618 ed->pos = 0;
1619 buf->len = 0;
1620 }
1621 }
1622 ed->sel = 0;
1623 CommitBuffer(ed, buf);
1624 ReleaseBuffer(ed, buf);
1625 out:
1626 AG_ObjectUnlock(ed);
1627 AG_Redraw(ed);
1628 }
1629
1630 /*
1631 * Overwrite the contents of the text buffer with the given formatted
1632 * C string (in UTF-8 encoding).
1633 */
1634 void
AG_EditablePrintf(void * obj,const char * fmt,...)1635 AG_EditablePrintf(void *obj, const char *fmt, ...)
1636 {
1637 AG_Editable *ed = obj;
1638 AG_EditableBuffer *buf;
1639 va_list ap;
1640 char *s;
1641
1642 #ifdef AG_DEBUG
1643 if (!AG_OfClass(obj, "AG_Widget:AG_Editable:*") &&
1644 !AG_OfClass(obj, "AG_Widget:AG_Textbox:*"))
1645 AG_FatalError(NULL);
1646 #endif
1647
1648 AG_ObjectLock(ed);
1649 if ((buf = GetBuffer(ed)) == NULL) {
1650 goto out;
1651 }
1652 if (fmt != NULL && fmt[0] != '\0') {
1653 va_start(ap, fmt);
1654 Vasprintf(&s, fmt, ap);
1655 va_end(ap);
1656
1657 Free(buf->s);
1658 buf->s = AG_ImportUnicode("UTF-8", s, &buf->len, &buf->maxLen);
1659 free(s);
1660
1661 if (buf->s != NULL) {
1662 ed->pos = buf->len;
1663 } else {
1664 buf->len = 0;
1665 }
1666 } else {
1667 Free(buf->s);
1668 if ((buf->s = TryMalloc(sizeof(Uint32))) != NULL) {
1669 buf->s[0] = '\0';
1670 }
1671 ed->pos = 0;
1672 buf->len = 0;
1673 }
1674 ed->sel = 0;
1675 CommitBuffer(ed, buf);
1676 ReleaseBuffer(ed, buf);
1677 out:
1678 AG_ObjectUnlock(ed);
1679 AG_Redraw(ed);
1680 }
1681
1682 /* Return a duplicate of the current string. */
1683 char *
AG_EditableDupString(AG_Editable * ed)1684 AG_EditableDupString(AG_Editable *ed)
1685 {
1686 AG_Variable *var;
1687 char *sDup, *s;
1688
1689 AG_ObjectLock(ed);
1690 if (AG_Defined(ed, "text")) {
1691 AG_Text *txt;
1692 var = AG_GetVariable(ed, "text", &txt);
1693 s = txt->ent[ed->lang].buf;
1694 sDup = TryStrdup((s != NULL) ? s : "");
1695 } else {
1696 var = AG_GetVariable(ed, "string", &s);
1697 sDup = TryStrdup(s);
1698 }
1699 AG_UnlockVariable(var);
1700 AG_ObjectUnlock(ed);
1701 return (sDup);
1702 }
1703
1704 /* Copy text to a fixed-size buffer and always NUL-terminate. */
1705 size_t
AG_EditableCopyString(AG_Editable * ed,char * dst,size_t dst_size)1706 AG_EditableCopyString(AG_Editable *ed, char *dst, size_t dst_size)
1707 {
1708 AG_Variable *var;
1709 size_t rv;
1710 char *s;
1711
1712 AG_ObjectLock(ed);
1713 if (AG_Defined(ed, "text")) {
1714 AG_Text *txt;
1715 var = AG_GetVariable(ed, "text", &txt);
1716 s = txt->ent[ed->lang].buf;
1717 rv = Strlcpy(dst, (s != NULL) ? s : "", dst_size);
1718 } else {
1719 var = AG_GetVariable(ed, "string", &s);
1720 rv = Strlcpy(dst, s, dst_size);
1721 }
1722 AG_UnlockVariable(var);
1723 AG_ObjectUnlock(ed);
1724 return (rv);
1725 }
1726
1727 /* Perform trivial conversion from string to int. */
1728 int
AG_EditableInt(AG_Editable * ed)1729 AG_EditableInt(AG_Editable *ed)
1730 {
1731 char abuf[32];
1732 AG_EditableBuffer *buf;
1733 int i;
1734
1735 AG_ObjectLock(ed);
1736 if ((buf = GetBuffer(ed)) == NULL) {
1737 AG_FatalError(NULL);
1738 }
1739 AG_ExportUnicode("UTF-8", abuf, buf->s, sizeof(abuf));
1740 i = atoi(abuf);
1741 ReleaseBuffer(ed, buf);
1742 AG_ObjectUnlock(ed);
1743 return (i);
1744 }
1745
1746 /* Perform trivial conversion from string to float . */
1747 float
AG_EditableFlt(AG_Editable * ed)1748 AG_EditableFlt(AG_Editable *ed)
1749 {
1750 char abuf[32];
1751 AG_EditableBuffer *buf;
1752 float flt;
1753
1754 AG_ObjectLock(ed);
1755 if ((buf = GetBuffer(ed)) == NULL) {
1756 AG_FatalError(NULL);
1757 }
1758 AG_ExportUnicode("UTF-8", abuf, buf->s, sizeof(abuf));
1759 flt = (float)strtod(abuf, NULL);
1760 ReleaseBuffer(ed, buf);
1761 AG_ObjectUnlock(ed);
1762 return (flt);
1763 }
1764
1765 /* Perform trivial conversion from string to double. */
1766 double
AG_EditableDbl(AG_Editable * ed)1767 AG_EditableDbl(AG_Editable *ed)
1768 {
1769 char abuf[32];
1770 AG_EditableBuffer *buf;
1771 double flt;
1772
1773 AG_ObjectLock(ed);
1774 if ((buf = GetBuffer(ed)) == NULL) {
1775 AG_FatalError(NULL);
1776 }
1777 AG_ExportUnicode("UTF-8", abuf, buf->s, sizeof(abuf));
1778 flt = strtod(abuf, NULL);
1779 ReleaseBuffer(ed, buf);
1780 AG_ObjectUnlock(ed);
1781 return (flt);
1782 }
1783
1784 static void
OnBindingChange(AG_Event * event)1785 OnBindingChange(AG_Event *event)
1786 {
1787 AG_Editable *ed = AG_SELF();
1788 AG_Variable *binding = AG_PTR(1);
1789
1790 if (strcmp(binding->name, "string") == 0) {
1791 AG_Unset(ed, "text");
1792 } else if (strcmp(binding->name, "text") == 0) {
1793 AG_Unset(ed, "string");
1794 }
1795 }
1796
1797 static void
Init(void * obj)1798 Init(void *obj)
1799 {
1800 AG_Editable *ed = obj;
1801
1802 WIDGET(ed)->flags |= AG_WIDGET_FOCUSABLE|
1803 AG_WIDGET_UNFOCUSED_MOTION|
1804 AG_WIDGET_TABLE_EMBEDDABLE|
1805 AG_WIDGET_USE_TEXT|
1806 AG_WIDGET_USE_MOUSEOVER;
1807
1808 ed->encoding = "UTF-8";
1809
1810 if ((ed->text = AG_TextNew(0)) == NULL)
1811 AG_FatalError(NULL);
1812
1813 ed->flags = AG_EDITABLE_BLINK_ON|AG_EDITABLE_MARKPREF;
1814 ed->pos = 0;
1815 ed->sel = 0;
1816 ed->selDblClick = -1;
1817 ed->compose = 0;
1818 ed->xCurs = 0;
1819 ed->yCurs = 0;
1820 ed->xCursPref = 0;
1821 ed->xSelStart = 0;
1822 ed->ySelStart = 0;
1823 ed->xScrollTo = NULL;
1824 ed->yScrollTo = NULL;
1825 ed->xScrollPx = 0;
1826
1827 ed->x = 0;
1828 ed->xMax = 10;
1829 ed->y = 0;
1830 ed->yMax = 1;
1831 ed->yVis = 1;
1832 ed->wheelTicks = 0;
1833 ed->r = AG_RECT(0,0,0,0);
1834 ed->ca = NULL;
1835 ed->fontMaxHeight = agTextFontHeight;
1836 ed->lineSkip = agTextFontLineSkip;
1837 ed->pm = NULL;
1838 ed->lang = AG_LANG_NONE;
1839 ed->wPre = 0;
1840 ed->hPre = 1;
1841
1842 ed->sBuf.var = NULL;
1843 ed->sBuf.s = NULL;
1844 ed->sBuf.len = 0;
1845 ed->sBuf.maxLen = 0;
1846 ed->sBuf.reallocable = 0;
1847
1848 AG_InitTimer(&ed->toRepeat, "repeat", 0);
1849 AG_InitTimer(&ed->toCursorBlink, "cursorBlink", 0);
1850 AG_InitTimer(&ed->toDblClick, "dblClick", 0);
1851
1852 AG_SetEvent(ed, "bound", OnBindingChange, NULL);
1853 AG_AddEvent(ed, "font-changed", OnFontChange, NULL);
1854 AG_AddEvent(ed, "widget-hidden", OnHide, NULL);
1855 AG_SetEvent(ed, "widget-lostfocus", OnFocusLoss, NULL);
1856 AG_SetEvent(ed, "key-down", KeyDown, NULL);
1857 AG_SetEvent(ed, "key-up", KeyUp, NULL);
1858 AG_SetEvent(ed, "mouse-button-down", MouseButtonDown, NULL);
1859 AG_SetEvent(ed, "mouse-button-up", MouseButtonUp, NULL);
1860 AG_SetEvent(ed, "mouse-motion", MouseMotion, NULL);
1861 AG_SetEvent(ed, "widget-gainfocus", OnFocusGain, NULL);
1862
1863 AG_BindPointer(ed, "text", (void *)ed->text);
1864
1865 AG_RedrawOnTick(ed, 1000);
1866
1867 #ifdef AG_DEBUG
1868 AG_BindInt(ed, "pos", &ed->pos);
1869 AG_BindInt(ed, "sel", &ed->sel);
1870 AG_BindInt(ed, "xCurs", &ed->xCurs);
1871 AG_BindInt(ed, "yCurs", &ed->yCurs);
1872 AG_BindInt(ed, "xCursPref", &ed->xCursPref);
1873 AG_BindInt(ed, "x", &ed->x);
1874 AG_BindInt(ed, "xMax", &ed->xMax);
1875 AG_BindInt(ed, "y", &ed->y);
1876 AG_BindInt(ed, "yMax", &ed->yMax);
1877 AG_BindInt(ed, "yVis", &ed->yVis);
1878 #endif /* AG_DEBUG */
1879 }
1880
1881 static void
Destroy(void * obj)1882 Destroy(void *obj)
1883 {
1884 AG_Editable *ed = obj;
1885
1886 if (ed->pm != NULL) {
1887 AG_PopupDestroy(ed->pm);
1888 }
1889 if (ed->flags & AG_EDITABLE_EXCL)
1890 Free(ed->sBuf.s);
1891
1892 AG_TextFree(ed->text);
1893 }
1894
1895 /* Initialize/release the global clipboards. */
1896 static void
InitClipboard(AG_EditableClipboard * cb)1897 InitClipboard(AG_EditableClipboard *cb)
1898 {
1899 AG_MutexInit(&cb->lock);
1900 Strlcpy(cb->encoding, "UTF-8", sizeof(cb->encoding));
1901 cb->s = NULL;
1902 cb->len = 0;
1903 }
1904 static void
FreeClipboard(AG_EditableClipboard * cb)1905 FreeClipboard(AG_EditableClipboard *cb)
1906 {
1907 Free(cb->s);
1908 AG_MutexDestroy(&cb->lock);
1909 }
1910 void
AG_EditableInitClipboards(void)1911 AG_EditableInitClipboards(void)
1912 {
1913 InitClipboard(&agEditableClipbrd);
1914 InitClipboard(&agEditableKillring);
1915 }
1916 void
AG_EditableDestroyClipboards(void)1917 AG_EditableDestroyClipboards(void)
1918 {
1919 FreeClipboard(&agEditableClipbrd);
1920 FreeClipboard(&agEditableKillring);
1921 }
1922
1923 AG_WidgetClass agEditableClass = {
1924 {
1925 "Agar(Widget:Editable)",
1926 sizeof(AG_Editable),
1927 { 0,0 },
1928 Init,
1929 NULL, /* free */
1930 Destroy,
1931 NULL, /* load */
1932 NULL, /* save */
1933 NULL /* edit */
1934 },
1935 Draw,
1936 SizeRequest,
1937 SizeAllocate
1938 };
1939