f6bfb27dc8c7dae032fa4c785012e6efa3a938f6
[silc.git] / apps / irssi / src / fe-text / gui-entry.c
1 /*
2  gui-entry.c : irssi
3
4     Copyright (C) 1999 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "misc.h"
23 #include "utf8.h"
24 #include "formats.h"
25
26 #include "gui-entry.h"
27 #include "gui-printtext.h"
28 #include "term.h"
29
30 const unichar empty_str[] = { 0 };
31
32 GUI_ENTRY_REC *active_entry;
33
34 static void entry_text_grow(GUI_ENTRY_REC *entry, int grow_size)
35 {
36         if (entry->text_len+grow_size < entry->text_alloc)
37                 return;
38
39         entry->text_alloc = nearest_power(entry->text_alloc+grow_size);
40         entry->text = g_realloc(entry->text,
41                                 sizeof(unichar) * entry->text_alloc);
42 }
43
44 GUI_ENTRY_REC *gui_entry_create(int xpos, int ypos, int width, int utf8)
45 {
46         GUI_ENTRY_REC *rec;
47
48         rec = g_new0(GUI_ENTRY_REC, 1);
49         rec->xpos = xpos;
50         rec->ypos = ypos;
51         rec->width = width;
52         rec->text_alloc = 1024;
53         rec->text = g_new(unichar, rec->text_alloc);
54         rec->text[0] = '\0';
55         rec->utf8 = utf8;
56         return rec;
57 }
58
59 void gui_entry_destroy(GUI_ENTRY_REC *entry)
60 {
61         g_return_if_fail(entry != NULL);
62
63         if (active_entry == entry)
64                 gui_entry_set_active(NULL);
65
66         g_free(entry->text);
67         g_free(entry->prompt);
68         g_free(entry);
69 }
70
71 /* Fixes the cursor position in screen */
72 static void gui_entry_fix_cursor(GUI_ENTRY_REC *entry)
73 {
74         int old_scrstart;
75
76         old_scrstart = entry->scrstart;
77         if (entry->pos - entry->scrstart < entry->width-2 - entry->promptlen &&
78             entry->pos - entry->scrstart > 0) {
79                 entry->scrpos = entry->pos - entry->scrstart;
80         } else if (entry->pos < entry->width-1 - entry->promptlen) {
81                 entry->scrstart = 0;
82                 entry->scrpos = entry->pos;
83         } else {
84                 entry->scrpos = (entry->width - entry->promptlen)*2/3;
85                 entry->scrstart = entry->pos - entry->scrpos;
86         }
87
88         if (old_scrstart != entry->scrstart)
89                 entry->redraw_needed_from = 0;
90 }
91
92 static void gui_entry_draw_from(GUI_ENTRY_REC *entry, int pos)
93 {
94         const unichar *p;
95         int xpos, end_xpos;
96
97         xpos = entry->xpos + entry->promptlen + pos;
98         end_xpos = entry->xpos + entry->width;
99         if (xpos > end_xpos)
100                 return;
101
102         term_set_color(root_window, ATTR_RESET);
103         term_move(root_window, xpos, entry->ypos);
104
105         p = entry->scrstart + pos < entry->text_len ?
106                 entry->text + entry->scrstart + pos : empty_str;
107         for (; *p != '\0'; p++) {
108                 xpos += utf8_width(*p);
109                 if (xpos > end_xpos)
110                         break;
111
112                 if (entry->hidden)
113                         term_addch(root_window, ' ');
114                 else if (*p >= 32 && (entry->utf8 || (*p & 127) >= 32))
115                         term_add_unichar(root_window, *p);
116                 else {
117                         term_set_color(root_window, ATTR_RESET|ATTR_REVERSE);
118                         term_addch(root_window, *p+'A'-1);
119                         term_set_color(root_window, ATTR_RESET);
120                 }
121         }
122
123         /* clear the rest of the input line */
124         if (end_xpos == term_width)
125                 term_clrtoeol(root_window);
126         else {
127                 while (xpos < end_xpos) {
128                         term_addch(root_window, ' ');
129                         xpos++;
130                 }
131         }
132 }
133
134 static void gui_entry_draw(GUI_ENTRY_REC *entry)
135 {
136         if (entry->redraw_needed_from >= 0) {
137                 gui_entry_draw_from(entry, entry->redraw_needed_from);
138                 entry->redraw_needed_from = -1;
139         }
140
141         term_move_cursor(entry->xpos + entry->scrpos + entry->promptlen,
142                          entry->ypos);
143         term_refresh(NULL);
144 }
145
146 static void gui_entry_redraw_from(GUI_ENTRY_REC *entry, int pos)
147 {
148         pos -= entry->scrstart;
149         if (pos < 0) pos = 0;
150
151         if (entry->redraw_needed_from == -1 ||
152             entry->redraw_needed_from > pos)
153                 entry->redraw_needed_from = pos;
154 }
155
156 void gui_entry_move(GUI_ENTRY_REC *entry, int xpos, int ypos, int width)
157 {
158         int old_width;
159
160         g_return_if_fail(entry != NULL);
161
162         if (entry->xpos != xpos || entry->ypos != ypos) {
163                 /* position in screen changed - needs a full redraw */
164                 entry->xpos = xpos;
165                 entry->ypos = ypos;
166                 entry->width = width;
167                 gui_entry_redraw(entry);
168                 return;
169         }
170
171         if (entry->width == width)
172                 return; /* no changes */
173
174         if (width > entry->width) {
175                 /* input line grew - need to draw text at the end */
176                 old_width = width;
177                 entry->width = width;
178                 gui_entry_redraw_from(entry, old_width);
179         } else {
180                 /* input line shrinked - make sure the cursor
181                    is inside the input line */
182                 entry->width = width;
183                 if (entry->pos - entry->scrstart >
184                     entry->width-2 - entry->promptlen) {
185                         gui_entry_fix_cursor(entry);
186                 }
187         }
188
189         gui_entry_draw(entry);
190 }
191
192 void gui_entry_set_active(GUI_ENTRY_REC *entry)
193 {
194         active_entry = entry;
195
196         if (entry != NULL) {
197                 term_move_cursor(entry->xpos + entry->scrpos +
198                                  entry->promptlen, entry->ypos);
199                 term_refresh(NULL);
200         }
201 }
202
203 void gui_entry_set_prompt(GUI_ENTRY_REC *entry, const char *str)
204 {
205         int oldlen;
206
207         g_return_if_fail(entry != NULL);
208
209         oldlen = entry->promptlen;
210         if (str != NULL) {
211                 g_free_not_null(entry->prompt);
212                 entry->prompt = g_strdup(str);
213                 entry->promptlen = format_get_length(str);
214         }
215
216         if (entry->prompt != NULL)
217                 gui_printtext(entry->xpos, entry->ypos, entry->prompt);
218
219         if (entry->promptlen != oldlen) {
220                 gui_entry_fix_cursor(entry);
221                 gui_entry_draw(entry);
222         }
223 }
224
225 void gui_entry_set_hidden(GUI_ENTRY_REC *entry, int hidden)
226 {
227         g_return_if_fail(entry != NULL);
228
229         entry->hidden = hidden;
230 }
231
232 void gui_entry_set_utf8(GUI_ENTRY_REC *entry, int utf8)
233 {
234         g_return_if_fail(entry != NULL);
235
236         entry->utf8 = utf8;
237 }
238
239 void gui_entry_set_text(GUI_ENTRY_REC *entry, const char *str)
240 {
241         g_return_if_fail(entry != NULL);
242         g_return_if_fail(str != NULL);
243
244         entry->text_len = 0;
245         entry->pos = 0;
246         entry->text[0] = '\0';
247
248         gui_entry_insert_text(entry, str);
249 }
250
251 char *gui_entry_get_text(GUI_ENTRY_REC *entry)
252 {
253         char *buf;
254         int i;
255
256         g_return_val_if_fail(entry != NULL, NULL);
257
258         buf = g_malloc(entry->text_len*6 + 1);
259         if (entry->utf8)
260                 utf16_to_utf8(entry->text, buf);
261         else {
262                 for (i = 0; i <= entry->text_len; i++)
263                         buf[i] = entry->text[i];
264         }
265         return buf;
266 }
267
268 void gui_entry_insert_text(GUI_ENTRY_REC *entry, const char *str)
269 {
270         unichar chr;
271         int i, len;
272
273         g_return_if_fail(entry != NULL);
274         g_return_if_fail(str != NULL);
275
276         gui_entry_redraw_from(entry, entry->pos);
277
278         len = !entry->utf8 ? strlen(str) : strlen_utf8(str);
279         entry_text_grow(entry, len);
280
281         /* make space for the string */
282         g_memmove(entry->text + entry->pos + len, entry->text + entry->pos,
283                   (entry->text_len-entry->pos + 1) * sizeof(unichar));
284
285         if (!entry->utf8) {
286                 for (i = 0; i < len; i++)
287                         entry->text[entry->pos+i] = str[i];
288         } else {
289                 chr = entry->text[entry->pos+len];
290                 utf8_to_utf16(str, entry->text+entry->pos);
291                 entry->text[entry->pos+len] = chr;
292         }
293
294         entry->text_len += len;
295         entry->pos += len;
296
297         gui_entry_fix_cursor(entry);
298         gui_entry_draw(entry);
299 }
300
301 void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr)
302 {
303         g_return_if_fail(entry != NULL);
304
305         if (chr == 0 || chr == 13 || chr == 10)
306                 return; /* never insert NUL, CR or LF characters */
307
308         gui_entry_redraw_from(entry, entry->pos);
309
310         entry_text_grow(entry, 1);
311
312         /* make space for the string */
313         g_memmove(entry->text + entry->pos + 1, entry->text + entry->pos,
314                   (entry->text_len-entry->pos + 1) * sizeof(unichar));
315
316         entry->text[entry->pos] = chr;
317         entry->text_len++;
318         entry->pos++;
319
320         gui_entry_fix_cursor(entry);
321         gui_entry_draw(entry);
322 }
323
324 char *gui_entry_get_cutbuffer(GUI_ENTRY_REC *entry)
325 {
326         char *buf;
327         int i;
328
329         g_return_val_if_fail(entry != NULL, NULL);
330
331         if (entry->cutbuffer == NULL)
332                 return NULL;
333
334         buf = g_malloc(entry->cutbuffer_len*6 + 1);
335         if (entry->utf8)
336                 utf16_to_utf8(entry->cutbuffer, buf);
337         else {
338                 for (i = 0; i <= entry->cutbuffer_len; i++)
339                         buf[i] = entry->cutbuffer[i];
340         }
341         return buf;
342 }
343
344 void gui_entry_erase(GUI_ENTRY_REC *entry, int size, int update_cutbuffer)
345 {
346         g_return_if_fail(entry != NULL);
347
348         if (entry->pos < size)
349                 return;
350
351         if (update_cutbuffer) {
352                 /* put erased text to cutbuffer */
353                 if (entry->cutbuffer == NULL || entry->cutbuffer_len < size) {
354                         g_free(entry->cutbuffer);
355                         entry->cutbuffer = g_new(unichar, size+1);
356                 }
357
358                 entry->cutbuffer_len = size;
359                 entry->cutbuffer[size] = '\0';
360                 memcpy(entry->cutbuffer, entry->text + entry->pos - size,
361                        size * sizeof(unichar));
362         }
363
364         if (size == 0) {
365                 /* we just wanted to clear the cutbuffer */
366                 return;
367         }
368
369         g_memmove(entry->text + entry->pos - size, entry->text + entry->pos,
370                   (entry->text_len-entry->pos+1) * sizeof(unichar));
371
372         entry->pos -= size;
373         entry->text_len -= size;
374
375         gui_entry_redraw_from(entry, entry->pos);
376         gui_entry_fix_cursor(entry);
377         gui_entry_draw(entry);
378 }
379
380 void gui_entry_erase_word(GUI_ENTRY_REC *entry, int to_space)
381 {
382         int to;
383
384         g_return_if_fail(entry != NULL);
385         if (entry->pos == 0)
386                 return;
387
388         to = entry->pos - 1;
389
390         if (to_space) {
391                 while (entry->text[to] == ' ' && to > 0)
392                         to--;
393                 while (entry->text[to] != ' ' && to > 0)
394                         to--;
395         } else {
396                 while (!i_isalnum(entry->text[to]) && to > 0)
397                         to--;
398                 while (i_isalnum(entry->text[to]) && to > 0)
399                         to--;
400         }
401         if (to > 0) to++;
402
403         gui_entry_erase(entry, entry->pos-to, TRUE);
404 }
405
406 void gui_entry_erase_next_word(GUI_ENTRY_REC *entry, int to_space)
407 {
408         int to, size;
409
410         g_return_if_fail(entry != NULL);
411         if (entry->pos == entry->text_len)
412                 return;
413
414         to = entry->pos;
415         if (to_space) {
416                 while (entry->text[to] == ' ' && to < entry->text_len)
417                         to++;
418                 while (entry->text[to] != ' ' && to < entry->text_len)
419                         to++;
420         } else {
421                 while (!i_isalnum(entry->text[to]) && to < entry->text_len)
422                         to++;
423                 while (i_isalnum(entry->text[to]) && to < entry->text_len)
424                         to++;
425         }
426
427         size = to-entry->pos;
428         entry->pos = to;
429         gui_entry_erase(entry, size, TRUE);
430 }
431
432 void gui_entry_transpose_chars(GUI_ENTRY_REC *entry)
433 {
434         unichar chr;
435
436         if (entry->pos == 0 || entry->text_len < 2)
437                 return;
438
439         if (entry->pos == entry->text_len)
440                 entry->pos--;
441
442         /* swap chars */
443         chr = entry->text[entry->pos];
444         entry->text[entry->pos] = entry->text[entry->pos-1];
445         entry->text[entry->pos-1] = chr;
446
447         entry->pos++;
448
449         gui_entry_redraw_from(entry, entry->pos-2);
450         gui_entry_fix_cursor(entry);
451         gui_entry_draw(entry);
452 }
453
454 int gui_entry_get_pos(GUI_ENTRY_REC *entry)
455 {
456         g_return_val_if_fail(entry != NULL, 0);
457
458         return entry->pos;
459 }
460
461 void gui_entry_set_pos(GUI_ENTRY_REC *entry, int pos)
462 {
463         g_return_if_fail(entry != NULL);
464
465         if (pos >= 0 && pos <= entry->text_len)
466                 entry->pos = pos;
467
468         gui_entry_fix_cursor(entry);
469         gui_entry_draw(entry);
470 }
471
472 void gui_entry_move_pos(GUI_ENTRY_REC *entry, int pos)
473 {
474         g_return_if_fail(entry != NULL);
475
476         if (entry->pos+pos >= 0 && entry->pos+pos <= entry->text_len)
477                 entry->pos += pos;
478
479         gui_entry_fix_cursor(entry);
480         gui_entry_draw(entry);
481 }
482
483 static void gui_entry_move_words_left(GUI_ENTRY_REC *entry, int count, int to_space)
484 {
485         int pos;
486
487         pos = entry->pos;
488         while (count > 0 && pos > 0) {
489                 if (to_space) {
490                         while (pos > 0 && entry->text[pos-1] == ' ')
491                                 pos--;
492                         while (pos > 0 && entry->text[pos-1] != ' ')
493                                 pos--;
494                 } else {
495                         while (pos > 0 && !i_isalnum(entry->text[pos-1]))
496                                 pos--;
497                         while (pos > 0 &&  i_isalnum(entry->text[pos-1]))
498                                 pos--;
499                 }
500                 count--;
501         }
502
503         entry->pos = pos;
504 }
505
506 static void gui_entry_move_words_right(GUI_ENTRY_REC *entry, int count, int to_space)
507 {
508         int pos;
509
510         pos = entry->pos;
511         while (count > 0 && pos < entry->text_len) {
512                 if (to_space) {
513                         while (pos < entry->text_len && entry->text[pos] == ' ')
514                                 pos++;
515                         while (pos < entry->text_len && entry->text[pos] != ' ')
516                                 pos++;
517                 } else {
518                         while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
519                                 pos++;
520                         while (pos < entry->text_len &&  i_isalnum(entry->text[pos]))
521                                 pos++;
522                 }
523                 count--;
524         }
525
526         entry->pos = pos;
527 }
528
529 void gui_entry_move_words(GUI_ENTRY_REC *entry, int count, int to_space)
530 {
531         g_return_if_fail(entry != NULL);
532
533         if (count < 0)
534                 gui_entry_move_words_left(entry, -count, to_space);
535         else if (count > 0)
536                 gui_entry_move_words_right(entry, count, to_space);
537
538         gui_entry_fix_cursor(entry);
539         gui_entry_draw(entry);
540 }
541
542 void gui_entry_redraw(GUI_ENTRY_REC *entry)
543 {
544         g_return_if_fail(entry != NULL);
545
546         gui_entry_set_prompt(entry, NULL);
547         gui_entry_redraw_from(entry, 0);
548         gui_entry_fix_cursor(entry);
549         gui_entry_draw(entry);
550 }