- API change in WINGs for WMDraw*String().
[wmaker-crm.git] / src / text.c
blob03d45fda9e25846a8ba18ff5b60d9556dc858ecc
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
45 #if 0
46 # define ENTER(X) fprintf(stderr,"Entering: %s()\n", X);
47 # define LEAVE(X) fprintf(stderr,"Leaving: %s()\n", X);
48 # define DEBUG(X) fprintf(stderr,"debug: %s()\n", X);
49 #else
50 # define ENTER(X)
51 # define LEAVE(X)
52 # define DEBUG(X)
53 #endif
55 extern Cursor wCursor[WCUR_LAST];
57 /********************************************************************\
58 * The event handler for the text-field: *
59 \********************************************************************/
60 static void textEventHandler( WObjDescriptor *desc, XEvent *event );
62 static void handleExpose( WObjDescriptor *desc, XEvent *event );
64 static void textInsert( WTextInput *wtext, char *txt );
65 #if 0
66 static void blink(void *data);
67 #endif
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
84 handleKeyPress( WTextInput *wtext, XKeyEvent *event )
86 KeySym ksym;
87 char buffer[32];
88 int count;
90 count = XLookupString(event, buffer, 32, &ksym, NULL);
92 /* Ignore these keys: */
93 if( IsFunctionKey(ksym) || IsKeypadKey(ksym) ||
94 IsMiscFunctionKey(ksym) || IsPFKey(ksym) ||
95 IsPrivateKeypadKey(ksym) )
96 /* If we don't handle it, make sure it isn't a key that
97 * the window manager needs to see */
98 return False;
100 /* Take care of the cursor keys.. ignore up and down
101 * cursor keys */
102 else if( IsCursorKey(ksym) )
104 int length = wtext->text.length;
105 switch(ksym)
107 case XK_Home:
108 case XK_Begin:
109 wtext->text.endPos = 0;
110 break;
111 case XK_Left:
112 wtext->text.endPos--;
113 break;
114 case XK_Right:
115 wtext->text.endPos++;
116 break;
117 case XK_End:
118 wtext->text.endPos = length;
119 break;
120 default:
121 return False;
123 /* make sure that the startPos and endPos have values
124 * that make sense (ie the are in [0..length] ) */
125 if( wtext->text.endPos < 0 )
126 wtext->text.endPos = 0;
127 if( wtext->text.endPos > length )
128 wtext->text.endPos = length;
129 wtext->text.startPos = wtext->text.endPos;
131 else
133 switch(ksym)
135 /* Ignore these keys: */
136 case XK_Escape:
137 wtext->canceled = True;
138 case XK_Return:
139 wtext->done = True;
140 break;
141 case XK_Tab:
142 case XK_Num_Lock:
143 break;
145 case XK_Delete:
146 /* delete after cursor */
147 if( (wtext->text.endPos == wtext->text.startPos) &&
148 (wtext->text.endPos < wtext->text.length) )
149 wtext->text.endPos++;
150 textInsert( wtext, "" );
151 break;
152 case XK_BackSpace:
153 /* delete before cursor */
154 if( (wtext->text.endPos == wtext->text.startPos) &&
155 (wtext->text.startPos > 0) )
156 wtext->text.startPos--;
157 textInsert( wtext, "" );
158 break;
159 default:
160 if (count==1 && !iscntrl(buffer[0])) {
161 buffer[count] = 0;
162 textInsert( wtext, buffer);
166 return True;
171 /********************************************************************\
172 * textXYtoPos *
173 * given X coord, return position in array *
174 \********************************************************************/
175 static int
176 textXtoPos( WTextInput *wtext, int x )
178 int pos;
179 x -= wtext->xOffset;
181 for( pos=0; wtext->text.txt[pos] != '\0'; pos++ )
183 if( x < 0 )
184 break;
185 else
186 x -= WMWidthOfString( wtext->font, &(wtext->text.txt[pos]), 1 );
189 return pos;
192 /********************************************************************\
193 * wTextCreate *
194 * create an instance of a text class *
196 * Args: *
197 * Return: *
198 * Global: dpy - the display *
199 \********************************************************************/
200 WTextInput *
201 wTextCreate( WCoreWindow *core, int x, int y, int width, int height )
203 WTextInput *wtext;
205 ENTER("wTextCreate");
207 wtext = wmalloc(sizeof(WTextInput));
208 wtext->core = wCoreCreate( core, x, y, width, height );
209 wtext->core->descriptor.handle_anything = &textEventHandler;
210 wtext->core->descriptor.handle_expose = &handleExpose;
211 wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT;
212 wtext->core->descriptor.parent = wtext;
214 wtext->font = core->screen_ptr->menu_entry_font;
216 XDefineCursor( dpy, wtext->core->window, wCursor[WCUR_TEXT] );
218 /* setup the text: */
219 wtext->text.txt = (char *)wmalloc(sizeof(char));
220 wtext->text.txt[0] = '\0';
221 wtext->text.length = 0;
222 wtext->text.startPos = 0;
223 wtext->text.endPos = 0;
226 XGCValues gcv;
228 gcv.foreground = core->screen_ptr->black_pixel;
229 gcv.background = core->screen_ptr->white_pixel;
230 gcv.line_width = 1;
231 gcv.function = GXcopy;
233 wtext->gc = XCreateGC( dpy, wtext->core->window,
234 (GCForeground|GCBackground|
235 GCFunction|GCLineWidth),
236 &gcv );
238 /* set up the regular context */
239 gcv.foreground = core->screen_ptr->black_pixel;
240 gcv.background = core->screen_ptr->white_pixel;
241 gcv.line_width = 1;
242 gcv.function = GXcopy;
244 wtext->regGC = XCreateGC( dpy, wtext->core->window,
245 (GCForeground|GCBackground|
246 GCFunction|GCLineWidth),
247 &gcv );
249 /* set up the inverted context */
250 gcv.function = GXcopyInverted;
252 wtext->invGC = XCreateGC( dpy, wtext->core->window,
253 (GCForeground|GCBackground|
254 GCFunction|GCLineWidth),
255 &gcv );
257 /* and set the background! */
258 XSetWindowBackground( dpy, wtext->core->window, gcv.background );
261 /* Figure out the y-offset... */
262 wtext->yOffset = (height - WMFontHeight(wtext->font))/2;
263 wtext->xOffset = wtext->yOffset;
265 wtext->canceled = False;
266 wtext->done = False; /* becomes True when the user *
267 * hits "Return" key */
269 XMapRaised(dpy, wtext->core->window);
271 LEAVE("wTextCreate");
273 return wtext;
276 /********************************************************************\
277 * wTextDestroy *
279 * Args: wtext - the text field *
280 * Return: *
281 * Global: dpy - the display *
282 \********************************************************************/
283 void
284 wTextDestroy( WTextInput *wtext )
286 ENTER("wTextDestroy")
288 #if 0
289 if (wtext->magic)
290 wDeleteTimerHandler(wtext->magic);
291 wtext->magic = NULL;
292 #endif
293 XFreeGC( dpy, wtext->gc );
294 XFreeGC( dpy, wtext->regGC );
295 XFreeGC( dpy, wtext->invGC );
296 wfree( wtext->text.txt );
297 wCoreDestroy( wtext->core );
298 wfree( wtext );
300 LEAVE("wTextDestroy");
304 /* The text-field consists of a frame drawn around the outside,
305 * and a textbox inside. The space between the frame and the
306 * text-box is the xOffset,yOffset. When the text needs refreshing,
307 * we only have to redraw the part inside the text-box, and we can
308 * leave the frame. If we get an expose event, or for some reason
309 * need to redraw the frame, wTextPaint will redraw the frame, and
310 * then call wTextRefresh to redraw the text-box */
313 /********************************************************************\
314 * textRefresh *
315 * Redraw the text field. Call this after messing with the text *
316 * field. wTextRefresh re-draws the inside of the text field. If *
317 * the frame-area of the text-field needs redrawing, call *
318 * wTextPaint() *
320 * Args: wtext - the text field *
321 * Return: none *
322 * Global: dpy - the display *
323 \********************************************************************/
324 static void
325 textRefresh( WTextInput *wtext )
327 int x1,x2,y1,y2;
328 char *ptr = wtext->text.txt;
329 WMColor *black, *white;
331 /* x1,y1 is the upper left corner of the text box */
332 x1 = wtext->xOffset;
333 y1 = wtext->yOffset;
334 /* x2,y2 is the lower right corner of the text box */
335 x2 = wtext->core->width - wtext->xOffset;
336 y2 = wtext->core->height - wtext->yOffset;
338 /* Fill in the text field. Use the invGC to draw the rectangle,
339 * becuase then it will be the background color */
340 XFillRectangle( dpy, wtext->core->window, wtext->invGC,
341 x1, y1, x2-x1, y2-y1 );
343 black = WMBlackColor(wtext->core->screen_ptr->wmscreen);
344 white = WMWhiteColor(wtext->core->screen_ptr->wmscreen);
345 /* Draw the text normally */
346 WMDrawImageString(wtext->core->screen_ptr->wmscreen, wtext->core->window,
347 black, white, wtext->font, x1, y1, ptr, wtext->text.length);
349 /* Draw the selected text */
350 if( wtext->text.startPos != wtext->text.endPos )
352 int sp,ep;
353 /* we need sp < ep */
354 if( wtext->text.startPos > wtext->text.endPos )
356 sp = wtext->text.endPos;
357 ep = wtext->text.startPos;
359 else
361 sp = wtext->text.startPos;
362 ep = wtext->text.endPos;
365 /* x1,y1 is now the upper-left of the selected area */
366 x1 += WMWidthOfString( wtext->font, ptr, sp );
367 /* and x2,y2 is the lower-right of the selected area */
368 ptr += sp * sizeof(char);
369 x2 = x1 + WMWidthOfString( wtext->font, ptr, (ep - sp) );
370 /* Fill in the area where the selected text will go: *
371 * use the regGC to draw the rectangle, becuase then it *
372 * will be the color of the non-selected text */
373 XFillRectangle( dpy, wtext->core->window, wtext->regGC,
374 x1, y1, x2-x1, y2-y1 );
376 /* Draw the selected text... use invGC so it will be the
377 * opposite color as the filled rectangle */
378 WMDrawImageString(wtext->core->screen_ptr->wmscreen, wtext->core->window,
379 white, black, wtext->font, x1, y1, ptr, (ep - sp));
382 WMReleaseColor(white);
383 WMReleaseColor(black);
385 /* And draw a quick little line for the cursor position */
386 x1 = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
387 + wtext->xOffset;
388 XDrawLine( dpy, wtext->core->window, wtext->regGC, x1, 2, x1,
389 wtext->core->height - 3 );
393 /********************************************************************\
394 * wTextPaint *
396 * Args: wtext - the text field *
397 * Return: *
398 * Global: dpy - the display *
399 \********************************************************************/
400 void
401 wTextPaint( WTextInput *wtext )
403 ENTER("wTextPaint");
405 /* refresh */
406 textRefresh( wtext );
408 /* Draw box */
409 XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
410 wtext->core->width-1, wtext->core->height-1);
412 LEAVE("wTextPaint");
416 /********************************************************************\
417 * wTextGetText *
418 * return the string in the text field wText. DO NOT FREE THE *
419 * RETURNED STRING! *
421 * Args: wtext - the text field *
422 * Return: the text in the text field (NULL terminated) *
423 * Global: *
424 \********************************************************************/
425 char *
426 wTextGetText( WTextInput *wtext )
428 if (!wtext->canceled)
429 return wtext->text.txt;
430 else
431 return NULL;
434 /********************************************************************\
435 * wTextPutText *
436 * Put the string txt in the text field wText. The text field *
437 * needs to be explicitly refreshed after wTextPutText by calling *
438 * wTextRefresh(). *
439 * The string txt is copied *
441 * Args: wtext - the text field *
442 * txt - the new text string... freed by the text field! *
443 * Return: none *
444 * Global: *
445 \********************************************************************/
446 void
447 wTextPutText( WTextInput *wtext, char *txt )
449 int length = strlen(txt);
451 /* no memory leaks! free the old txt */
452 if( wtext->text.txt != NULL )
453 wfree( wtext->text.txt );
455 wtext->text.txt = (char *)wmalloc((length+1)*sizeof(char));
456 strcpy(wtext->text.txt, txt );
457 wtext->text.length = length;
458 /* By default No text is selected, and the cursor is at the end */
459 wtext->text.startPos = length;
460 wtext->text.endPos = length;
463 /********************************************************************\
464 * textInsert *
465 * Insert some text at the cursor. (if startPos != endPos, *
466 * replace the selected text, otherwise insert) *
467 * The string txt is copied. *
469 * Args: wText - the text field *
470 * txt - the new text string... freed by the text field! *
471 * Return: none *
472 * Global: *
473 \********************************************************************/
474 static void
475 textInsert( WTextInput *wtext, char *txt )
477 char *newTxt;
478 int newLen, txtLen, i,j;
479 int sp,ep;
481 /* we need sp < ep */
482 if( wtext->text.startPos > wtext->text.endPos )
484 sp = wtext->text.endPos;
485 ep = wtext->text.startPos;
487 else
489 sp = wtext->text.startPos;
490 ep = wtext->text.endPos;
493 txtLen = strlen(txt);
494 newLen = wtext->text.length + txtLen - (ep - sp) + 1;
496 newTxt = (char *)malloc(newLen*sizeof(char));
498 /* copy the old text up to sp */
499 for( i=0; i<sp; i++ )
500 newTxt[i] = wtext->text.txt[i];
502 /* insert new text */
503 for( j=0; j<txtLen; j++,i++ )
504 newTxt[i] = txt[j];
506 /* copy old text after ep */
507 for( j=ep; j<wtext->text.length; j++,i++ )
508 newTxt[i] = wtext->text.txt[j];
510 newTxt[i] = '\0';
512 /* By default No text is selected, and the cursor is at the end
513 * of inserted text */
514 wtext->text.startPos = sp+txtLen;
515 wtext->text.endPos = sp+txtLen;
517 wfree(wtext->text.txt);
518 wtext->text.txt = newTxt;
519 wtext->text.length = newLen-1;
522 /********************************************************************\
523 * wTextSelect *
524 * Select some text. If start == end, then the cursor is moved *
525 * to that position. If end == -1, then the text from start to *
526 * the end of the text entered in the text field is selected. *
527 * The text field is not automatically re-drawn! You must call *
528 * wTextRefresh to re-draw the text field. *
530 * Args: wtext - the text field *
531 * start - the beginning of the selected text *
532 * end - the end of the selected text *
533 * Return: none *
534 * Global: *
535 \********************************************************************/
536 void
537 wTextSelect( WTextInput *wtext, int start, int end )
539 if( end == -1 )
540 wtext->text.endPos = wtext->text.length;
541 else
542 wtext->text.endPos = end;
543 wtext->text.startPos = start;
546 #if 0
547 static void
548 blink(void *data)
550 int x;
551 WTextInput *wtext = (WTextInput*)data;
552 GC gc;
554 /* And draw a quick little line for the cursor position */
555 if (wtext->blink_on) {
556 gc = wtext->regGC;
557 wtext->blink_on = 0;
558 } else {
559 gc = wtext->invGC;
560 wtext->blink_on = 1;
562 x = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
563 + wtext->xOffset;
564 XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3);
566 if (wtext->blinking)
567 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
569 #endif
571 /********************************************************************\
572 * textEventHandler -- handles and dispatches all the events that *
573 * the text field class supports *
575 * Args: desc - all we need to know about this object *
576 * Return: none *
577 * Global: *
578 \********************************************************************/
579 static void
580 textEventHandler( WObjDescriptor *desc, XEvent *event )
582 WTextInput *wtext = desc->parent;
583 int handled = False; /* has the event been handled */
585 switch( event->type )
587 case MotionNotify:
588 /* If the button isn't down, we don't care about the
589 * event, but otherwise we want to adjust the selected
590 * text so we can wTextRefresh() */
591 if( event->xmotion.state & (Button1Mask|Button3Mask|Button2Mask) )
593 DEBUG("MotionNotify");
594 handled = True;
595 wtext->text.endPos = textXtoPos( wtext, event->xmotion.x );
597 break;
599 case ButtonPress:
600 DEBUG("ButtonPress");
601 handled = True;
602 wtext->text.startPos = textXtoPos( wtext, event->xbutton.x );
603 wtext->text.endPos = wtext->text.startPos;
604 break;
606 case ButtonRelease:
607 DEBUG("ButtonRelease");
608 handled = True;
609 wtext->text.endPos = textXtoPos( wtext, event->xbutton.x );
610 break;
612 case KeyPress:
613 DEBUG("KeyPress");
614 handled = handleKeyPress( wtext, &event->xkey );
615 break;
617 case EnterNotify:
618 DEBUG("EnterNotify");
619 handled = True;
620 #if 0
621 if (!wtext->magic)
623 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
624 wtext->blink_on = !wtext->blink_on;
625 blink(wtext);
626 wtext->blinking = 1;
628 #endif
629 break;
631 case LeaveNotify:
632 DEBUG("LeaveNotify");
633 handled = True;
634 #if 0
635 wtext->blinking = 0;
636 if (wtext->blink_on)
637 blink(wtext);
638 if (wtext->magic)
639 wDeleteTimerHandler(wtext->magic);
640 wtext->magic = NULL;
641 #endif
642 break;
644 default:
645 break;
648 if( handled )
649 textRefresh(wtext);
650 else
651 WMHandleEvent(event);
653 return;
657 static void
658 handleExpose(WObjDescriptor *desc, XEvent *event)
660 wTextPaint(desc->parent);