1 /* textinput.c - draw an editable text widget
2    Copyright (C) 1996-2017 Paul Sheer
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307, USA.
18  */
19 
20 
21 #include <config.h>
22 #include <stdio.h>
23 #include <my_string.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 
27 #include <X11/Intrinsic.h>
28 #include <X11/Xatom.h>
29 #include "lkeysym.h"
30 
31 #include "stringtools.h"
32 #include "app_glob.c"
33 
34 #include "coolwidget.h"
35 #include "coollocal.h"
36 
37 #include "edit.h"
38 #include "editcmddef.h"
39 #include "mousemark.h"
40 
41 #include "mad.h"
42 
43 
44 int eh_textinput (CWidget * w, XEvent * xevent, CEvent * cwevent);
45 void input_mouse_mark (CWidget * w, XEvent * event, CEvent * ce);
46 
47 extern struct look *look;
48 
49 
50 /* {{{  history stuff: draws a history of inputs a widget of the same ident */
51 
52 
53 #define MAX_HIST_LEN 64
54 #define MAX_HIST_WIDGETS 128
55 
56 struct textinput_history {
57     char ident[32];
58     int text_len;	/* length of a newline separate list of all 'input' strings */
59     int last;
60     char *input[MAX_HIST_LEN];
61 };
62 
63 static struct textinput_history *history_widgets[MAX_HIST_WIDGETS] =
64 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
65  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
66  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
67  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
68  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
69  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
70  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
71  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
72 static int last = 0;
73 
add_to_history(struct textinput_history * h,const char * text,int allow_blank_lines)74 static void add_to_history (struct textinput_history *h, const char *text, int allow_blank_lines)
75 {
76     int i, j;
77     char *t, *p;
78     if (!text)
79 	return;
80     if (!*text && !allow_blank_lines)
81 	return;
82     t = (char *) strdup (text);
83     if ((p = strchr (t, '\n')))
84 	*p = '\0';		/* no newlines allowed in the history */
85     if (h->last)
86 	for (i = h->last - 1; i >= 0; i--)
87 	    if (!strcmp (h->input[i], text)) {	/* avoid adding duplicates */
88 		text = h->input[i];
89 		if (i < h->last - 1)
90 		    for (j = i; j < h->last - 1; j++)	/* shift all entries up one place */
91 			h->input[j] = h->input[j + 1];
92 		h->input[h->last - 1] = (char *) text;	/* move new entry to top */
93 		free (t);
94 		return;
95 	    }
96     h->input[h->last++] = t;
97     if (h->last == MAX_HIST_LEN) {
98 	h->text_len -= strlen (h->input[0]) + 1;
99 	free (h->input[0]);
100 	memmove (h->input, h->input + 1, (MAX_HIST_LEN - 1) * sizeof (char *));		/* shift up to make space */
101 	h->last--;
102     }
103     h->text_len += strlen (text) + 1;	/* maintain total list length */
104 }
105 
add_to_widget_history(const char * ident,const char * text)106 static void add_to_widget_history (const char *ident, const char *text)
107 {
108     int i;
109     int allow_blank_lines = 0;
110     allow_blank_lines = (strchr (ident, '+') != 0);
111     for (i = 0; i < MAX_HIST_WIDGETS; i++) {
112 	if (!history_widgets[i])
113 	    break;
114 	if (!strcmp (history_widgets[i]->ident, ident)) {
115 	    add_to_history (history_widgets[i], text, allow_blank_lines);
116 	    return;
117 	}
118     }
119 /* create a new history */
120     history_widgets[last] = CMalloc (sizeof (struct textinput_history));
121     memset (history_widgets[last], 0, sizeof (struct textinput_history));
122     strcpy (history_widgets[last]->ident, ident);
123     add_to_history (history_widgets[last], text, allow_blank_lines);
124     last++;
125 
126     if (last == MAX_HIST_WIDGETS) {	/* shift up one */
127 	for (i=0;i<history_widgets[0]->last;i++) {
128 	    if (!history_widgets[0]->input[i])
129 		break;
130 	    free (history_widgets[0]->input[i]);
131 	}
132 	free (history_widgets[0]);
133 	memmove (history_widgets, history_widgets + 1, (MAX_HIST_WIDGETS - 1) * sizeof (struct textinput_history *));
134 	last--;
135     }
136 }
137 
CAddToTextInputHistory(const char * ident,const char * text)138 void CAddToTextInputHistory (const char *ident, const char *text)
139 {
140     add_to_widget_history (ident, text);
141 }
142 
143 /*
144    Returns a newline separate list of all 'input' in the history.
145    Result must be free'd .
146  */
get_history_list(const char * ident,int reverse,int * num_lines)147 static char *get_history_list (const char *ident, int reverse, int *num_lines)
148 {
149     char *s, *r;
150     int i, j;
151     for (i = 0; i < MAX_HIST_WIDGETS; i++) {
152 	if (!history_widgets[i])
153 	    break;
154 	if (!strcmp (history_widgets[i]->ident, ident)) {
155 	    r = s = CMalloc (history_widgets[i]->text_len);
156 	    if (!(*num_lines = history_widgets[i]->last))
157 		break;
158 	    if (reverse) {
159 		for (j = 0; j < history_widgets[i]->last; j++) {
160 		    strcpy (s, history_widgets[i]->input[j]);
161 		    s += strlen (s);
162 		    *s++ = '\n';
163 		}
164 	    } else {
165 		for (j = history_widgets[i]->last - 1; j >= 0; j--) {	/* most recent at top */
166 		    strcpy (s, history_widgets[i]->input[j]);
167 		    s += strlen (s);
168 		    *s++ = '\n';
169 		}
170 	    }
171 	    *(--s) = 0;
172 	    return r;
173 	}
174     }
175     *num_lines = 1;
176     return (char *) strdup ("");
177 }
178 
179 /* result must not be free'd */
180 /* returns the last text inputted in the input widget named ident */
CLastInput(const char * ident)181 char *CLastInput (const char *ident)
182 {
183     int i;
184     for (i = 0; i < MAX_HIST_WIDGETS; i++) {
185 	if (!history_widgets[i])
186 	    break;
187 	if (!strcmp (history_widgets[i]->ident, ident)) {
188 	    if (!history_widgets[i]->last)
189 		return "";
190 	    return history_widgets[i]->input[history_widgets[i]->last - 1];
191 	}
192     }
193     return "";
194 }
195 
196 #define HISTORY_LINES 10
197 
clip_lines(int lines,int num_lines)198 static int clip_lines (int lines, int num_lines)
199 {
200     if (lines > num_lines)
201 	lines = num_lines;
202     if (lines > HISTORY_LINES)
203 	lines = HISTORY_LINES;
204     if (lines < 1)
205 	lines = 1;
206     return lines;
207 }
208 
209 /* gets a list of all input histories and all widget for use in saving
210    to an options file (if you want to save the state of an application,
211    for example. result must be free'd */
get_all_lists(void)212 char *get_all_lists (void)
213 {
214     char *s, *r;
215     int i, j;
216     int tot_len;
217 
218     tot_len = 0;
219 
220 /* calc length to alloc */
221     for (i = 0; i < MAX_HIST_WIDGETS; i++) {
222 	if (!history_widgets[i])
223 	    break;
224 	tot_len += strlen (history_widgets[i]->ident) + 1;
225 	tot_len += history_widgets[i]->text_len + history_widgets[i]->last;
226     }
227 
228     r = s = CMalloc (tot_len + 1);
229 
230     for (i = 0; i < MAX_HIST_WIDGETS; i++) {
231 	if (!history_widgets[i])
232 	    break;
233 	strcpy (s, history_widgets[i]->ident);
234 	s += strlen (s);
235 	*s++ = '\n';
236 	for (j = 0; j < history_widgets[i]->last; j++) {
237 	    *s++ = '\t';
238 	    strcpy (s, history_widgets[i]->input[j]);
239 	    s += strlen (s);
240 	    *s++ = '\n';
241 	}
242     }
243     *s = 0;
244     return r;
245 }
246 
free_all_lists(void)247 void free_all_lists (void)
248 {
249     int i, j;
250     for (i = 0; i < MAX_HIST_WIDGETS; i++) {
251 	if (!history_widgets[i])
252 	    break;
253 	for (j = 0; j < history_widgets[i]->last; j++) {
254 	    if (!history_widgets[i]->input[j])
255 		break;
256 	    free (history_widgets[i]->input[j]);
257 	    history_widgets[i]->input[j] = 0;
258 	}
259 	free (history_widgets[i]);
260 	history_widgets[i] = 0;
261     }
262 }
263 
put_all_lists(char * list)264 void put_all_lists (char *list)
265 {
266     char *p;
267     char ident[33];
268     char input[1024];
269 
270     ident[32] = 0;
271     input[1023] = 0;
272 
273     if (!list)
274 	return;
275     while (*list) {
276 	p = strchr (list, '\n');
277 	if (!p)
278 	    return;
279 	*p++ = 0;
280 	strncpy (ident, list, 32);
281 	list = p;
282 	while (*list == '\t') {
283 	    list++;
284 	    p = strchr (list, '\n');
285 	    if (!p)
286 		return;
287 	    *p++ = 0;
288 	    strncpy (input, list, 1023);
289 	    list = p;
290 	    add_to_widget_history (ident, input);
291 	}
292     }
293 }
294 
draw_text_input_history(CWidget * text_input)295 static char *draw_text_input_history (CWidget * text_input)
296 {
297     char *p, *r;
298     int num_lines;
299     CWidget *w;
300     int x, y;
301     int columns, lines;
302 
303     if (text_input->options & TEXTINPUT_PASSWORD)	/* password lines, not allowed a history! */
304 	return 0;
305 
306     x = text_input->x;
307     CPushFont ("editor", 0);
308     columns = (text_input->width - WIDGET_SPACING * 3 - 4 - 6 - 20) / FONT_MEAN_WIDTH;
309     w = CWidgetOfWindow (text_input->parentid);
310 
311     if (!w) {
312     	CPopFont ();
313 	return 0;
314     }
315     if (text_input->y > w->height / 2) {
316 	p = get_history_list (text_input->ident, 1, &num_lines);
317 	lines = (text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
318 	lines = clip_lines (lines, num_lines);
319 	y = text_input->y - lines * FONT_PIX_PER_LINE - WIDGET_SPACING * 2 - 4 - 6;
320 	r = CTrivialSelectionDialog (w->winid, x, y, columns, lines, p, max (0, num_lines - lines), num_lines - 1);
321     } else {
322 	p = get_history_list (text_input->ident, 0, &num_lines);
323 	lines = (w->height - text_input->height - text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
324 	lines = clip_lines (lines, num_lines);
325 	y = text_input->y + text_input->height;
326 	r = CTrivialSelectionDialog (w->winid, x, y, columns, lines, p, 0, 0);
327     }
328     free (p);
329     CPopFont ();
330     return r;
331 }
332 
draw_selection_history(CWidget * text_input)333 static char *draw_selection_history (CWidget * text_input)
334 {
335     CWidget *w;
336     char *p, *r;
337     int x, y;
338     long len;
339     int columns, lines;
340     x = text_input->x;
341     CPushFont ("editor", 0);
342     columns = (text_input->width - WIDGET_SPACING * 3 - 4 - 6 - 20) / FONT_MEAN_WIDTH;
343     w = CWidgetOfWindow (text_input->parentid);
344     if (!w) {
345 	CPopFont ();
346 	return 0;
347     }
348     if (text_input->y > w->height / 2) {
349 	lines = (text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
350 	y = text_input->y - lines * FONT_PIX_PER_LINE - WIDGET_SPACING * 2 - 4 - 6;
351     } else {
352 	lines =
353 	    (w->height - text_input->height - text_input->y - 2 - WIDGET_SPACING * 2 - 4 -
354 	     6) / FONT_PIX_PER_LINE;
355 	y = text_input->y + text_input->height;
356     }
357     p = edit_get_text_from_selection_history (w->winid, x, y, columns, lines, &len);
358     if (p) {
359 	r = (char *) CMalloc (len + 1);
360 	strncpy (r, p, len + 1);
361     } else {
362 	r = NULL;
363     }
364     CPopFont ();
365     return r;
366 }
367 
draw_selection_completion(CWidget * text_input)368 static char *draw_selection_completion (CWidget * text_input)
369 {
370     CWidget *w;
371     char *r;
372     int x, y;
373     int columns, lines;
374     x = text_input->x;
375     CPushFont ("editor", 0);
376     columns = (text_input->width - WIDGET_SPACING * 3 - 4 - 6 - 20) / FONT_MEAN_WIDTH;
377     w = CWidgetOfWindow (text_input->parentid);
378     if (!w) {
379 	CPopFont ();
380 	return 0;
381     }
382     if (text_input->y > w->height / 2) {
383 	lines = (text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
384 	y = text_input->y - lines * FONT_PIX_PER_LINE - WIDGET_SPACING * 2 - 4 - 6;
385     } else {
386 	lines =
387 	    (w->height - text_input->height - text_input->y - 2 - WIDGET_SPACING * 2 - 4 -
388 	     6) / FONT_PIX_PER_LINE;
389 	y = text_input->y + text_input->height;
390     }
391     r = user_file_list_complete (w->winid, x, y, columns, lines, text_input->text);
392     CPopFont ();
393     return r;
394 }
395 
396 #if 0
397 wchar_t *mbstowcs_dup (unsigned char *s);
398 int wchar_t_strlen (wchar_t * s);
399 
400 int wchar_t_columns (char *t, int x)
401 {
402     wchar_t *s;
403     t = (char *) strdup ((char *) t);
404     t[x] = '\0';
405     s = mbstowcs_dup ((unsigned char *) t);
406     x = wchar_t_strlen (s);
407     free (t);
408     free (s);
409     return x;
410 }
411 #endif
412 
render_passwordinput(CWidget * wdt)413 void render_passwordinput (CWidget * wdt)
414 {
415     int wc, k, l, w = wdt->width, h = wdt->height;
416     Window win;
417     char *password;
418 
419     CPushFont ("editor", 0);
420 
421     win = wdt->winid;
422 
423     CSetBackgroundColor (COLOR_WHITE);
424     CSetColor (COLOR_BLACK);
425     password = (char *) strdup (wdt->text);
426     memset (password, '*', strlen (wdt->text));
427     CImageString (win, FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF,
428 		      FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
429 		      password);
430     CSetColor (COLOR_WHITE);
431     l = CImageStringWidth (password);
432     k = min (l, w - 6);
433     memset (password, 0, strlen (password));
434     free (password);
435     CRectangle (win, 3, 3, k, option_text_line_spacing + 1);
436     CLine (win, 3, 4, 3, h - 5);
437     CLine (win, 3, h - 4, k + 3, h - 4);
438     CRectangle (win, k + 3, 3, w - 6 - k, h - 6);
439     (*look->render_passwordinput_tidbits) (wdt, win == CGetFocus ());
440     wc = 3 + TEXTINPUT_RELIEF + 1 + CImageTextWidth (password, wdt->cursor);
441     set_cursor_position (win, wc, 5, 0, h - 5, CURSOR_TYPE_TEXTINPUT, 0, 0, 0, 0);
442     CPopFont ();
443     return;
444 }
445 
render_textinput(CWidget * wdt)446 void render_textinput (CWidget * wdt)
447 {
448     int wc, isfocussed = 0;
449     int f, k, l;
450     int x, m1, m2;
451     int w = wdt->width, h = wdt->height;
452     Window win;
453     char *s;
454 
455     if (wdt->options & TEXTINPUT_PASSWORD) {
456 	render_passwordinput (wdt);
457 	return;
458     }
459 
460     CPushFont ("editor", 0);
461     win = wdt->winid;
462     isfocussed = (win == CGetFocus ());
463 
464 /*This is a little untidy, but it will account for uneven font widths
465    without having to think to hard */
466 
467     do {
468 	f = 0;
469 /*wc is the position of the cursor from the left of the input window */
470 	wc = 3 + TEXTINPUT_RELIEF + 1 +
471 	    CImageTextWidth (wdt->text + wdt->firstcolumn, wdt->cursor - wdt->firstcolumn);
472 
473 	/*now lets make sure the cursor is well within the view */
474 
475 /*except for when the cursor is at the end of the line */
476 	if (wdt->cursor == strlen (wdt->text)) {
477 	    if (wc > w - 3 - h) {
478 		wdt->firstcolumn++;
479 		f = 1;
480 	    }
481 	} else if (wc > max (w - FONT_MEAN_WIDTH - h, w * 3 / 4 - h)) {
482 	    wdt->firstcolumn++;
483 	    f = 1;
484 	}
485 	if (wc < min (FONT_MEAN_WIDTH, w / 4)) {
486 	    wdt->firstcolumn--;
487 	    f = 1;
488 	    /*Unless of course we are at the beginning of the string */
489 	    if (wdt->firstcolumn <= 0) {
490 		wdt->firstcolumn = 0;
491 		f = 0;
492 	    }
493 	}
494     } while (f);		/*recalculate if firstcolumn has changed */
495 
496     s = wdt->text + wdt->firstcolumn;
497     l = strlen (s);
498     CSetColor (COLOR_WHITE);
499     k = min (CImageTextWidth (s, l), w - h - 6);
500     CRectangle (win, 3, 3, k, option_text_line_spacing + 1);
501     CLine (win, 3, 4, 3, h - 5);
502     CLine (win, 3, h - 4, k + 3, h - 4);
503     CRectangle (win, k + 3, 3, w - h - 6 - k, h - 6);
504 /* now draw the visible part of the string */
505     wdt->mark1 = min (wdt->mark1, l + wdt->firstcolumn);
506     wdt->mark2 = min (wdt->mark2, l + wdt->firstcolumn);
507     m1 = min (wdt->mark1, wdt->mark2);
508     m2 = max (wdt->mark1, wdt->mark2);
509     x = 0;
510     if (m1 > wdt->firstcolumn) {
511 	CSetBackgroundColor (COLOR_WHITE);
512 	CSetColor (COLOR_BLACK);
513 	CImageText (win,
514 			  FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF,
515 			  FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
516 			  s, m1 - wdt->firstcolumn);
517 	x += CImageTextWidth (s, m1 - wdt->firstcolumn);
518 	s += m1 - wdt->firstcolumn;
519     }
520     if (x < w - h && m2 > wdt->firstcolumn) {
521 	m1 = max (wdt->firstcolumn, m1);
522 	CSetBackgroundColor (COLOR_BLACK);
523 	CSetColor (COLOR_WHITE);
524 	CImageText (win,
525 			  FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF + x,
526 			  FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
527 			  s, m2 - m1);
528 	x += CImageTextWidth (s, m2 - m1);
529 	s += m2 - m1;
530     }
531     if (x < w - h) {
532 	CSetBackgroundColor (COLOR_WHITE);
533 	CSetColor (COLOR_BLACK);
534 	CImageString (win,
535 			  FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF + x,
536 			  FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
537 			  s);
538     }
539 
540     (*look->render_textinput_tidbits) (wdt, isfocussed);
541 
542     set_cursor_position (win, wc, 5, 0, h - 5, CURSOR_TYPE_TEXTINPUT, 0, 0, 0, 0);
543     CPopFont();
544     return;
545 }
546 
547 
text_input_destroy(CWidget * w)548 void text_input_destroy (CWidget * w)
549 {
550     CAddToTextInputHistory (w->ident, w->text);
551 }
552 
xy(int x,int y,int * x_return,int * y_return)553 static void xy (int x, int y, int *x_return, int *y_return)
554 {
555     *x_return = x - (3 + TEXTINPUT_RELIEF + 1);
556     *y_return = 0;
557 }
558 
cp(CWidget * wdt,int x,int y)559 static long cp (CWidget * wdt, int x, int y)
560 {
561     int i;
562     i = wdt->firstcolumn;
563     for (;;) {
564 	int a;
565 	a = CImageTextWidth (wdt->text + wdt->firstcolumn, i - wdt->firstcolumn);
566 	if (a > x) {
567 	    if (i > 0)
568 		return i - 1;
569 	    return 0;
570 	}
571 	if (!wdt->text[i])
572 	    break;
573 	i++;
574     }
575     return strlen (wdt->text);
576 }
577 
578 /* return 1 if not marked */
marks(CWidget * w,long * start,long * end)579 static int marks (CWidget * w, long *start, long *end)
580 {
581     if (w->mark1 == w->mark2)
582 	return 1;
583     *start = min (w->mark1, w->mark2);
584     *end = max (w->mark1, w->mark2);
585     return 0;
586 }
587 
588 extern int range (CWidget * w, long start, long end, int click);
589 
move_mark(CWidget * w)590 static void move_mark (CWidget * w)
591 {
592     w->mark2 = w->mark1 = w->cursor;
593 }
594 
fin_mark(CWidget * w)595 static void fin_mark (CWidget * w)
596 {
597     w->mark2 = w->mark1 = -1;
598 }
599 
release_mark(CWidget * w,XEvent * event)600 static void release_mark (CWidget * w, XEvent * event)
601 {
602     w->mark2 = w->cursor;
603     if (w->mark2 != w->mark1 && event) {
604 	XSetSelectionOwner (CDisplay, XA_PRIMARY, w->winid, event->xbutton.time);
605 	XSetSelectionOwner (CDisplay, ATOM_ICCCM_P2P_CLIPBOARD, w->winid, event->xbutton.time);
606     }
607 }
608 
get_block(CWidget * w,long start_mark,long end_mark,int * type,int * l)609 static char *get_block (CWidget * w, long start_mark, long end_mark, int *type, int *l)
610 {
611     char *t;
612     if (w->options & TEXTINPUT_PASSWORD) {
613 	*type = DndText;
614 	*l = 0;
615 	return (char *) strdup ("");
616     }
617     *l = abs (w->mark2 - w->mark1);
618     t = CMalloc (*l + 1);
619     memcpy (t, w->text + min (w->mark1, w->mark2), *l);
620     t[*l] = 0;
621     if (*type == DndFile || *type == DndFiles) {
622 	char *s;
623 	int i;
624 	s = CDndFileList (t, l, &i);
625 	free (t);
626 	t = s;
627     }
628     return t;
629 }
630 
move(CWidget * w,long click,int row)631 static void move (CWidget * w, long click, int row)
632 {
633     w->cursor = click;
634     if (w->mark2 == -1)
635 	w->mark1 = click;
636     w->mark2 = click;
637 }
638 
motion(CWidget * w,long click)639 static void motion (CWidget * w, long click)
640 {
641     w->mark2 = click;
642 }
643 
644 void input_insert (CWidget * w, int c);
645 char *filename_from_url (char *data, int size, int i);
646 
insert_drop(CWidget * w,Window from,unsigned char * data,int size,int xs,int ys,Atom type,Atom action)647 static int insert_drop (CWidget * w, Window from, unsigned char *data, int size, int xs, int ys, Atom type, Atom action)
648 {
649     int cursor;
650     char *f;
651     int x, y, i;
652     if (xs < 0 || ys < 0 || xs >= w->width || ys >= w->height)
653 	return 1;
654     xy (xs, ys, &x, &y);
655     f = filename_from_url ((char *) data, size, 0);
656     data = (unsigned char *) f;
657     cursor = w->cursor = cp (w, x, y);
658     if (type == XInternAtom (CDisplay, "url/url", False) || \
659 	type == XInternAtom (CDisplay, "text/uri-list", False))
660 	if (!strncmp ((char *) data, "file:/", 6))
661 	    data += 5;
662     for (i = 0; i < size && data[i] != '\n' && data[i]; i++)
663 	input_insert (w, data[i] < ' ' ? ' ' : data[i]);
664     if (cursor > strlen (w->text))
665 	cursor = strlen (w->text);
666     w->cursor = cursor;
667     free (f);
668     return 0;
669 }
670 
671 static char *mime_majors[3] =
672 {"text", 0};
673 
674 static struct mouse_funcs input_mouse_funcs =
675 {
676     0,
677     (void (*)(int, int, int *, int *)) xy,
678     (long (*)(void *, int, int)) cp,
679     (int (*)(void *, long *, long *)) marks,
680     (int (*)(void *, long, long, long)) range,
681     (void (*)(void *)) fin_mark,
682     (void (*)(void *)) move_mark,
683     (void (*)(void *, XEvent *)) release_mark,
684     (char *(*)(void *, long, long, int *, int *)) get_block,
685     (void (*)(void *, long, int)) move,
686     (void (*)(void *, long)) motion,
687     0,
688     0,
689     (int (*)(void *, Window, unsigned char *, int, int, int, Atom, Atom)) insert_drop,
690     0,
691     DndText,
692     mime_majors
693 };
694 
695 /*
696    This will reallocate a previous draw of the same identifier.
697    so you can draw the same widget over and over without flicker
698  */
CDrawTextInput(const char * identifier,Window parent,int x,int y,int width,int height,int maxlen,const char * text)699 CWidget *CDrawTextInput (const char *identifier, Window parent, int x, int y,
700 		     int width, int height, int maxlen, const char *text)
701 {
702     CWidget *wdt;
703 
704     if (text == TEXTINPUT_LAST_INPUT)
705 	text = CLastInput (identifier);
706 
707     CPushFont ("editor", 0);
708     if (!(wdt = CIdent (identifier))) {
709 	int w, h;
710 	if (width == AUTO_WIDTH || height == AUTO_HEIGHT)
711 	    CTextSize (&w, &h, text);
712 	if (width == AUTO_WIDTH)
713 	    width = w + 6 + TEXTINPUT_RELIEF * 2;
714 	if (height == AUTO_HEIGHT)
715 	    height = FONT_PIX_PER_LINE + 6 + TEXTINPUT_RELIEF * 2;
716 
717 	set_hint_pos (x + width + WIDGET_SPACING, y + height + WIDGET_SPACING);
718 
719 	wdt = CSetupWidget (identifier, parent, x, y,
720 	 width, height, C_TEXTINPUT_WIDGET, INPUT_KEY, COLOR_FLAT, 1);
721 
722 /* For the text input widget we need enough memory allocated to the label
723    for it to grow to maxlen, so reallocate it */
724 
725 	wdt->text = CMalloc (maxlen + 16);
726 	strcpy (wdt->text, text);
727 	wdt->cursor = strlen (text);
728 	wdt->firstcolumn = 0;
729 	wdt->textlength = maxlen;
730 	wdt->destroy = text_input_destroy;
731 	wdt->options |= WIDGET_TAKES_SELECTION;
732 	wdt->funcs = mouse_funcs_new (wdt, &input_mouse_funcs);
733 
734 	xdnd_set_dnd_aware (CDndClass, wdt->winid, 0);
735 	xdnd_set_type_list (CDndClass, wdt->winid, xdnd_typelist_send[DndText]);
736     } else {			/*redraw the thing so it doesn't flicker if its redrawn in the same place.
737 				   Also, this doesn't need an undraw */
738 	CSetWidgetSize (identifier, width, height);
739 	wdt->x = x;
740 	wdt->y = y;
741 	XMoveWindow (CDisplay, wdt->winid, x, y);
742 	free (wdt->text);
743 	wdt->text = CMalloc (maxlen + 16);
744 	strcpy (wdt->text, text);
745 	wdt->cursor = strlen (text);
746 	wdt->firstcolumn = 0;
747 	wdt->textlength = maxlen;
748 	wdt->keypressed = 0;
749 	render_textinput (wdt);
750     }
751     CPopFont();
752 
753     return wdt;
754 }
755 
756 void paste_prop (void *data, void (*insert) (void *, int), Window win, unsigned prop, int delete);
757 
input_insert(CWidget * w,int c)758 void input_insert (CWidget * w, int c)
759 {
760     if (strlen ((char *) w->text) < w->textlength) {
761 	if (!w->keypressed) {
762 	    w->keypressed = 1;
763 	    w->cursor = 0;
764 	    w->text[0] = '\0';
765 	}
766 	memmove ((char *) w->text + w->cursor + 1, w->text + w->cursor, strlen ((char *) w->text) - w->cursor + 1);
767 	w->text[w->cursor] = c;
768 	w->cursor++;
769     }
770 }
771 
772 static void xy (int x, int y, int *x_return, int *y_return);
773 static long cp (CWidget * wdt, int x, int y);
774 void text_get_selection (CWidget * w);
775 void selection_send (XSelectionRequestEvent * rq);
776 
eh_textinput(CWidget * w,XEvent * xevent,CEvent * cwevent)777 int eh_textinput (CWidget * w, XEvent * xevent, CEvent * cwevent)
778 {
779     int handled = 0, save_options;
780     int cursor;
781     char *u;
782 
783     switch (xevent->type) {
784     case FocusIn:
785     case FocusOut:
786 	render_textinput (w);
787 	break;
788     case SelectionRequest:
789 	text_get_selection (w);
790 	selection_send (&(xevent->xselectionrequest));
791 	render_textinput (w);
792 	return 1;
793     case SelectionNotify:
794 	cursor = w->keypressed ? w->cursor : 0;
795 	paste_prop ((void *) w, (void (*)(void *, int)) input_insert,
796 	xevent->xselection.requestor, xevent->xselection.property, True);
797 	w->mark1 = w->mark2 = 0;
798 	w->cursor = cursor;
799 	render_textinput (w);
800 	break;
801     case ButtonPress:
802 	resolve_button (xevent, cwevent);
803 	if (!(w->options & TEXTINPUT_PASSWORD)) {
804 	    if (xevent->xbutton.x >= w->width - w->height) {
805 		char *p;
806 		w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
807 		w->options |= BUTTON_PRESSED;
808 		p = draw_text_input_history (w);
809 		if (p) {
810 		    strncpy (w->text, p, w->textlength);
811 		    w->keypressed = 1;
812 		    w->cursor = strlen (w->text);
813 		    w->firstcolumn = 0;
814 		}
815 	    } else {
816 		input_mouse_mark (w, xevent, cwevent);
817 		w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
818 	    }
819 	}
820 	render_textinput (w);
821 	CFocus (w);
822     case ButtonRelease:
823 	if (!(w->options & TEXTINPUT_PASSWORD))
824 	    input_mouse_mark (w, xevent, cwevent);
825 	render_textinput (w);
826 	break;
827     case Expose:
828 	if (xevent->xexpose.count)
829 	    return 0;
830     case EnterNotify:
831 	w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
832 	if (xevent->xbutton.x >= w->width - w->height)
833 	    w->options |= BUTTON_HIGHLIGHT;
834 	render_textinput (w);
835 	break;
836     case MotionNotify:
837 	save_options = w->options;
838 	w->options &= ~(BUTTON_PRESSED | BUTTON_HIGHLIGHT);
839 	if (xevent->xmotion.x >= w->width - w->height) {
840 	    w->options |= BUTTON_HIGHLIGHT;
841 	    if (save_options != w->options)
842 		render_textinput (w);
843 	    return 0;
844 	} else {
845 	    if (!xevent->xmotion.state) {
846 		if (save_options != w->options)
847 		    render_textinput (w);
848 		return 0;
849 	    }
850 	}
851 	if (!(w->options & TEXTINPUT_PASSWORD))
852 	    input_mouse_mark (w, xevent, cwevent);
853 	render_textinput (w);
854 	break;
855     case LeaveNotify:
856 	w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
857 	render_textinput (w);
858 	break;
859     case KeyPress:
860 	cwevent->ident = w->ident;
861 	cwevent->state = xevent->xkey.state;
862 	cursor = w->cursor;
863 	if (cwevent->insert > 0) {
864             if (!((w->options & TEXTINPUT_NUMBERS) && !(cwevent->insert >= '0' && cwevent->insert <= '9')))
865 	        input_insert (w, cwevent->insert);
866 	    handled = 1;
867 	} else {
868 	    unsigned char *intext;
869 	    intext = (unsigned char *) w->text;
870 	    switch (cwevent->command) {
871 	    case CK_Complete:
872 		u = draw_selection_completion (w);
873 		if (u) {
874 		    w->cursor = 0;
875 		    w->text[0] = '\0';
876 		    while (*u)
877 			input_insert (w, *u++);
878 		}
879 		handled = 1;
880 		break;
881 	    case CK_Selection_History:
882 		u = draw_selection_history (w);
883 		if (u)
884 		    while (*u)
885 			input_insert (w, *u++);
886 		handled = 1;
887 		break;
888 	    case CK_Insert_Unicode:
889 		u = (char *) CGetUnichar (CRoot, "Unicode characters");
890 		if (u)
891 		    while (*u)
892 			input_insert (w, *u++);
893 		handled = 1;
894 		break;
895 	    case CK_XPaste:
896 		if (!XGetSelectionOwner (CDisplay, XA_PRIMARY)) {
897 		    cursor = w->cursor;
898 		    paste_prop ((void *) w, (void (*)(void *, int)) input_insert,
899 				CRoot, XA_CUT_BUFFER0, False);
900 		    w->cursor = cursor;
901 		} else {
902 		    XConvertSelection (CDisplay, XA_PRIMARY, XA_STRING,
903 			   XInternAtom (CDisplay, "VT_SELECTION", False),
904 				       w->winid, CurrentTime);
905 		    return 0;
906 		}
907 		handled = 1;
908 		break;
909 	    case CK_BackSpace:
910 		if (w->mark1 != w->mark2) {
911 		    memmove (intext + min (w->mark1, w->mark2), intext + max (w->mark1, w->mark2), strlen ((char *) intext + max (w->mark1, w->mark2)) + 1);
912 		    w->cursor = min (w->mark1, w->mark2);
913 		} else if (w->cursor > 0) {
914 		    memmove ((char *) intext + w->cursor - 1, intext + w->cursor, strlen ((char *) intext) - w->cursor + 1);
915 		    w->cursor--;
916 		}
917 		handled = 1;
918 		break;
919 	    case CK_Left:
920 		if (w->cursor > 0)
921 		    w->cursor--;
922 		handled = 1;
923 		break;
924 	    case CK_Down:
925 	    case CK_Up:
926 	    case CK_Down_Highlight:
927 	    case CK_Up_Highlight:
928 		if (cwevent->state & ShiftMask) {
929 		    char *p;
930 		    w->options |= BUTTON_PRESSED;
931 		    p = draw_text_input_history (w);
932 		    if (p) {
933 			strncpy (w->text, p, w->textlength);
934 			w->keypressed = 1;
935 			w->cursor = strlen (w->text);
936 			w->firstcolumn = 0;
937 		    }
938 		    w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
939 		    handled = 1;
940 		}
941 		break;
942 	    case CK_Right:
943 		if (w->cursor < strlen ((char *) intext))
944 		    w->cursor++;
945 		handled = 1;
946 		break;
947 	    case CK_Delete:
948 		if (w->mark1 != w->mark2) {
949 		    memmove (intext + min (w->mark1, w->mark2), intext + max (w->mark1, w->mark2), strlen ((char *) intext + max (w->mark1, w->mark2)) + 1);
950 		    w->cursor = min (w->mark1, w->mark2);
951 		} else if (w->cursor < strlen ((char *) intext))
952 		    memmove (intext + w->cursor, intext + w->cursor + 1, strlen ((char *) intext) - w->cursor + 1);
953 		handled = 1;
954 		break;
955 	    case CK_Home:
956 		w->cursor = 0;
957 		handled = 1;
958 		break;
959 	    case CK_End:
960 		w->cursor = strlen ((char *) intext);
961 		handled = 1;
962 		break;
963 	    }
964 	    w->keypressed |= handled;
965 	}
966 	if (handled) {
967 	    w->mark1 = w->mark2 = 0;
968 	    render_textinput (w);
969 	}
970 	cwevent->text = w->text;
971     }
972 
973     return handled;
974 }
975 
input_mouse_mark(CWidget * w,XEvent * event,CEvent * ce)976 void input_mouse_mark (CWidget * w, XEvent * event, CEvent * ce)
977 {
978     CPushFont ("editor", 0);
979     mouse_mark (event, ce->double_click, w->funcs);
980     CPopFont ();
981 }
982 
983 
984 
985 
986 
987 
988 
989 
990