fixed cosmetic bug in geom. dpy window for 8bpp
[wmaker-crm.git] / src / text.c
blob85702879a8f2e4f9611f492d4b5a3db261f35644
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 \********************************************************************/
25 #include "wconfig.h"
27 #include <X11/Xlib.h>
28 #include <X11/Xutil.h>
29 #include <X11/keysym.h>
30 #include <stdlib.h>
31 #include <ctype.h>
33 #include "WindowMaker.h"
34 #include "funcs.h"
35 #include "text.h"
36 #include "actions.h"
38 /* X11R5 don't have this */
39 #ifndef IsPrivateKeypadKey
40 #define IsPrivateKeypadKey(keysym) \
41 (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF))
42 #endif
46 #define TEXT_SHIFT -4 /* Move the text up relative to the bottom of
47 * the text-box */
48 #if 0
49 # define ENTER(X) fprintf(stderr,"Entering: %s()\n", X);
50 # define LEAVE(X) fprintf(stderr,"Leaving: %s()\n", X);
51 # define DEBUG(X) fprintf(stderr,"debug: %s()\n", X);
52 #else
53 # define ENTER(X)
54 # define LEAVE(X)
55 # define DEBUG(X)
56 #endif
58 extern Cursor wCursor[WCUR_LAST];
60 /********************************************************************\
61 * The event handler for the text-field: *
62 \********************************************************************/
63 static void textEventHandler( WObjDescriptor *desc, XEvent *event );
65 static void handleExpose( WObjDescriptor *desc, XEvent *event );
67 static void textInsert( WTextInput *wtext, char *txt );
68 #if 0
69 static void blink(void *data);
70 #endif
72 /********************************************************************\
73 * handleKeyPress *
74 * handle cursor keys, regular keys, etc. Inserts characters in *
75 * text field, at cursor position, if it is a "Normal" key. If *
76 * ksym is the delete key, backspace key, etc., the appropriate *
77 * action is performed on the text in the text field. Does not *
78 * refresh the text field *
79 * *
80 * Args: wText - the text field *
81 * ksym - the key that was pressed *
82 * Return: True, unless the ksym is ignored *
83 * Global: modifier - the bitfield that keeps track of the modifier *
84 * keys that are down *
85 \********************************************************************/
86 static int
87 handleKeyPress( WTextInput *wtext, XKeyEvent *event )
89 KeySym ksym;
90 char buffer[32];
91 int count;
93 count = XLookupString(event, buffer, 32, &ksym, NULL);
95 /* Ignore these keys: */
96 if( IsFunctionKey(ksym) || IsKeypadKey(ksym) ||
97 IsMiscFunctionKey(ksym) || IsPFKey(ksym) ||
98 IsPrivateKeypadKey(ksym) )
99 /* If we don't handle it, make sure it isn't a key that
100 * the window manager needs to see */
101 return False;
103 /* Take care of the cursor keys.. ignore up and down
104 * cursor keys */
105 else if( IsCursorKey(ksym) )
107 int length = wtext->text.length;
108 switch(ksym)
110 case XK_Home:
111 case XK_Begin:
112 wtext->text.endPos = 0;
113 break;
114 case XK_Left:
115 wtext->text.endPos--;
116 break;
117 case XK_Right:
118 wtext->text.endPos++;
119 break;
120 case XK_End:
121 wtext->text.endPos = length;
122 break;
123 default:
124 return False;
126 /* make sure that the startPos and endPos have values
127 * that make sense (ie the are in [0..length] ) */
128 if( wtext->text.endPos < 0 )
129 wtext->text.endPos = 0;
130 if( wtext->text.endPos > length )
131 wtext->text.endPos = length;
132 wtext->text.startPos = wtext->text.endPos;
134 else
136 switch(ksym)
138 /* Ignore these keys: */
139 case XK_Escape:
140 wtext->canceled = True;
141 case XK_Return:
142 wtext->done = True;
143 break;
144 case XK_Tab:
145 case XK_Num_Lock:
146 break;
148 case XK_Delete:
149 /* delete after cursor */
150 if( (wtext->text.endPos == wtext->text.startPos) &&
151 (wtext->text.endPos < wtext->text.length) )
152 wtext->text.endPos++;
153 textInsert( wtext, "" );
154 break;
155 case XK_BackSpace:
156 /* delete before cursor */
157 if( (wtext->text.endPos == wtext->text.startPos) &&
158 (wtext->text.startPos > 0) )
159 wtext->text.startPos--;
160 textInsert( wtext, "" );
161 break;
162 default:
163 if (count==1 && !iscntrl(buffer[0])) {
164 buffer[count] = 0;
165 textInsert( wtext, buffer);
169 return True;
174 /********************************************************************\
175 * textXYtoPos *
176 * given X coord, return position in array *
177 \********************************************************************/
178 static int
179 textXtoPos( WTextInput *wtext, int x )
181 int pos;
182 x -= wtext->xOffset;
184 for( pos=0; wtext->text.txt[pos] != '\0'; pos++ )
186 if( x < 0 )
187 break;
188 else
189 x -= WMWidthOfString( wtext->font, &(wtext->text.txt[pos]), 1 );
192 return pos;
195 /********************************************************************\
196 * wTextCreate *
197 * create an instance of a text class *
199 * Args: *
200 * Return: *
201 * Global: dpy - the display *
202 \********************************************************************/
203 WTextInput *
204 wTextCreate( WCoreWindow *core, int x, int y, int width, int height )
206 WTextInput *wtext;
208 ENTER("wTextCreate");
210 wtext = wmalloc(sizeof(WTextInput));
211 wtext->core = wCoreCreate( core, x, y, width, height );
212 wtext->core->descriptor.handle_anything = &textEventHandler;
213 wtext->core->descriptor.handle_expose = &handleExpose;
214 wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT;
215 wtext->core->descriptor.parent = wtext;
217 wtext->font = core->screen_ptr->menu_entry_font;
219 XDefineCursor( dpy, wtext->core->window, wCursor[WCUR_TEXT] );
221 /* setup the text: */
222 wtext->text.txt = (char *)wmalloc(sizeof(char));
223 wtext->text.txt[0] = '\0';
224 wtext->text.length = 0;
225 wtext->text.startPos = 0;
226 wtext->text.endPos = 0;
229 XGCValues gcv;
231 gcv.foreground = core->screen_ptr->black_pixel;
232 gcv.background = core->screen_ptr->white_pixel;
233 gcv.line_width = 1;
234 gcv.function = GXcopy;
236 wtext->gc = XCreateGC( dpy, wtext->core->window,
237 (GCForeground|GCBackground|
238 GCFunction|GCLineWidth),
239 &gcv );
241 /* set up the regular context */
242 gcv.foreground = core->screen_ptr->black_pixel;
243 gcv.background = core->screen_ptr->white_pixel;
244 gcv.line_width = 1;
245 gcv.function = GXcopy;
247 wtext->regGC = XCreateGC( dpy, wtext->core->window,
248 (GCForeground|GCBackground|
249 GCFunction|GCLineWidth),
250 &gcv );
252 /* set up the inverted context */
253 gcv.function = GXcopyInverted;
255 wtext->invGC = XCreateGC( dpy, wtext->core->window,
256 (GCForeground|GCBackground|
257 GCFunction|GCLineWidth),
258 &gcv );
260 /* and set the background! */
261 XSetWindowBackground( dpy, wtext->core->window, gcv.background );
264 /* Figure out the y-offset... */
265 wtext->yOffset = (height - WMFontHeight(wtext->font))/2;
266 wtext->xOffset = wtext->yOffset;
268 wtext->canceled = False;
269 wtext->done = False; /* becomes True when the user *
270 * hits "Return" key */
272 XMapRaised(dpy, wtext->core->window);
274 LEAVE("wTextCreate");
276 return wtext;
279 /********************************************************************\
280 * wTextDestroy *
282 * Args: wtext - the text field *
283 * Return: *
284 * Global: dpy - the display *
285 \********************************************************************/
286 void
287 wTextDestroy( WTextInput *wtext )
289 ENTER("wTextDestroy")
291 #if 0
292 if (wtext->magic)
293 wDeleteTimerHandler(wtext->magic);
294 wtext->magic = NULL;
295 #endif
296 XFreeGC( dpy, wtext->gc );
297 XFreeGC( dpy, wtext->regGC );
298 XFreeGC( dpy, wtext->invGC );
299 free( wtext->text.txt );
300 wCoreDestroy( wtext->core );
301 free( wtext );
303 LEAVE("wTextDestroy");
307 /* The text-field consists of a frame drawn around the outside,
308 * and a textbox inside. The space between the frame and the
309 * text-box is the xOffset,yOffset. When the text needs refreshing,
310 * we only have to redraw the part inside the text-box, and we can
311 * leave the frame. If we get an expose event, or for some reason
312 * need to redraw the frame, wTextPaint will redraw the frame, and
313 * then call wTextRefresh to redraw the text-box */
316 /********************************************************************\
317 * textRefresh *
318 * Redraw the text field. Call this after messing with the text *
319 * field. wTextRefresh re-draws the inside of the text field. If *
320 * the frame-area of the text-field needs redrawing, call *
321 * wTextPaint() *
323 * Args: wtext - the text field *
324 * Return: none *
325 * Global: dpy - the display *
326 \********************************************************************/
327 static void
328 textRefresh( WTextInput *wtext )
330 int x1,x2,y1,y2;
331 char *ptr = wtext->text.txt;
333 /* x1,y1 is the upper left corner of the text box */
334 x1 = wtext->xOffset;
335 y1 = wtext->yOffset;
336 /* x2,y2 is the lower right corner of the text box */
337 x2 = wtext->core->width - wtext->xOffset;
338 y2 = wtext->core->height - wtext->yOffset;
340 /* Fill in the text field. Use the invGC to draw the rectangle,
341 * becuase then it will be the background color */
342 XFillRectangle( dpy, wtext->core->window, wtext->invGC,
343 x1, y1, x2-x1, y2-y1 );
345 /* Draw the text normally */
346 WMDrawString(wtext->core->screen_ptr->wmscreen, wtext->core->window,
347 wtext->regGC, wtext->font,
348 x1, y2+TEXT_SHIFT, ptr, wtext->text.length);
350 /* Draw the selected text */
351 if( wtext->text.startPos != wtext->text.endPos )
353 int sp,ep;
354 /* we need sp < ep */
355 if( wtext->text.startPos > wtext->text.endPos )
357 sp = wtext->text.endPos;
358 ep = wtext->text.startPos;
360 else
362 sp = wtext->text.startPos;
363 ep = wtext->text.endPos;
366 /* x1,y1 is now the upper-left of the selected area */
367 x1 += WMWidthOfString( wtext->font, ptr, sp );
368 /* and x2,y2 is the lower-right of the selected area */
369 ptr += sp * sizeof(char);
370 x2 = x1 + WMWidthOfString( wtext->font, ptr, (ep - sp) );
371 /* Fill in the area where the selected text will go: *
372 * use the regGC to draw the rectangle, becuase then it *
373 * will be the color of the non-selected text */
374 XFillRectangle( dpy, wtext->core->window, wtext->regGC,
375 x1, y1, x2-x1, y2-y1 );
377 /* Draw the selected text... use invGC so it will be the
378 * opposite color as the filled rectangle */
379 WMDrawString(wtext->core->screen_ptr->wmscreen, wtext->core->window,
380 wtext->invGC, wtext->font, x1, y2+TEXT_SHIFT, ptr, (ep - sp));
383 /* And draw a quick little line for the cursor position */
384 x1 = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
385 + wtext->xOffset;
386 XDrawLine( dpy, wtext->core->window, wtext->regGC, x1, 2, x1,
387 wtext->core->height - 3 );
391 /********************************************************************\
392 * wTextPaint *
394 * Args: wtext - the text field *
395 * Return: *
396 * Global: dpy - the display *
397 \********************************************************************/
398 void
399 wTextPaint( WTextInput *wtext )
401 ENTER("wTextPaint");
403 /* refresh */
404 textRefresh( wtext );
406 /* Draw box */
407 XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
408 wtext->core->width-1, wtext->core->height-1);
410 LEAVE("wTextPaint");
414 /********************************************************************\
415 * wTextGetText *
416 * return the string in the text field wText. DO NOT FREE THE *
417 * RETURNED STRING! *
419 * Args: wtext - the text field *
420 * Return: the text in the text field (NULL terminated) *
421 * Global: *
422 \********************************************************************/
423 char *
424 wTextGetText( WTextInput *wtext )
426 if (!wtext->canceled)
427 return wtext->text.txt;
428 else
429 return NULL;
432 /********************************************************************\
433 * wTextPutText *
434 * Put the string txt in the text field wText. The text field *
435 * needs to be explicitly refreshed after wTextPutText by calling *
436 * wTextRefresh(). *
437 * The string txt is copied *
439 * Args: wtext - the text field *
440 * txt - the new text string... freed by the text field! *
441 * Return: none *
442 * Global: *
443 \********************************************************************/
444 void
445 wTextPutText( WTextInput *wtext, char *txt )
447 int length = strlen(txt);
449 /* no memory leaks! free the old txt */
450 if( wtext->text.txt != NULL )
451 free( wtext->text.txt );
453 wtext->text.txt = (char *)wmalloc((length+1)*sizeof(char));
454 strcpy(wtext->text.txt, txt );
455 wtext->text.length = length;
456 /* By default No text is selected, and the cursor is at the end */
457 wtext->text.startPos = length;
458 wtext->text.endPos = length;
461 /********************************************************************\
462 * textInsert *
463 * Insert some text at the cursor. (if startPos != endPos, *
464 * replace the selected text, otherwise insert) *
465 * The string txt is copied. *
467 * Args: wText - the text field *
468 * txt - the new text string... freed by the text field! *
469 * Return: none *
470 * Global: *
471 \********************************************************************/
472 static void
473 textInsert( WTextInput *wtext, char *txt )
475 char *newTxt;
476 int newLen, txtLen, i,j;
477 int sp,ep;
479 /* we need sp < ep */
480 if( wtext->text.startPos > wtext->text.endPos )
482 sp = wtext->text.endPos;
483 ep = wtext->text.startPos;
485 else
487 sp = wtext->text.startPos;
488 ep = wtext->text.endPos;
491 txtLen = strlen(txt);
492 newLen = wtext->text.length + txtLen - (ep - sp) + 1;
494 newTxt = (char *)malloc(newLen*sizeof(char));
496 /* copy the old text up to sp */
497 for( i=0; i<sp; i++ )
498 newTxt[i] = wtext->text.txt[i];
500 /* insert new text */
501 for( j=0; j<txtLen; j++,i++ )
502 newTxt[i] = txt[j];
504 /* copy old text after ep */
505 for( j=ep; j<wtext->text.length; j++,i++ )
506 newTxt[i] = wtext->text.txt[j];
508 newTxt[i] = '\0';
510 /* By default No text is selected, and the cursor is at the end
511 * of inserted text */
512 wtext->text.startPos = sp+txtLen;
513 wtext->text.endPos = sp+txtLen;
515 free(wtext->text.txt);
516 wtext->text.txt = newTxt;
517 wtext->text.length = newLen-1;
520 /********************************************************************\
521 * wTextSelect *
522 * Select some text. If start == end, then the cursor is moved *
523 * to that position. If end == -1, then the text from start to *
524 * the end of the text entered in the text field is selected. *
525 * The text field is not automatically re-drawn! You must call *
526 * wTextRefresh to re-draw the text field. *
528 * Args: wtext - the text field *
529 * start - the beginning of the selected text *
530 * end - the end of the selected text *
531 * Return: none *
532 * Global: *
533 \********************************************************************/
534 void
535 wTextSelect( WTextInput *wtext, int start, int end )
537 if( end == -1 )
538 wtext->text.endPos = wtext->text.length;
539 else
540 wtext->text.endPos = end;
541 wtext->text.startPos = start;
544 #if 0
545 static void
546 blink(void *data)
548 int x;
549 WTextInput *wtext = (WTextInput*)data;
550 GC gc;
552 /* And draw a quick little line for the cursor position */
553 if (wtext->blink_on) {
554 gc = wtext->regGC;
555 wtext->blink_on = 0;
556 } else {
557 gc = wtext->invGC;
558 wtext->blink_on = 1;
560 x = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
561 + wtext->xOffset;
562 XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3);
564 if (wtext->blinking)
565 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
567 #endif
569 /********************************************************************\
570 * textEventHandler -- handles and dispatches all the events that *
571 * the text field class supports *
573 * Args: desc - all we need to know about this object *
574 * Return: none *
575 * Global: *
576 \********************************************************************/
577 static void
578 textEventHandler( WObjDescriptor *desc, XEvent *event )
580 WTextInput *wtext = desc->parent;
581 int handled = False; /* has the event been handled */
583 switch( event->type )
585 case MotionNotify:
586 /* If the button isn't down, we don't care about the
587 * event, but otherwise we want to adjust the selected
588 * text so we can wTextRefresh() */
589 if( event->xmotion.state & (Button1Mask|Button3Mask|Button2Mask) )
591 DEBUG("MotionNotify");
592 handled = True;
593 wtext->text.endPos = textXtoPos( wtext, event->xmotion.x );
595 break;
597 case ButtonPress:
598 DEBUG("ButtonPress");
599 handled = True;
600 wtext->text.startPos = textXtoPos( wtext, event->xbutton.x );
601 wtext->text.endPos = wtext->text.startPos;
602 break;
604 case ButtonRelease:
605 DEBUG("ButtonRelease");
606 handled = True;
607 wtext->text.endPos = textXtoPos( wtext, event->xbutton.x );
608 break;
610 case KeyPress:
611 DEBUG("KeyPress");
612 handled = handleKeyPress( wtext, &event->xkey );
613 break;
615 case EnterNotify:
616 DEBUG("EnterNotify");
617 handled = True;
618 #if 0
619 if (!wtext->magic)
621 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
622 wtext->blink_on = !wtext->blink_on;
623 blink(wtext);
624 wtext->blinking = 1;
626 #endif
627 break;
629 case LeaveNotify:
630 DEBUG("LeaveNotify");
631 handled = True;
632 #if 0
633 wtext->blinking = 0;
634 if (wtext->blink_on)
635 blink(wtext);
636 if (wtext->magic)
637 wDeleteTimerHandler(wtext->magic);
638 wtext->magic = NULL;
639 #endif
640 break;
642 default:
643 break;
646 if( handled )
647 textRefresh(wtext);
648 else
649 WMHandleEvent(event);
651 return;
655 static void
656 handleExpose(WObjDescriptor *desc, XEvent *event)
658 wTextPaint(desc->parent);