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 }