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