add existing code (build system, libs, initial mizucp code)
[mizunara.git] / ui / motif / text.c
1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright 2014 Olaf Wintermann. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *   1. Redistributions of source code must retain the above copyright
10  *      notice, this list of conditions and the following disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above copyright
13  *      notice, this list of conditions and the following disclaimer in the
14  *      documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31
32 #include "text.h"
33 #include "container.h"
34
35
36 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
37     UiContainer *ct = uic_get_current_container(obj);
38     int n = 0;
39     Arg args[16];
40     
41     //XtSetArg(args[n], XmNeditable, TRUE);
42     //n++;
43     XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
44     n++;
45     
46     Widget parent = ct->prepare(ct, args, &n, TRUE);
47     Widget text_area = XmCreateScrolledText(parent, "text_area", args, n);
48     ct->add(ct, XtParent(text_area));
49     XtManageChild(text_area);
50     
51     UiTextArea *uitext = ucx_mempool_malloc(
52             obj->ctx->mempool,
53             sizeof(UiTextArea));
54     uitext->ctx = obj->ctx;
55     uitext->last_selection_state = 0;
56     XtAddCallback(
57                 text_area,
58                 XmNmotionVerifyCallback,
59                 (XtCallbackProc)ui_text_selection_callback,
60                 uitext);
61     
62     // bind value
63     if(var->value) {
64         UiText *value = var->value;
65         if(value->value.ptr) {
66             XmTextSetString(text_area, value->value.ptr);
67             value->value.free(value->value.ptr);
68         }
69         
70         value->set = ui_textarea_set;
71         value->get = ui_textarea_get;
72         value->getsubstr = ui_textarea_getsubstr;
73         value->insert = ui_textarea_insert;
74         value->setposition = ui_textarea_setposition;
75         value->position = ui_textarea_position;
76         value->selection = ui_textarea_selection;
77         value->length = ui_textarea_length;
78         value->value.ptr = NULL;
79         value->obj = text_area;
80         
81         if(!value->undomgr) {
82             value->undomgr = ui_create_undomgr();
83         }
84         
85         XtAddCallback(
86                 text_area,
87                 XmNmodifyVerifyCallback,
88                 (XtCallbackProc)ui_text_modify_callback,
89                 var);
90     }
91     
92     return text_area;
93 }
94
95 UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
96     UiVar *var = malloc(sizeof(UiVar));
97     var->value = value;
98     var->type = UI_VAR_SPECIAL;
99     return ui_textarea_var(obj, var);
100 }
101
102 UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
103     UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
104     if(var) {
105         return ui_textarea_var(obj, var);
106     } else {
107         // TODO: error
108     }
109     return NULL;
110 }
111
112 char* ui_textarea_get(UiText *text) {
113     if(text->value.ptr) {
114         text->value.free(text->value.ptr);
115     }
116     char *str = XmTextGetString(text->obj);
117     text->value.ptr = str;
118     text->value.free = (ui_freefunc)XtFree;
119     return str;
120 }
121
122 void ui_textarea_set(UiText *text, char *str) {
123     XmTextSetString(text->obj, str);
124     if(text->value.ptr) {
125         text->value.free(text->value.ptr);
126     }
127     text->value.ptr = NULL;
128 }
129
130 char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
131     if(text->value.ptr) {
132         text->value.free(text->value.ptr);
133     }
134     int length = end - begin;
135     char *str = XtMalloc(length + 1);
136     XmTextGetSubstring(text->obj, begin, length, length + 1, str);
137     text->value.ptr = str;
138     text->value.free = (ui_freefunc)XtFree;
139     return str;
140 }
141
142 void ui_textarea_insert(UiText *text, int pos, char *str) {
143     text->value.ptr = NULL;
144     XmTextInsert(text->obj, pos, str);
145     if(text->value.ptr) {
146         text->value.free(text->value.ptr);
147     }
148 }
149
150 void ui_textarea_setposition(UiText *text, int pos) {
151     XmTextSetInsertionPosition(text->obj, pos);
152 }
153
154 int ui_textarea_position(UiText *text) {
155     long begin;
156     long end;
157     XmTextGetSelectionPosition(text->obj, &begin, &end);
158     text->pos = begin;
159     return text->pos;
160 }
161
162 void ui_textarea_selection(UiText *text, int *begin, int *end) {
163     XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
164 }
165
166 int ui_textarea_length(UiText *text) {
167     return (int)XmTextGetLastPosition(text->obj);
168 }
169
170
171 void ui_text_set(UiText *text, char *str) {
172     if(text->set) {
173         text->set(text, str);
174     } else {
175         if(text->value.ptr) {
176             text->value.free(text->value.ptr);
177         }
178         text->value.ptr = XtNewString(str);
179         text->value.free = (ui_freefunc)XtFree;
180     }
181 }
182
183 char* ui_text_get(UiText *text) {
184     if(text->get) {
185         return text->get(text);
186     } else {
187         return text->value.ptr;
188     }
189 }
190
191
192 UiUndoMgr* ui_create_undomgr() {
193     UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
194     mgr->begin = NULL;
195     mgr->cur = NULL;
196     mgr->length = 0;
197     mgr->event = 1;
198     return mgr;
199 }
200
201 void ui_text_selection_callback(
202         Widget widget,
203         UiTextArea *textarea,
204         XtPointer data)
205 {
206     long left = 0;
207     long right = 0;
208     XmTextGetSelectionPosition(widget, &left, &right);
209     int sel = left < right ? 1 : 0;
210     if(sel != textarea->last_selection_state) {
211         if(sel) {
212             ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
213         } else {
214             ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
215         }
216     }
217     textarea->last_selection_state = sel;
218 }
219
220 void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
221     UiText *value = var->value;
222     if(!value->obj) {
223         // TODO: bug, fix
224         return;
225     }
226     if(!value->undomgr) {
227         value->undomgr = ui_create_undomgr();
228     }
229     
230     XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
231     int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
232     UiUndoMgr *mgr = value->undomgr;
233     if(!mgr->event) {
234         return;
235     }
236     
237     char *text = txv->text->ptr;
238     int length = txv->text->length;
239     
240     if(mgr->cur) {
241         UcxList *elm = mgr->cur->next;
242         if(elm) {
243             mgr->cur->next = NULL;
244             while(elm) {
245                 elm->prev = NULL;   
246                 UcxList *next = elm->next;
247                 ui_free_textbuf_op(elm->data);
248                 free(elm);
249                 elm = next;
250             }
251         }
252         
253         if(type == UI_TEXTBUF_INSERT) {
254             UiTextBufOp *last_op = mgr->cur->data;
255             if(
256                 last_op->type == UI_TEXTBUF_INSERT &&
257                 ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
258             {
259                 // append text to last op
260                 int ln = last_op->len;
261                 char *newtext = malloc(ln + length + 1);
262                 memcpy(newtext, last_op->text, ln);
263                 memcpy(newtext+ln, text, length);
264                 newtext[ln+length] = '\0';
265                 
266                 last_op->text = newtext;
267                 last_op->len = ln + length;
268                 last_op->end += length;
269
270                 return;
271             }
272         }
273     }
274     
275     char *str;
276     if(type == UI_TEXTBUF_INSERT) {
277         str = malloc(length + 1);
278         memcpy(str, text, length);
279         str[length] = 0;
280     } else {
281         length = txv->endPos - txv->startPos;
282         str = malloc(length + 1);
283         XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
284     }
285     
286     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
287     op->type = type;
288     op->start = txv->startPos;
289     op->end = txv->endPos + 1;
290     op->len = length;
291     op->text = str;
292     
293     UcxList *elm = ucx_list_append(NULL, op);
294     mgr->cur = elm;
295     mgr->begin = ucx_list_concat(mgr->begin, elm);
296 }
297
298 int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
299     // return 1 if oldstr + newstr are one word
300     
301     int has_space = 0;
302     for(int i=0;i<oldlen;i++) {
303         if(oldstr[i] < 33) {
304             has_space = 1;
305             break;
306         }
307     }
308     
309     for(int i=0;i<newlen;i++) {
310         if(has_space && newstr[i] > 32) {
311             return 1;
312         }
313     }
314     
315     return 0;
316 }
317
318 void ui_free_textbuf_op(UiTextBufOp *op) {
319     if(op->text) {
320         free(op->text);
321     }
322     free(op);
323 }
324
325
326 void ui_text_undo(UiText *value) {
327     UiUndoMgr *mgr = value->undomgr;
328     
329     if(mgr->cur) {
330         UiTextBufOp *op = mgr->cur->data;
331         mgr->event = 0;
332         switch(op->type) {
333             case UI_TEXTBUF_INSERT: {
334                 XmTextReplace(value->obj, op->start, op->end, "");
335                 break;
336             }
337             case UI_TEXTBUF_DELETE: {
338                 XmTextInsert(value->obj, op->start, op->text);
339                 break;
340             }
341         }
342         mgr->event = 1;
343         mgr->cur = mgr->cur->prev;
344     }
345 }
346
347 void ui_text_redo(UiText *value) {
348     UiUndoMgr *mgr = value->undomgr;
349     
350     UcxList *elm = NULL;
351     if(mgr->cur) {
352         if(mgr->cur->next) {
353             elm = mgr->cur->next;
354         }
355     } else if(mgr->begin) {
356         elm = mgr->begin;
357     }
358     
359     if(elm) {
360         UiTextBufOp *op = elm->data;
361         mgr->event = 0;
362         switch(op->type) {
363             case UI_TEXTBUF_INSERT: {
364                 XmTextInsert(value->obj, op->start, op->text);
365                 break;
366             }
367             case UI_TEXTBUF_DELETE: {
368                 XmTextReplace(value->obj, op->start, op->end, "");
369                 break;
370             }
371         }
372         mgr->event = 1;
373         mgr->cur = elm;
374     }
375 }
376
377
378 /* ------------------------- textfield ------------------------- */
379
380 static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
381     UiContainer *ct = uic_get_current_container(obj);
382     int n = 0;
383     Arg args[16];
384     XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
385     n++;
386     if(width > 0) {
387         XtSetArg(args[n], XmNcolumns, width / 2 + 1);
388         n++;
389     }
390     if(frameless) {
391         XtSetArg(args[n], XmNshadowThickness, 0);
392         n++;
393     }
394     if(password) {
395         // TODO
396     }
397     
398     Widget parent = ct->prepare(ct, args, &n, FALSE);
399     Widget textfield = XmCreateText(parent, "text_field", args, n);
400     ct->add(ct, textfield);
401     XtManageChild(textfield);
402     
403     // bind value
404     if(value) {
405         if(value->value.ptr) {
406             XmTextSetString(textfield, value->value.ptr);
407             value->value.free(value->value.ptr);
408         }
409         
410         value->set = ui_textfield_set;
411         value->get = ui_textfield_get;
412         value->value.ptr = NULL;
413         value->obj = textfield;
414     }
415     
416     return textfield;
417 }
418
419 static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
420     UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
421     if(var) {
422         UiString *value = var->value;
423         return ui_textfield(obj, value);
424     } else {
425         // TODO: error
426     }
427     return NULL;
428 }
429
430 UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
431     return create_textfield(obj, 0, FALSE, FALSE, value);
432 }
433
434 UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
435     return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
436 }
437
438 UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
439     return create_textfield(obj, width, FALSE, FALSE, value);
440 }
441
442 UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
443     return create_textfield_nv(obj, width, FALSE, FALSE, varname);
444 }
445
446 UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
447     return create_textfield(obj, 0, TRUE, FALSE, value);
448 }
449
450 UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
451     return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
452 }
453
454 UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
455     return create_textfield(obj, 0, FALSE, TRUE, value);
456 }
457
458 UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
459     return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
460 }
461
462 UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
463     return create_textfield(obj, width, FALSE, TRUE, value);
464 }
465
466 UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
467     return create_textfield_nv(obj, width, FALSE, TRUE, varname);
468 }
469
470
471 char* ui_textfield_get(UiString *str) {
472     if(str->value.ptr) {
473         str->value.free(str->value.ptr);
474     }
475     char *value = XmTextGetString(str->obj);
476     str->value.ptr = value;
477     str->value.free = (ui_freefunc)XtFree;
478     return value;
479 }
480
481 void ui_textfield_set(UiString *str, char *value) {
482     XmTextSetString(str->obj, value);
483     if(str->value.ptr) {
484         str->value.free(str->value.ptr);
485     }
486     str->value.ptr = NULL;
487 }
488