update uwproj
[mizunara.git] / ui / gtk / text.c
1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright 2017 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 #include <string.h>
32
33 #include "text.h"
34 #include "container.h"
35
36
37 static void selection_handler(
38         GtkTextBuffer *buf,
39         GtkTextIter *location,
40         GtkTextMark *mark,
41         UiTextArea *textview)
42 {
43     const char *mname = gtk_text_mark_get_name(mark);
44     if(mname) {
45         GtkTextIter begin;
46         GtkTextIter end;
47         int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
48         if(sel != textview->last_selection_state) {
49             if(sel) {
50                 ui_set_group(textview->ctx, UI_GROUP_SELECTION);
51             } else {
52                 ui_unset_group(textview->ctx, UI_GROUP_SELECTION);
53             }
54         }
55         textview->last_selection_state = sel;
56     }
57 }
58
59 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
60     GtkWidget *text_area = gtk_text_view_new();
61     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
62     g_signal_connect(
63             text_area,
64             "realize",
65             G_CALLBACK(ui_textarea_realize_event),
66             NULL);
67     
68     UiTextArea *uitext = malloc(sizeof(UiTextArea));
69     uitext->ctx = obj->ctx;
70     uitext->var = var;
71     uitext->last_selection_state = 0;
72     
73     g_signal_connect(
74                 text_area,
75                 "destroy",
76                 G_CALLBACK(ui_textarea_destroy),
77                 uitext);
78     
79     GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
80     gtk_scrolled_window_set_policy(
81             GTK_SCROLLED_WINDOW(scroll_area),
82             GTK_POLICY_AUTOMATIC,
83             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
84     gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
85     
86     // font and padding
87     PangoFontDescription *font;
88     font = pango_font_description_from_string("Monospace");
89     gtk_widget_modify_font(text_area, font);
90     pango_font_description_free(font);
91     
92     gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
93     gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
94     
95     // add
96     UiContainer *ct = uic_get_current_container(obj);
97     ct->add(ct, scroll_area, TRUE);
98     
99     // bind value
100     UiText *value = var->value;
101     if(value) {
102         GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
103         
104         if(value->value.ptr) {
105             gtk_text_buffer_set_text(buf, value->value.ptr, -1);
106             value->value.free(value->value.ptr);
107         }
108         
109         value->get = ui_textarea_get;
110         value->set = ui_textarea_set;
111         value->getsubstr = ui_textarea_getsubstr;
112         value->insert = ui_textarea_insert;
113         value->setposition = ui_textarea_setposition;
114         value->position = ui_textarea_position;
115         value->selection = ui_textarea_selection;
116         value->length = ui_textarea_length;
117         value->remove = ui_textarea_remove;
118         value->value.ptr = NULL;
119         value->value.free = NULL;
120         value->obj = buf;
121         if(!value->undomgr) {
122             value->undomgr = ui_create_undomgr();
123         }
124         
125         g_signal_connect(
126                 buf,
127                 "changed",
128                 G_CALLBACK(ui_textbuf_changed),
129                 uitext);
130         
131         // register undo manager
132         g_signal_connect(
133                 buf,
134                 "insert-text",
135                 G_CALLBACK(ui_textbuf_insert),
136                 var);
137         g_signal_connect(
138                 buf,
139                 "delete-range",
140                 G_CALLBACK(ui_textbuf_delete),
141                 var); 
142         g_signal_connect(
143                 buf,
144                 "mark-set",
145                 G_CALLBACK(selection_handler),
146                 uitext);
147     }
148     
149     return scroll_area;
150 }
151
152 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
153     ui_destroy_boundvar(textarea->ctx, textarea->var);
154     free(textarea);
155 }
156
157 UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
158     UiVar *var = malloc(sizeof(UiVar));
159     var->value = value;
160     var->type = UI_VAR_SPECIAL;
161     var->from = NULL;
162     var->from_ctx = NULL;
163     return ui_textarea_var(obj, var);
164 }
165
166 UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
167     UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
168     if(var) {
169         return ui_textarea_var(obj, var);
170     } else {
171         // TODO: error
172     }
173     return NULL;
174 }
175
176 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
177     return gtk_bin_get_child(GTK_BIN(textarea));
178 }
179
180 char* ui_textarea_get(UiText *text) {
181     if(text->value.ptr) {
182         text->value.free(text->value.ptr);
183     }
184     GtkTextBuffer *buf = text->obj;
185     GtkTextIter start;
186     GtkTextIter end;
187     gtk_text_buffer_get_bounds(buf, &start, &end);
188     char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
189     text->value.ptr = g_strdup(str);
190     text->value.free = (ui_freefunc)g_free;
191     return str;
192 }
193
194 void ui_textarea_set(UiText *text, char *str) {
195     gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
196     if(text->value.ptr) {
197         text->value.free(text->value.ptr);
198     }
199     text->value.ptr = NULL;
200     text->value.free = NULL;
201 }
202
203 char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
204     if(text->value.ptr) {
205         text->value.free(text->value.ptr);
206     }
207     GtkTextBuffer *buf = text->obj;
208     GtkTextIter ib;
209     GtkTextIter ie;
210     gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
211     gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
212     char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE);
213     text->value.ptr = g_strdup(str);
214     text->value.free = (ui_freefunc)g_free;
215     return str;
216 }
217
218 void ui_textarea_insert(UiText *text, int pos, char *str) {
219     GtkTextIter offset;
220     gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
221     gtk_text_buffer_insert(text->obj, &offset, str, -1);
222     if(text->value.ptr) {
223         text->value.free(text->value.ptr);
224     }
225     text->value.ptr = NULL;
226     text->value.free = NULL;
227 }
228
229 void ui_textarea_setposition(UiText *text, int pos) {
230     GtkTextIter iter;
231     gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
232     gtk_text_buffer_place_cursor(text->obj, &iter);
233 }
234
235 int ui_textarea_position(UiText *text) {
236     GtkTextIter begin;
237     GtkTextIter end;
238     gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
239     text->pos = gtk_text_iter_get_offset(&begin);
240     return text->pos;
241 }
242
243 void ui_textarea_selection(UiText *text, int *begin, int *end) {
244     GtkTextIter b;
245     GtkTextIter e;
246     gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
247     *begin = gtk_text_iter_get_offset(&b);
248     *end = gtk_text_iter_get_offset(&e);
249 }
250
251 int ui_textarea_length(UiText *text) {
252     GtkTextBuffer *buf = text->obj;
253     GtkTextIter start;
254     GtkTextIter end;
255     gtk_text_buffer_get_bounds(buf, &start, &end);
256     return gtk_text_iter_get_offset(&end);
257 }
258
259 void ui_textarea_remove(UiText *text, int begin, int end) {
260     GtkTextBuffer *buf = text->obj;
261     GtkTextIter ib;
262     GtkTextIter ie;
263     gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
264     gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
265     gtk_text_buffer_delete(buf, &ib, &ie);
266 }
267
268 void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
269     gtk_widget_grab_focus(widget);
270 }
271
272
273
274 void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
275     UiText *value = textarea->var->value;
276     if(value->observers) {
277         UiEvent e;
278         e.obj = textarea->ctx->obj;
279         e.window = e.obj->window;
280         e.document = textarea->ctx->document;
281         e.eventdata = value;
282         e.intval = 0;
283         ui_notify_evt(value->observers, &e);
284     }
285 }
286
287 // undo manager functions
288
289 void ui_textbuf_insert(
290         GtkTextBuffer *textbuffer,
291         GtkTextIter *location,
292         char *text,
293         int length,
294         void *data)
295 {
296     UiVar *var = data;
297     UiText *value = var->value;
298     if(!value->undomgr) {
299         value->undomgr = ui_create_undomgr();
300     }
301     UiUndoMgr *mgr = value->undomgr;
302     if(!mgr->event) {
303         return;
304     }
305     
306     if(mgr->cur) {
307         UcxList *elm = mgr->cur->next;
308         if(elm) {
309             mgr->cur->next = NULL;
310             while(elm) {
311                 elm->prev = NULL;   
312                 UcxList *next = elm->next;
313                 ui_free_textbuf_op(elm->data);
314                 free(elm);
315                 elm = next;
316             }
317         }
318         
319         UiTextBufOp *last_op = mgr->cur->data;
320         if(
321             last_op->type == UI_TEXTBUF_INSERT &&
322             ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
323         {
324             // append text to last op       
325             int ln = last_op->len;
326             char *newtext = malloc(ln + length + 1);
327             memcpy(newtext, last_op->text, ln);
328             memcpy(newtext+ln, text, length);
329             newtext[ln+length] = '\0';
330             
331             last_op->text = newtext;
332             last_op->len = ln + length;
333             last_op->end += length;
334             
335             return;
336         }
337     }
338     
339     char *dpstr = malloc(length + 1);
340     memcpy(dpstr, text, length);
341     dpstr[length] = 0;
342     
343     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
344     op->type = UI_TEXTBUF_INSERT;
345     op->start = gtk_text_iter_get_offset(location);
346     op->end = op->start+length;
347     op->len = length;
348     op->text = dpstr;
349     
350     UcxList *elm = ucx_list_append(NULL, op);
351     mgr->cur = elm;
352     mgr->begin = ucx_list_concat(mgr->begin, elm);
353 }
354
355 void ui_textbuf_delete(
356         GtkTextBuffer *textbuffer,
357         GtkTextIter *start,
358         GtkTextIter *end,
359         void *data)
360 {
361     UiVar *var = data;
362     UiText *value = var->value;
363     if(!value->undomgr) {
364         value->undomgr = ui_create_undomgr();
365     }
366     UiUndoMgr *mgr = value->undomgr;
367     if(!mgr->event) {
368         return;
369     }
370     
371     if(mgr->cur) {
372         UcxList *elm = mgr->cur->next;
373         if(elm) {
374             mgr->cur->next = NULL;
375             while(elm) {
376                 elm->prev = NULL;   
377                 UcxList *next = elm->next;
378                 ui_free_textbuf_op(elm->data);
379                 free(elm);
380                 elm = next;
381             }
382         }
383     }
384     
385     char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
386     
387     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
388     op->type = UI_TEXTBUF_DELETE;
389     op->start = gtk_text_iter_get_offset(start);
390     op->end = gtk_text_iter_get_offset(end);
391     op->len = op->end - op->start;
392     
393     char *dpstr = malloc(op->len + 1);
394     memcpy(dpstr, text, op->len);
395     dpstr[op->len] = 0;
396     op->text = dpstr;
397     
398     UcxList *elm = ucx_list_append(NULL, op);
399     mgr->cur = elm;
400     mgr->begin = ucx_list_concat(mgr->begin, elm);
401 }
402
403 UiUndoMgr* ui_create_undomgr() {
404     UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
405     mgr->begin = NULL;
406     mgr->cur = NULL;
407     mgr->length = 0;
408     mgr->event = 1;
409     return mgr;
410 }
411
412 void ui_free_textbuf_op(UiTextBufOp *op) {
413     if(op->text) {
414         free(op->text);
415     }
416     free(op);
417 }
418
419 int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
420     // return 1 if oldstr + newstr are one word
421     
422     int has_space = 0;
423     for(int i=0;i<oldlen;i++) {
424         if(oldstr[i] < 33) {
425             has_space = 1;
426             break;
427         }
428     }
429     
430     for(int i=0;i<newlen;i++) {
431         if(has_space && newstr[i] > 32) {
432             return 1;
433         }
434     }
435     
436     return 0;
437 }
438
439 void ui_text_undo(UiText *value) {
440     UiUndoMgr *mgr = value->undomgr;
441     
442     if(mgr->cur) {
443         UiTextBufOp *op = mgr->cur->data;
444         mgr->event = 0;
445         switch(op->type) {
446             case UI_TEXTBUF_INSERT: {
447                 GtkTextIter begin;
448                 GtkTextIter end;
449                 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
450                 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
451                 gtk_text_buffer_delete(value->obj, &begin, &end);
452                 break;
453             }
454             case UI_TEXTBUF_DELETE: {
455                 GtkTextIter begin;
456                 GtkTextIter end;
457                 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
458                 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
459                 gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
460                 break;
461             }
462         }
463         mgr->event = 1;
464         mgr->cur = mgr->cur->prev;
465     }
466 }
467
468 void ui_text_redo(UiText *value) {
469     UiUndoMgr *mgr = value->undomgr;
470     
471     UcxList *elm = NULL;
472     if(mgr->cur) {
473         if(mgr->cur->next) {
474             elm = mgr->cur->next;
475         }
476     } else if(mgr->begin) {
477         elm = mgr->begin;
478     }
479     
480     if(elm) {
481         UiTextBufOp *op = elm->data;
482         mgr->event = 0;
483         switch(op->type) {
484             case UI_TEXTBUF_INSERT: {
485                 GtkTextIter begin;
486                 GtkTextIter end;
487                 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
488                 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
489                 gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
490                 break;
491             }
492             case UI_TEXTBUF_DELETE: {
493                 GtkTextIter begin;
494                 GtkTextIter end;
495                 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
496                 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
497                 gtk_text_buffer_delete(value->obj, &begin, &end);
498                 break;
499             }
500         }
501         mgr->event = 1;
502         mgr->cur = elm;
503     }
504 }
505
506
507 static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) {
508     GtkWidget *textfield = gtk_entry_new();
509     
510     UiTextField *uitext = malloc(sizeof(UiTextField));
511     uitext->ctx = obj->ctx;
512     uitext->var = var;
513     
514     g_signal_connect(
515                 textfield,
516                 "destroy",
517                 G_CALLBACK(ui_textfield_destroy),
518                 uitext);
519     
520     if(width > 0) {
521         gtk_entry_set_width_chars(GTK_ENTRY(textfield), width);
522     }
523     if(frameless) {
524         // TODO: gtk2legacy workaroud
525         gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
526     }
527     if(password) {
528         gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
529     }
530     
531     UiContainer *ct = uic_get_current_container(obj);
532     ct->add(ct, textfield, FALSE);
533     
534     if(var) {
535         UiString *value = var->value;
536         if(value->value.ptr) {
537             gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
538             value->value.free(value->value.ptr);
539             value->value.ptr = NULL;
540             value->value.free = NULL;
541         }
542         
543         value->get = ui_textfield_get;
544         value->set = ui_textfield_set;
545         value->value.ptr = NULL;
546         value->value.free = NULL;
547         value->obj = GTK_ENTRY(textfield);
548         
549         g_signal_connect(
550                 textfield,
551                 "changed",
552                 G_CALLBACK(ui_textfield_changed),
553                 uitext);
554     }
555     
556     return textfield;
557 }
558
559 static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
560     UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
561     if(var) {
562         return create_textfield_var(obj, width, frameless, password, var);
563     } else {
564         // TODO: error
565     }
566     return NULL;
567 }
568
569 static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
570     UiVar *var = NULL;
571     if(value) {
572         var = malloc(sizeof(UiVar));
573         var->value = value;
574         var->type = UI_VAR_SPECIAL;
575         var->from = NULL;
576         var->from_ctx = NULL;
577     }
578     return create_textfield_var(obj, width, frameless, password, var);
579 }
580
581 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
582     if(textfield->var) {
583         ui_destroy_boundvar(textfield->ctx, textfield->var);
584     }
585     free(textfield);
586 }
587
588 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
589     UiString *value = textfield->var->value;
590     if(value->observers) {
591         UiEvent e;
592         e.obj = textfield->ctx->obj;
593         e.window = e.obj->window;
594         e.document = textfield->ctx->document;
595         e.eventdata = value;
596         e.intval = 0;
597         ui_notify_evt(value->observers, &e);
598     }
599 }
600
601 UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
602     return create_textfield(obj, 0, FALSE, FALSE, value);
603 }
604
605 UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
606     return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
607 }
608
609 UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
610     return create_textfield(obj, width, FALSE, FALSE, value);
611 }
612
613 UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
614     return create_textfield_nv(obj, width, FALSE, FALSE, varname);
615 }
616
617 UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
618     return create_textfield(obj, 0, TRUE, FALSE, value);
619 }
620
621 UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
622     return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
623 }
624
625 UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
626     return create_textfield(obj, 0, FALSE, TRUE, value);
627 }
628
629 UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
630     return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
631 }
632
633 UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
634     return create_textfield(obj, width, FALSE, TRUE, value);
635 }
636
637 UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
638     return create_textfield_nv(obj, width, FALSE, TRUE, varname);
639 }
640
641 char* ui_textfield_get(UiString *str) {
642     if(str->value.ptr) {
643         str->value.free(str->value.ptr);
644     }
645     str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
646     str->value.free = (ui_freefunc)g_free;
647     return str->value.ptr;
648 }
649
650 void ui_textfield_set(UiString *str, char *value) {
651     gtk_entry_set_text(str->obj, value);
652     if(str->value.ptr) {
653         str->value.free(str->value.ptr);
654         str->value.ptr = NULL;
655         str->value.free = NULL;
656     }
657 }