Initial revision
[wmaker-crm.git] / src / text.c
blob632a42b07a6b5c6c80f9f0d3563f9f06ed2b6e41
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 -= wTextWidth( wtext->font->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 #ifndef I18N_MB
234 gcv.font = wtext->font->font->fid;
235 #endif
236 gcv.line_width = 1;
237 gcv.function = GXcopy;
239 wtext->gc = XCreateGC( dpy, wtext->core->window,
240 (GCForeground|GCBackground|
241 #ifndef I18N_MB
242 GCFont|
243 #endif
244 GCFunction|GCLineWidth),
245 &gcv );
247 /* set up the regular context */
248 gcv.foreground = core->screen_ptr->black_pixel;
249 gcv.background = core->screen_ptr->white_pixel;
250 #ifndef I18N_MB
251 gcv.font = wtext->font->font->fid;
252 #endif
253 gcv.line_width = 1;
254 gcv.function = GXcopy;
256 wtext->regGC = XCreateGC( dpy, wtext->core->window,
257 (GCForeground|GCBackground|
258 #ifndef I18N_MB
259 GCFont|
260 #endif
261 GCFunction|GCLineWidth),
262 &gcv );
264 /* set up the inverted context */
265 gcv.function = GXcopyInverted;
267 wtext->invGC = XCreateGC( dpy, wtext->core->window,
268 (GCForeground|GCBackground|
269 #ifndef I18N_MB
270 GCFont|
271 #endif
272 GCFunction|GCLineWidth),
273 &gcv );
275 /* and set the background! */
276 XSetWindowBackground( dpy, wtext->core->window, gcv.background );
279 /* Figure out the y-offset... */
280 wtext->yOffset = (height - wtext->font->height)/2;
281 wtext->xOffset = wtext->yOffset;
283 wtext->canceled = False;
284 wtext->done = False; /* becomes True when the user *
285 * hits "Return" key */
287 XMapRaised(dpy, wtext->core->window);
289 LEAVE("wTextCreate");
291 return wtext;
294 /********************************************************************\
295 * wTextDestroy *
297 * Args: wtext - the text field *
298 * Return: *
299 * Global: dpy - the display *
300 \********************************************************************/
301 void
302 wTextDestroy( WTextInput *wtext )
304 ENTER("wTextDestroy")
306 #if 0
307 if (wtext->magic)
308 wDeleteTimerHandler(wtext->magic);
309 wtext->magic = NULL;
310 #endif
311 XFreeGC( dpy, wtext->gc );
312 XFreeGC( dpy, wtext->regGC );
313 XFreeGC( dpy, wtext->invGC );
314 free( wtext->text.txt );
315 wCoreDestroy( wtext->core );
316 free( wtext );
318 LEAVE("wTextDestroy");
322 /* The text-field consists of a frame drawn around the outside,
323 * and a textbox inside. The space between the frame and the
324 * text-box is the xOffset,yOffset. When the text needs refreshing,
325 * we only have to redraw the part inside the text-box, and we can
326 * leave the frame. If we get an expose event, or for some reason
327 * need to redraw the frame, wTextPaint will redraw the frame, and
328 * then call wTextRefresh to redraw the text-box */
331 /********************************************************************\
332 * textRefresh *
333 * Redraw the text field. Call this after messing with the text *
334 * field. wTextRefresh re-draws the inside of the text field. If *
335 * the frame-area of the text-field needs redrawing, call *
336 * wTextPaint() *
338 * Args: wtext - the text field *
339 * Return: none *
340 * Global: dpy - the display *
341 \********************************************************************/
342 static void
343 textRefresh( WTextInput *wtext )
345 int x1,x2,y1,y2;
346 char *ptr = wtext->text.txt;
348 /* x1,y1 is the upper left corner of the text box */
349 x1 = wtext->xOffset;
350 y1 = wtext->yOffset;
351 /* x2,y2 is the lower right corner of the text box */
352 x2 = wtext->core->width - wtext->xOffset;
353 y2 = wtext->core->height - wtext->yOffset;
355 /* Fill in the text field. Use the invGC to draw the rectangle,
356 * becuase then it will be the background color */
357 XFillRectangle( dpy, wtext->core->window, wtext->invGC,
358 x1, y1, x2-x1, y2-y1 );
360 /* Draw the text normally */
361 wDrawString(wtext->core->window, wtext->font, wtext->regGC,
362 x1, y2+TEXT_SHIFT, ptr, wtext->text.length);
364 /* Draw the selected text */
365 if( wtext->text.startPos != wtext->text.endPos )
367 int sp,ep;
368 /* we need sp < ep */
369 if( wtext->text.startPos > wtext->text.endPos )
371 sp = wtext->text.endPos;
372 ep = wtext->text.startPos;
374 else
376 sp = wtext->text.startPos;
377 ep = wtext->text.endPos;
380 /* x1,y1 is now the upper-left of the selected area */
381 x1 += wTextWidth( wtext->font->font, ptr, sp );
382 /* and x2,y2 is the lower-right of the selected area */
383 ptr += sp * sizeof(char);
384 x2 = x1 +wTextWidth( wtext->font->font, ptr, (ep - sp) );
385 /* Fill in the area where the selected text will go: *
386 * use the regGC to draw the rectangle, becuase then it *
387 * will be the color of the non-selected text */
388 XFillRectangle( dpy, wtext->core->window, wtext->regGC,
389 x1, y1, x2-x1, y2-y1 );
391 /* Draw the selected text... use invGC so it will be the
392 * opposite color as the filled rectangle */
393 wDrawString(wtext->core->window, wtext->font, wtext->invGC,
394 x1, y2+TEXT_SHIFT, ptr, (ep - sp));
397 /* And draw a quick little line for the cursor position */
398 x1 = wTextWidth( wtext->font->font, wtext->text.txt, wtext->text.endPos )
399 + wtext->xOffset;
400 XDrawLine( dpy, wtext->core->window, wtext->regGC, x1, 2, x1,
401 wtext->core->height - 3 );
405 /********************************************************************\
406 * wTextPaint *
408 * Args: wtext - the text field *
409 * Return: *
410 * Global: dpy - the display *
411 \********************************************************************/
412 void
413 wTextPaint( WTextInput *wtext )
415 ENTER("wTextPaint");
417 /* refresh */
418 textRefresh( wtext );
420 /* Draw box */
421 XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
422 wtext->core->width-1, wtext->core->height-1);
424 LEAVE("wTextPaint");
428 /********************************************************************\
429 * wTextGetText *
430 * return the string in the text field wText. DO NOT FREE THE *
431 * RETURNED STRING! *
433 * Args: wtext - the text field *
434 * Return: the text in the text field (NULL terminated) *
435 * Global: *
436 \********************************************************************/
437 char *
438 wTextGetText( WTextInput *wtext )
440 if (!wtext->canceled)
441 return wtext->text.txt;
442 else
443 return NULL;
446 /********************************************************************\
447 * wTextPutText *
448 * Put the string txt in the text field wText. The text field *
449 * needs to be explicitly refreshed after wTextPutText by calling *
450 * wTextRefresh(). *
451 * The string txt is copied *
453 * Args: wtext - the text field *
454 * txt - the new text string... freed by the text field! *
455 * Return: none *
456 * Global: *
457 \********************************************************************/
458 void
459 wTextPutText( WTextInput *wtext, char *txt )
461 int length = strlen(txt);
463 /* no memory leaks! free the old txt */
464 if( wtext->text.txt != NULL )
465 free( wtext->text.txt );
467 wtext->text.txt = (char *)wmalloc((length+1)*sizeof(char));
468 strcpy(wtext->text.txt, txt );
469 wtext->text.length = length;
470 /* By default No text is selected, and the cursor is at the end */
471 wtext->text.startPos = length;
472 wtext->text.endPos = length;
475 /********************************************************************\
476 * textInsert *
477 * Insert some text at the cursor. (if startPos != endPos, *
478 * replace the selected text, otherwise insert) *
479 * The string txt is copied. *
481 * Args: wText - the text field *
482 * txt - the new text string... freed by the text field! *
483 * Return: none *
484 * Global: *
485 \********************************************************************/
486 static void
487 textInsert( WTextInput *wtext, char *txt )
489 char *newTxt;
490 int newLen, txtLen, i,j;
491 int sp,ep;
493 /* we need sp < ep */
494 if( wtext->text.startPos > wtext->text.endPos )
496 sp = wtext->text.endPos;
497 ep = wtext->text.startPos;
499 else
501 sp = wtext->text.startPos;
502 ep = wtext->text.endPos;
505 txtLen = strlen(txt);
506 newLen = wtext->text.length + txtLen - (ep - sp) + 1;
508 newTxt = (char *)malloc(newLen*sizeof(char));
510 /* copy the old text up to sp */
511 for( i=0; i<sp; i++ )
512 newTxt[i] = wtext->text.txt[i];
514 /* insert new text */
515 for( j=0; j<txtLen; j++,i++ )
516 newTxt[i] = txt[j];
518 /* copy old text after ep */
519 for( j=ep; j<wtext->text.length; j++,i++ )
520 newTxt[i] = wtext->text.txt[j];
522 newTxt[i] = '\0';
524 /* By default No text is selected, and the cursor is at the end
525 * of inserted text */
526 wtext->text.startPos = sp+txtLen;
527 wtext->text.endPos = sp+txtLen;
529 free(wtext->text.txt);
530 wtext->text.txt = newTxt;
531 wtext->text.length = newLen-1;
534 /********************************************************************\
535 * wTextSelect *
536 * Select some text. If start == end, then the cursor is moved *
537 * to that position. If end == -1, then the text from start to *
538 * the end of the text entered in the text field is selected. *
539 * The text field is not automatically re-drawn! You must call *
540 * wTextRefresh to re-draw the text field. *
542 * Args: wtext - the text field *
543 * start - the beginning of the selected text *
544 * end - the end of the selected text *
545 * Return: none *
546 * Global: *
547 \********************************************************************/
548 void
549 wTextSelect( WTextInput *wtext, int start, int end )
551 if( end == -1 )
552 wtext->text.endPos = wtext->text.length;
553 else
554 wtext->text.endPos = end;
555 wtext->text.startPos = start;
558 #if 0
559 static void
560 blink(void *data)
562 int x;
563 WTextInput *wtext = (WTextInput*)data;
564 GC gc;
566 /* And draw a quick little line for the cursor position */
567 if (wtext->blink_on) {
568 gc = wtext->regGC;
569 wtext->blink_on = 0;
570 } else {
571 gc = wtext->invGC;
572 wtext->blink_on = 1;
574 x = wTextWidth( wtext->font->font, wtext->text.txt, wtext->text.endPos )
575 + wtext->xOffset;
576 XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3);
578 if (wtext->blinking)
579 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
581 #endif
583 /********************************************************************\
584 * textEventHandler -- handles and dispatches all the events that *
585 * the text field class supports *
587 * Args: desc - all we need to know about this object *
588 * Return: none *
589 * Global: *
590 \********************************************************************/
591 static void
592 textEventHandler( WObjDescriptor *desc, XEvent *event )
594 WTextInput *wtext = desc->parent;
595 int handled = False; /* has the event been handled */
597 switch( event->type )
599 case MotionNotify:
600 /* If the button isn't down, we don't care about the
601 * event, but otherwise we want to adjust the selected
602 * text so we can wTextRefresh() */
603 if( event->xmotion.state & (Button1Mask|Button3Mask|Button2Mask) )
605 DEBUG("MotionNotify");
606 handled = True;
607 wtext->text.endPos = textXtoPos( wtext, event->xmotion.x );
609 break;
611 case ButtonPress:
612 DEBUG("ButtonPress");
613 handled = True;
614 wtext->text.startPos = textXtoPos( wtext, event->xbutton.x );
615 wtext->text.endPos = wtext->text.startPos;
616 break;
618 case ButtonRelease:
619 DEBUG("ButtonRelease");
620 handled = True;
621 wtext->text.endPos = textXtoPos( wtext, event->xbutton.x );
622 break;
624 case KeyPress:
625 DEBUG("KeyPress");
626 handled = handleKeyPress( wtext, &event->xkey );
627 break;
629 case EnterNotify:
630 DEBUG("EnterNotify");
631 handled = True;
632 #if 0
633 if (!wtext->magic)
635 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
636 wtext->blink_on = !wtext->blink_on;
637 blink(wtext);
638 wtext->blinking = 1;
640 #endif
641 break;
643 case LeaveNotify:
644 DEBUG("LeaveNotify");
645 handled = True;
646 #if 0
647 wtext->blinking = 0;
648 if (wtext->blink_on)
649 blink(wtext);
650 if (wtext->magic)
651 wDeleteTimerHandler(wtext->magic);
652 wtext->magic = NULL;
653 #endif
654 break;
656 default:
657 break;
660 if( handled )
661 textRefresh(wtext);
662 else
663 WMHandleEvent(event);
665 return;
669 static void
670 handleExpose(WObjDescriptor *desc, XEvent *event)
672 wTextPaint(desc->parent);