Change to the linux kernel coding style
[wmaker-crm.git] / src / text.c
1 /********************************************************************
2  * text.c -- a basic text field                                     *
3  * Copyright (C) 1997 Robin D. Clark                                *
4  *                                                                  *
5  * This program is free software; you can redistribute it and/or    *
6  * modify it under the terms of the GNU General Public License as   *
7  * published by the Free Software Foundation; either version 2 of   *
8  * the License, or (at your option) any later version.              *
9  *                                                                  *
10  * This program is distributed in the hope that it will be useful,  *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
13  * GNU General Public License for more details.                     *
14  *                                                                  *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, write to the Free Software      *
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.        *
18  *                                                                  *
19  *   Author: Rob Clark                                              *
20  * Internet: rclark@cs.hmc.edu                                      *
21  *  Address: 609 8th Street                                         *
22  *           Huntington Beach, CA 92648-4632                        *
23  ********************************************************************/
24
25 #include "wconfig.h"
26
27 #include <X11/Xlib.h>
28 #include <X11/Xutil.h>
29 #include <X11/keysym.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h>
33
34 #include "WindowMaker.h"
35 #include "funcs.h"
36 #include "text.h"
37 #include "actions.h"
38
39 /* X11R5 don't have this */
40 #ifndef IsPrivateKeypadKey
41 #define IsPrivateKeypadKey(keysym) \
42     (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF))
43 #endif
44
45 #ifdef DEBUG
46 #  define  ENTER(X)  fprintf(stderr,"Entering:  %s()\n", X);
47 #  define  LEAVE(X)  fprintf(stderr,"Leaving:   %s()\n", X);
48 #  define  PDEBUG(X)  fprintf(stderr,"debug:     %s()\n", X);
49 #else
50 #  define  ENTER(X)
51 #  define  LEAVE(X)
52 #  define  PDEBUG(X)
53 #endif
54
55 extern Cursor wCursor[WCUR_LAST];
56
57 /********************************************************************
58  * The event handler for the text-field:                            *
59  ********************************************************************/
60 static void textEventHandler(WObjDescriptor * desc, XEvent * event);
61
62 static void handleExpose(WObjDescriptor * desc, XEvent * event);
63
64 static void textInsert(WTextInput * wtext, char *txt);
65 #if 0
66 static void blink(void *data);
67 #endif
68
69 /********************************************************************
70  * handleKeyPress                                                   *
71  *   handle cursor keys, regular keys, etc.  Inserts characters in  *
72  *   text field, at cursor position, if it is a "Normal" key.  If   *
73  *   ksym is the delete key, backspace key, etc., the appropriate   *
74  *   action is performed on the text in the text field.  Does not   *
75  *   refresh the text field                                         *
76  *                                                                  *
77  * Args:   wText - the text field                                   *
78  *         ksym  - the key that was pressed                         *
79  * Return: True, unless the ksym is ignored                         *
80  * Global: modifier - the bitfield that keeps track of the modifier *
81  *                    keys that are down                            *
82  ********************************************************************/
83 static int handleKeyPress(WTextInput * wtext, XKeyEvent * event)
84 {
85         KeySym ksym;
86         char buffer[32];
87         int count;
88
89         count = XLookupString(event, buffer, 32, &ksym, NULL);
90
91         /* Ignore these keys: */
92         if (IsFunctionKey(ksym) || IsKeypadKey(ksym) ||
93             IsMiscFunctionKey(ksym) || IsPFKey(ksym) || IsPrivateKeypadKey(ksym))
94                 /* If we don't handle it, make sure it isn't a key that
95                  * the window manager needs to see */
96                 return False;
97
98         /* Take care of the cursor keys.. ignore up and down
99          * cursor keys */
100         else if (IsCursorKey(ksym)) {
101                 int length = wtext->text.length;
102                 switch (ksym) {
103                 case XK_Home:
104                 case XK_Begin:
105                         wtext->text.endPos = 0;
106                         break;
107                 case XK_Left:
108                         wtext->text.endPos--;
109                         break;
110                 case XK_Right:
111                         wtext->text.endPos++;
112                         break;
113                 case XK_End:
114                         wtext->text.endPos = length;
115                         break;
116                 default:
117                         return False;
118                 }
119                 /* make sure that the startPos and endPos have values
120                  * that make sense  (ie the are in [0..length] ) */
121                 if (wtext->text.endPos < 0)
122                         wtext->text.endPos = 0;
123                 if (wtext->text.endPos > length)
124                         wtext->text.endPos = length;
125                 wtext->text.startPos = wtext->text.endPos;
126         } else {
127                 switch (ksym) {
128                         /* Ignore these keys: */
129                 case XK_Escape:
130                         wtext->canceled = True;
131                 case XK_Return:
132                         wtext->done = True;
133                         break;
134                 case XK_Tab:
135                 case XK_Num_Lock:
136                         break;
137
138                 case XK_Delete:
139                         /* delete after cursor */
140                         if ((wtext->text.endPos == wtext->text.startPos) &&
141                             (wtext->text.endPos < wtext->text.length))
142                                 wtext->text.endPos++;
143                         textInsert(wtext, "");
144                         break;
145                 case XK_BackSpace:
146                         /* delete before cursor */
147                         if ((wtext->text.endPos == wtext->text.startPos) && (wtext->text.startPos > 0))
148                                 wtext->text.startPos--;
149                         textInsert(wtext, "");
150                         break;
151                 default:
152                         if (count == 1 && !iscntrl(buffer[0])) {
153                                 buffer[count] = 0;
154                                 textInsert(wtext, buffer);
155                         }
156                 }
157         }
158         return True;
159 }
160
161 /********************************************************************
162  * textXYtoPos                                                      *
163  *   given X coord, return position in array                        *
164  ********************************************************************/
165 static int textXtoPos(WTextInput * wtext, int x)
166 {
167         int pos;
168         x -= wtext->xOffset;
169
170         for (pos = 0; wtext->text.txt[pos] != '\0'; pos++) {
171                 if (x < 0)
172                         break;
173                 else
174                         x -= WMWidthOfString(wtext->font, &(wtext->text.txt[pos]), 1);
175         }
176
177         return pos;
178 }
179
180 /********************************************************************
181  * wTextCreate                                                      *
182  *   create an instance of a text class                             *
183  *                                                                  *
184  * Args:                                                            *
185  * Return:                                                          *
186  * Global: dpy - the display                                        *
187  ********************************************************************/
188 WTextInput *wTextCreate(WCoreWindow * core, int x, int y, int width, int height)
189 {
190         WTextInput *wtext;
191
192         ENTER("wTextCreate");
193
194         wtext = wmalloc(sizeof(WTextInput));
195         wtext->core = wCoreCreate(core, x, y, width, height);
196         wtext->core->descriptor.handle_anything = &textEventHandler;
197         wtext->core->descriptor.handle_expose = &handleExpose;
198         wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT;
199         wtext->core->descriptor.parent = wtext;
200
201         wtext->font = core->screen_ptr->menu_entry_font;
202
203         XDefineCursor(dpy, wtext->core->window, wCursor[WCUR_TEXT]);
204
205         /* setup the text: */
206         wtext->text.txt = (char *)wmalloc(sizeof(char));
207         wtext->text.txt[0] = '\0';
208         wtext->text.length = 0;
209         wtext->text.startPos = 0;
210         wtext->text.endPos = 0;
211
212         {
213                 XGCValues gcv;
214
215                 gcv.foreground = core->screen_ptr->black_pixel;
216                 gcv.background = core->screen_ptr->white_pixel;
217                 gcv.line_width = 1;
218                 gcv.function = GXcopy;
219
220                 wtext->gc = XCreateGC(dpy, wtext->core->window,
221                                       (GCForeground | GCBackground | GCFunction | GCLineWidth), &gcv);
222
223                 /* set up the regular context */
224                 gcv.foreground = core->screen_ptr->black_pixel;
225                 gcv.background = core->screen_ptr->white_pixel;
226                 gcv.line_width = 1;
227                 gcv.function = GXcopy;
228
229                 wtext->regGC = XCreateGC(dpy, wtext->core->window,
230                                          (GCForeground | GCBackground | GCFunction | GCLineWidth), &gcv);
231
232                 /* set up the inverted context */
233                 gcv.function = GXcopyInverted;
234
235                 wtext->invGC = XCreateGC(dpy, wtext->core->window,
236                                          (GCForeground | GCBackground | GCFunction | GCLineWidth), &gcv);
237
238                 /* and set the background! */
239                 XSetWindowBackground(dpy, wtext->core->window, gcv.background);
240         }
241
242         /* Figure out the y-offset... */
243         wtext->yOffset = (height - WMFontHeight(wtext->font)) / 2;
244         wtext->xOffset = wtext->yOffset;
245
246         wtext->canceled = False;
247         wtext->done = False;    /* becomes True when the user    *
248                                  * hits "Return" key             */
249
250         XMapRaised(dpy, wtext->core->window);
251
252         LEAVE("wTextCreate");
253
254         return wtext;
255 }
256
257 /********************************************************************
258  * wTextDestroy                                                     *
259  *                                                                  *
260  * Args:   wtext  - the text field                                  *
261  * Return:                                                          *
262  * Global: dpy    - the display                                     *
263  ********************************************************************/
264 void wTextDestroy(WTextInput * wtext)
265 {
266         ENTER("wTextDestroy")
267 #if 0
268             if (wtext->magic)
269                 wDeleteTimerHandler(wtext->magic);
270         wtext->magic = NULL;
271 #endif
272         XFreeGC(dpy, wtext->gc);
273         XFreeGC(dpy, wtext->regGC);
274         XFreeGC(dpy, wtext->invGC);
275         wfree(wtext->text.txt);
276         wCoreDestroy(wtext->core);
277         wfree(wtext);
278
279         LEAVE("wTextDestroy");
280 }
281
282 /* The text-field consists of a frame drawn around the outside,
283  * and a textbox inside.  The space between the frame and the
284  * text-box is the xOffset,yOffset.  When the text needs refreshing,
285  * we only have to redraw the part inside the text-box, and we can
286  * leave the frame.  If we get an expose event, or for some reason
287  * need to redraw the frame, wTextPaint will redraw the frame, and
288  * then call wTextRefresh to redraw the text-box */
289
290 /********************************************************************
291  * textRefresh                                                     *
292  *   Redraw the text field.  Call this after messing with the text  *
293  *   field.  wTextRefresh re-draws the inside of the text field. If *
294  *   the frame-area of the text-field needs redrawing, call         *
295  *   wTextPaint()                                                   *
296  *                                                                  *
297  * Args:   wtext  - the text field                                  *
298  * Return: none                                                     *
299  * Global: dpy    - the display                                     *
300  ********************************************************************/
301 static void textRefresh(WTextInput * wtext)
302 {
303         WScreen *scr = wtext->core->screen_ptr;
304         char *ptr = wtext->text.txt;
305         int x1, x2, y1, y2;
306
307         /* x1,y1 is the upper left corner of the text box */
308         x1 = wtext->xOffset;
309         y1 = wtext->yOffset;
310         /* x2,y2 is the lower right corner of the text box */
311         x2 = wtext->core->width - wtext->xOffset;
312         y2 = wtext->core->height - wtext->yOffset;
313
314         /* Fill in the text field.  Use the invGC to draw the rectangle,
315          * becuase then it will be the background color */
316         XFillRectangle(dpy, wtext->core->window, wtext->invGC, x1, y1, x2 - x1, y2 - y1);
317
318         /* Draw the text normally */
319         WMDrawImageString(scr->wmscreen, wtext->core->window,
320                           scr->black, scr->white, wtext->font, x1, y1, ptr, wtext->text.length);
321
322         /* Draw the selected text */
323         if (wtext->text.startPos != wtext->text.endPos) {
324                 int sp, ep;
325                 /* we need sp < ep */
326                 if (wtext->text.startPos > wtext->text.endPos) {
327                         sp = wtext->text.endPos;
328                         ep = wtext->text.startPos;
329                 } else {
330                         sp = wtext->text.startPos;
331                         ep = wtext->text.endPos;
332                 }
333
334                 /* x1,y1 is now the upper-left of the selected area */
335                 x1 += WMWidthOfString(wtext->font, ptr, sp);
336                 /* and x2,y2 is the lower-right of the selected area */
337                 ptr += sp * sizeof(char);
338                 x2 = x1 + WMWidthOfString(wtext->font, ptr, (ep - sp));
339                 /* Fill in the area  where the selected text will go:     *
340                  * use the regGC to draw the rectangle, becuase then it   *
341                  * will be the color of the non-selected text             */
342                 XFillRectangle(dpy, wtext->core->window, wtext->regGC, x1, y1, x2 - x1, y2 - y1);
343
344                 /* Draw the selected text. Inverse bg and fg colors for selection */
345                 WMDrawImageString(scr->wmscreen, wtext->core->window,
346                                   scr->white, scr->black, wtext->font, x1, y1, ptr, (ep - sp));
347         }
348
349         /* And draw a quick little line for the cursor position */
350         x1 = WMWidthOfString(wtext->font, wtext->text.txt, wtext->text.endPos)
351             + wtext->xOffset;
352         XDrawLine(dpy, wtext->core->window, wtext->regGC, x1, 2, x1, wtext->core->height - 3);
353 }
354
355 /********************************************************************
356  * wTextPaint                                                       *
357  *                                                                  *
358  * Args:   wtext  - the text field                                  *
359  * Return:                                                          *
360  * Global: dpy    - the display                                     *
361  ********************************************************************/
362 void wTextPaint(WTextInput * wtext)
363 {
364         ENTER("wTextPaint");
365
366         /* refresh */
367         textRefresh(wtext);
368
369         /* Draw box */
370         XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0, wtext->core->width - 1, wtext->core->height - 1);
371
372         LEAVE("wTextPaint");
373 }
374
375 /********************************************************************
376  * wTextGetText                                                     *
377  *   return the string in the text field wText.  DO NOT FREE THE    *
378  *   RETURNED STRING!                                               *
379  *                                                                  *
380  * Args:   wtext  - the text field                                  *
381  * Return: the text in the text field (NULL terminated)             *
382  * Global:                                                          *
383  ********************************************************************/
384 char *wTextGetText(WTextInput * wtext)
385 {
386         if (!wtext->canceled)
387                 return wtext->text.txt;
388         else
389                 return NULL;
390 }
391
392 /********************************************************************
393  * wTextPutText                                                     *
394  *   Put the string txt in the text field wText.  The text field    *
395  *   needs to be explicitly refreshed after wTextPutText by calling *
396  *   wTextRefresh().                                                *
397  *   The string txt is copied                                       *
398  *                                                                  *
399  * Args:   wtext  - the text field                                  *
400  *         txt    - the new text string... freed by the text field! *
401  * Return: none                                                     *
402  * Global:                                                          *
403  ********************************************************************/
404 void wTextPutText(WTextInput * wtext, char *txt)
405 {
406         int length = strlen(txt);
407
408         /* no memory leaks!  free the old txt */
409         if (wtext->text.txt != NULL)
410                 wfree(wtext->text.txt);
411
412         wtext->text.txt = (char *)wmalloc((length + 1) * sizeof(char));
413         strcpy(wtext->text.txt, txt);
414         wtext->text.length = length;
415         /* By default No text is selected, and the cursor is at the end */
416         wtext->text.startPos = length;
417         wtext->text.endPos = length;
418 }
419
420 /********************************************************************
421  * textInsert                                                      *
422  *   Insert some text at the cursor.  (if startPos != endPos,       *
423  *   replace the selected text, otherwise insert)                   *
424  *   The string txt is copied.                                      *
425  *                                                                  *
426  * Args:   wText  - the text field                                  *
427  *         txt    - the new text string... freed by the text field! *
428  * Return: none                                                     *
429  * Global:                                                          *
430  ********************************************************************/
431 static void textInsert(WTextInput * wtext, char *txt)
432 {
433         char *newTxt;
434         int newLen, txtLen, i, j;
435         int sp, ep;
436
437         /* we need sp < ep */
438         if (wtext->text.startPos > wtext->text.endPos) {
439                 sp = wtext->text.endPos;
440                 ep = wtext->text.startPos;
441         } else {
442                 sp = wtext->text.startPos;
443                 ep = wtext->text.endPos;
444         }
445
446         txtLen = strlen(txt);
447         newLen = wtext->text.length + txtLen - (ep - sp) + 1;
448
449         newTxt = (char *)malloc(newLen * sizeof(char));
450
451         /* copy the old text up to sp */
452         for (i = 0; i < sp; i++)
453                 newTxt[i] = wtext->text.txt[i];
454
455         /* insert new text */
456         for (j = 0; j < txtLen; j++, i++)
457                 newTxt[i] = txt[j];
458
459         /* copy old text after ep */
460         for (j = ep; j < wtext->text.length; j++, i++)
461                 newTxt[i] = wtext->text.txt[j];
462
463         newTxt[i] = '\0';
464
465         /* By default No text is selected, and the cursor is at the end
466          * of inserted text */
467         wtext->text.startPos = sp + txtLen;
468         wtext->text.endPos = sp + txtLen;
469
470         wfree(wtext->text.txt);
471         wtext->text.txt = newTxt;
472         wtext->text.length = newLen - 1;
473 }
474
475 /********************************************************************
476  * wTextSelect                                                      *
477  *   Select some text.  If start == end, then the cursor is moved   *
478  *   to that position.  If end == -1, then the text from start to   *
479  *   the end of the text entered in the text field is selected.     *
480  *   The text field is not automatically re-drawn!  You must call   *
481  *   wTextRefresh to re-draw the text field.                        *
482  *                                                                  *
483  * Args:   wtext  - the text field                                  *
484  *         start  - the beginning of the selected text              *
485  *         end    - the end of the selected text                    *
486  * Return: none                                                     *
487  * Global:                                                          *
488  ********************************************************************/
489 void wTextSelect(WTextInput * wtext, int start, int end)
490 {
491         if (end == -1)
492                 wtext->text.endPos = wtext->text.length;
493         else
494                 wtext->text.endPos = end;
495         wtext->text.startPos = start;
496 }
497
498 #if 0
499 static void blink(void *data)
500 {
501         int x;
502         WTextInput *wtext = (WTextInput *) data;
503         GC gc;
504
505         /* And draw a quick little line for the cursor position */
506         if (wtext->blink_on) {
507                 gc = wtext->regGC;
508                 wtext->blink_on = 0;
509         } else {
510                 gc = wtext->invGC;
511                 wtext->blink_on = 1;
512         }
513         x = WMWidthOfString(wtext->font, wtext->text.txt, wtext->text.endPos)
514             + wtext->xOffset;
515         XDrawLine(dpy, wtext->core->window, gc, x, 2, x, wtext->core->height - 3);
516
517         if (wtext->blinking)
518                 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
519 }
520 #endif
521
522 /********************************************************************
523  * textEventHandler -- handles and dispatches all the events that   *
524  *   the text field class supports                                  *
525  *                                                                  *
526  * Args:   desc - all we need to know about this object             *
527  * Return: none                                                     *
528  * Global:                                                          *
529  ********************************************************************/
530 static void textEventHandler(WObjDescriptor * desc, XEvent * event)
531 {
532         WTextInput *wtext = desc->parent;
533         int handled = False;    /* has the event been handled */
534
535         switch (event->type) {
536         case MotionNotify:
537                 /* If the button isn't down, we don't care about the
538                  * event, but otherwise we want to adjust the selected
539                  * text so we can wTextRefresh() */
540                 if (event->xmotion.state & (Button1Mask | Button3Mask | Button2Mask)) {
541                         PDEBUG("MotionNotify");
542                         handled = True;
543                         wtext->text.endPos = textXtoPos(wtext, event->xmotion.x);
544                 }
545                 break;
546
547         case ButtonPress:
548                 PDEBUG("ButtonPress");
549                 handled = True;
550                 wtext->text.startPos = textXtoPos(wtext, event->xbutton.x);
551                 wtext->text.endPos = wtext->text.startPos;
552                 break;
553
554         case ButtonRelease:
555                 PDEBUG("ButtonRelease");
556                 handled = True;
557                 wtext->text.endPos = textXtoPos(wtext, event->xbutton.x);
558                 break;
559
560         case KeyPress:
561                 PDEBUG("KeyPress");
562                 handled = handleKeyPress(wtext, &event->xkey);
563                 break;
564
565         case EnterNotify:
566                 PDEBUG("EnterNotify");
567                 handled = True;
568 #if 0
569                 if (!wtext->magic) {
570                         wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
571                         wtext->blink_on = !wtext->blink_on;
572                         blink(wtext);
573                         wtext->blinking = 1;
574                 }
575 #endif
576                 break;
577
578         case LeaveNotify:
579                 PDEBUG("LeaveNotify");
580                 handled = True;
581 #if 0
582                 wtext->blinking = 0;
583                 if (wtext->blink_on)
584                         blink(wtext);
585                 if (wtext->magic)
586                         wDeleteTimerHandler(wtext->magic);
587                 wtext->magic = NULL;
588 #endif
589                 break;
590
591         default:
592                 break;
593         }
594
595         if (handled)
596                 textRefresh(wtext);
597         else
598                 WMHandleEvent(event);
599
600         return;
601 }
602
603 static void handleExpose(WObjDescriptor * desc, XEvent * event)
604 {
605         wTextPaint(desc->parent);
606 }