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