changed indentation to use spaces only
[wmaker-crm.git] / src / text.c
blob54643bea241fdd88afd7a790d229616be63086f2
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 <string.h>
32 #include <ctype.h>
34 #include "WindowMaker.h"
35 #include "funcs.h"
36 #include "text.h"
37 #include "actions.h"
39 /* X11R5 don't have this */
40 #ifndef IsPrivateKeypadKey
41 #define IsPrivateKeypadKey(keysym) \
42 (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF))
43 #endif
46 #ifdef DEBUG
47 # define ENTER(X) fprintf(stderr,"Entering: %s()\n", X);
48 # define LEAVE(X) fprintf(stderr,"Leaving: %s()\n", X);
49 # define PDEBUG(X) fprintf(stderr,"debug: %s()\n", X);
50 #else
51 # define ENTER(X)
52 # define LEAVE(X)
53 # define PDEBUG(X)
54 #endif
56 extern Cursor wCursor[WCUR_LAST];
58 /********************************************************************
59 * The event handler for the text-field: *
60 ********************************************************************/
61 static void textEventHandler( WObjDescriptor *desc, XEvent *event );
63 static void handleExpose( WObjDescriptor *desc, XEvent *event );
65 static void textInsert( WTextInput *wtext, char *txt );
66 #if 0
67 static void blink(void *data);
68 #endif
70 /********************************************************************
71 * handleKeyPress *
72 * handle cursor keys, regular keys, etc. Inserts characters in *
73 * text field, at cursor position, if it is a "Normal" key. If *
74 * ksym is the delete key, backspace key, etc., the appropriate *
75 * action is performed on the text in the text field. Does not *
76 * refresh the text field *
77 * *
78 * Args: wText - the text field *
79 * ksym - the key that was pressed *
80 * Return: True, unless the ksym is ignored *
81 * Global: modifier - the bitfield that keeps track of the modifier *
82 * keys that are down *
83 ********************************************************************/
84 static int
85 handleKeyPress( WTextInput *wtext, XKeyEvent *event )
87 KeySym ksym;
88 char buffer[32];
89 int count;
91 count = XLookupString(event, buffer, 32, &ksym, NULL);
93 /* Ignore these keys: */
94 if( IsFunctionKey(ksym) || IsKeypadKey(ksym) ||
95 IsMiscFunctionKey(ksym) || IsPFKey(ksym) ||
96 IsPrivateKeypadKey(ksym) )
97 /* If we don't handle it, make sure it isn't a key that
98 * the window manager needs to see */
99 return False;
101 /* Take care of the cursor keys.. ignore up and down
102 * cursor keys */
103 else if( IsCursorKey(ksym) )
105 int length = wtext->text.length;
106 switch(ksym)
108 case XK_Home:
109 case XK_Begin:
110 wtext->text.endPos = 0;
111 break;
112 case XK_Left:
113 wtext->text.endPos--;
114 break;
115 case XK_Right:
116 wtext->text.endPos++;
117 break;
118 case XK_End:
119 wtext->text.endPos = length;
120 break;
121 default:
122 return False;
124 /* make sure that the startPos and endPos have values
125 * that make sense (ie the are in [0..length] ) */
126 if( wtext->text.endPos < 0 )
127 wtext->text.endPos = 0;
128 if( wtext->text.endPos > length )
129 wtext->text.endPos = length;
130 wtext->text.startPos = wtext->text.endPos;
132 else
134 switch(ksym)
136 /* Ignore these keys: */
137 case XK_Escape:
138 wtext->canceled = True;
139 case XK_Return:
140 wtext->done = True;
141 break;
142 case XK_Tab:
143 case XK_Num_Lock:
144 break;
146 case XK_Delete:
147 /* delete after cursor */
148 if( (wtext->text.endPos == wtext->text.startPos) &&
149 (wtext->text.endPos < wtext->text.length) )
150 wtext->text.endPos++;
151 textInsert( wtext, "" );
152 break;
153 case XK_BackSpace:
154 /* delete before cursor */
155 if( (wtext->text.endPos == wtext->text.startPos) &&
156 (wtext->text.startPos > 0) )
157 wtext->text.startPos--;
158 textInsert( wtext, "" );
159 break;
160 default:
161 if (count==1 && !iscntrl(buffer[0])) {
162 buffer[count] = 0;
163 textInsert( wtext, buffer);
167 return True;
172 /********************************************************************
173 * textXYtoPos *
174 * given X coord, return position in array *
175 ********************************************************************/
176 static int
177 textXtoPos( WTextInput *wtext, int x )
179 int pos;
180 x -= wtext->xOffset;
182 for( pos=0; wtext->text.txt[pos] != '\0'; pos++ )
184 if( x < 0 )
185 break;
186 else
187 x -= WMWidthOfString( wtext->font, &(wtext->text.txt[pos]), 1 );
190 return pos;
193 /********************************************************************
194 * wTextCreate *
195 * create an instance of a text class *
197 * Args: *
198 * Return: *
199 * Global: dpy - the display *
200 ********************************************************************/
201 WTextInput *
202 wTextCreate( WCoreWindow *core, int x, int y, int width, int height )
204 WTextInput *wtext;
206 ENTER("wTextCreate");
208 wtext = wmalloc(sizeof(WTextInput));
209 wtext->core = wCoreCreate( core, x, y, width, height );
210 wtext->core->descriptor.handle_anything = &textEventHandler;
211 wtext->core->descriptor.handle_expose = &handleExpose;
212 wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT;
213 wtext->core->descriptor.parent = wtext;
215 wtext->font = core->screen_ptr->menu_entry_font;
217 XDefineCursor( dpy, wtext->core->window, wCursor[WCUR_TEXT] );
219 /* setup the text: */
220 wtext->text.txt = (char *)wmalloc(sizeof(char));
221 wtext->text.txt[0] = '\0';
222 wtext->text.length = 0;
223 wtext->text.startPos = 0;
224 wtext->text.endPos = 0;
227 XGCValues gcv;
229 gcv.foreground = core->screen_ptr->black_pixel;
230 gcv.background = core->screen_ptr->white_pixel;
231 gcv.line_width = 1;
232 gcv.function = GXcopy;
234 wtext->gc = XCreateGC( dpy, wtext->core->window,
235 (GCForeground|GCBackground|
236 GCFunction|GCLineWidth),
237 &gcv );
239 /* set up the regular context */
240 gcv.foreground = core->screen_ptr->black_pixel;
241 gcv.background = core->screen_ptr->white_pixel;
242 gcv.line_width = 1;
243 gcv.function = GXcopy;
245 wtext->regGC = XCreateGC( dpy, wtext->core->window,
246 (GCForeground|GCBackground|
247 GCFunction|GCLineWidth),
248 &gcv );
250 /* set up the inverted context */
251 gcv.function = GXcopyInverted;
253 wtext->invGC = XCreateGC( dpy, wtext->core->window,
254 (GCForeground|GCBackground|
255 GCFunction|GCLineWidth),
256 &gcv );
258 /* and set the background! */
259 XSetWindowBackground( dpy, wtext->core->window, gcv.background );
262 /* Figure out the y-offset... */
263 wtext->yOffset = (height - WMFontHeight(wtext->font))/2;
264 wtext->xOffset = wtext->yOffset;
266 wtext->canceled = False;
267 wtext->done = False; /* becomes True when the user *
268 * hits "Return" key */
270 XMapRaised(dpy, wtext->core->window);
272 LEAVE("wTextCreate");
274 return wtext;
277 /********************************************************************
278 * wTextDestroy *
280 * Args: wtext - the text field *
281 * Return: *
282 * Global: dpy - the display *
283 ********************************************************************/
284 void
285 wTextDestroy( WTextInput *wtext )
287 ENTER("wTextDestroy")
289 #if 0
290 if (wtext->magic)
291 wDeleteTimerHandler(wtext->magic);
292 wtext->magic = NULL;
293 #endif
294 XFreeGC( dpy, wtext->gc );
295 XFreeGC( dpy, wtext->regGC );
296 XFreeGC( dpy, wtext->invGC );
297 wfree( wtext->text.txt );
298 wCoreDestroy( wtext->core );
299 wfree( wtext );
301 LEAVE("wTextDestroy");
305 /* The text-field consists of a frame drawn around the outside,
306 * and a textbox inside. The space between the frame and the
307 * text-box is the xOffset,yOffset. When the text needs refreshing,
308 * we only have to redraw the part inside the text-box, and we can
309 * leave the frame. If we get an expose event, or for some reason
310 * need to redraw the frame, wTextPaint will redraw the frame, and
311 * then call wTextRefresh to redraw the text-box */
314 /********************************************************************
315 * textRefresh *
316 * Redraw the text field. Call this after messing with the text *
317 * field. wTextRefresh re-draws the inside of the text field. If *
318 * the frame-area of the text-field needs redrawing, call *
319 * wTextPaint() *
321 * Args: wtext - the text field *
322 * Return: none *
323 * Global: dpy - the display *
324 ********************************************************************/
325 static void
326 textRefresh(WTextInput *wtext)
328 WScreen *scr = wtext->core->screen_ptr;
329 char *ptr = wtext->text.txt;
330 int x1,x2,y1,y2;
332 /* x1,y1 is the upper left corner of the text box */
333 x1 = wtext->xOffset;
334 y1 = wtext->yOffset;
335 /* x2,y2 is the lower right corner of the text box */
336 x2 = wtext->core->width - wtext->xOffset;
337 y2 = wtext->core->height - wtext->yOffset;
339 /* Fill in the text field. Use the invGC to draw the rectangle,
340 * becuase then it will be the background color */
341 XFillRectangle(dpy, wtext->core->window, wtext->invGC,
342 x1, y1, x2-x1, y2-y1);
344 /* Draw the text normally */
345 WMDrawImageString(scr->wmscreen, wtext->core->window,
346 scr->black, scr->white, wtext->font, x1, y1, ptr,
347 wtext->text.length);
349 /* Draw the selected text */
350 if (wtext->text.startPos != wtext->text.endPos) {
351 int sp,ep;
352 /* we need sp < ep */
353 if (wtext->text.startPos > wtext->text.endPos) {
354 sp = wtext->text.endPos;
355 ep = wtext->text.startPos;
356 } else {
357 sp = wtext->text.startPos;
358 ep = wtext->text.endPos;
361 /* x1,y1 is now the upper-left of the selected area */
362 x1 += WMWidthOfString(wtext->font, ptr, sp);
363 /* and x2,y2 is the lower-right of the selected area */
364 ptr += sp * sizeof(char);
365 x2 = x1 + WMWidthOfString(wtext->font, ptr, (ep - sp));
366 /* Fill in the area where the selected text will go: *
367 * use the regGC to draw the rectangle, becuase then it *
368 * will be the color of the non-selected text */
369 XFillRectangle(dpy, wtext->core->window, wtext->regGC,
370 x1, y1, x2-x1, y2-y1);
372 /* Draw the selected text. Inverse bg and fg colors for selection */
373 WMDrawImageString(scr->wmscreen, wtext->core->window,
374 scr->white, scr->black, wtext->font, x1, y1, ptr,
375 (ep - sp));
378 /* And draw a quick little line for the cursor position */
379 x1 = WMWidthOfString(wtext->font, wtext->text.txt, wtext->text.endPos)
380 + wtext->xOffset;
381 XDrawLine(dpy, wtext->core->window, wtext->regGC, x1, 2, x1,
382 wtext->core->height - 3);
386 /********************************************************************
387 * wTextPaint *
389 * Args: wtext - the text field *
390 * Return: *
391 * Global: dpy - the display *
392 ********************************************************************/
393 void
394 wTextPaint( WTextInput *wtext )
396 ENTER("wTextPaint");
398 /* refresh */
399 textRefresh( wtext );
401 /* Draw box */
402 XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
403 wtext->core->width-1, wtext->core->height-1);
405 LEAVE("wTextPaint");
409 /********************************************************************
410 * wTextGetText *
411 * return the string in the text field wText. DO NOT FREE THE *
412 * RETURNED STRING! *
414 * Args: wtext - the text field *
415 * Return: the text in the text field (NULL terminated) *
416 * Global: *
417 ********************************************************************/
418 char *
419 wTextGetText( WTextInput *wtext )
421 if (!wtext->canceled)
422 return wtext->text.txt;
423 else
424 return NULL;
427 /********************************************************************
428 * wTextPutText *
429 * Put the string txt in the text field wText. The text field *
430 * needs to be explicitly refreshed after wTextPutText by calling *
431 * wTextRefresh(). *
432 * The string txt is copied *
434 * Args: wtext - the text field *
435 * txt - the new text string... freed by the text field! *
436 * Return: none *
437 * Global: *
438 ********************************************************************/
439 void
440 wTextPutText( WTextInput *wtext, char *txt )
442 int length = strlen(txt);
444 /* no memory leaks! free the old txt */
445 if( wtext->text.txt != NULL )
446 wfree( wtext->text.txt );
448 wtext->text.txt = (char *)wmalloc((length+1)*sizeof(char));
449 strcpy(wtext->text.txt, txt );
450 wtext->text.length = length;
451 /* By default No text is selected, and the cursor is at the end */
452 wtext->text.startPos = length;
453 wtext->text.endPos = length;
456 /********************************************************************
457 * textInsert *
458 * Insert some text at the cursor. (if startPos != endPos, *
459 * replace the selected text, otherwise insert) *
460 * The string txt is copied. *
462 * Args: wText - the text field *
463 * txt - the new text string... freed by the text field! *
464 * Return: none *
465 * Global: *
466 ********************************************************************/
467 static void
468 textInsert( WTextInput *wtext, char *txt )
470 char *newTxt;
471 int newLen, txtLen, i,j;
472 int sp,ep;
474 /* we need sp < ep */
475 if( wtext->text.startPos > wtext->text.endPos )
477 sp = wtext->text.endPos;
478 ep = wtext->text.startPos;
480 else
482 sp = wtext->text.startPos;
483 ep = wtext->text.endPos;
486 txtLen = strlen(txt);
487 newLen = wtext->text.length + txtLen - (ep - sp) + 1;
489 newTxt = (char *)malloc(newLen*sizeof(char));
491 /* copy the old text up to sp */
492 for( i=0; i<sp; i++ )
493 newTxt[i] = wtext->text.txt[i];
495 /* insert new text */
496 for( j=0; j<txtLen; j++,i++ )
497 newTxt[i] = txt[j];
499 /* copy old text after ep */
500 for( j=ep; j<wtext->text.length; j++,i++ )
501 newTxt[i] = wtext->text.txt[j];
503 newTxt[i] = '\0';
505 /* By default No text is selected, and the cursor is at the end
506 * of inserted text */
507 wtext->text.startPos = sp+txtLen;
508 wtext->text.endPos = sp+txtLen;
510 wfree(wtext->text.txt);
511 wtext->text.txt = newTxt;
512 wtext->text.length = newLen-1;
515 /********************************************************************
516 * wTextSelect *
517 * Select some text. If start == end, then the cursor is moved *
518 * to that position. If end == -1, then the text from start to *
519 * the end of the text entered in the text field is selected. *
520 * The text field is not automatically re-drawn! You must call *
521 * wTextRefresh to re-draw the text field. *
523 * Args: wtext - the text field *
524 * start - the beginning of the selected text *
525 * end - the end of the selected text *
526 * Return: none *
527 * Global: *
528 ********************************************************************/
529 void
530 wTextSelect( WTextInput *wtext, int start, int end )
532 if( end == -1 )
533 wtext->text.endPos = wtext->text.length;
534 else
535 wtext->text.endPos = end;
536 wtext->text.startPos = start;
539 #if 0
540 static void
541 blink(void *data)
543 int x;
544 WTextInput *wtext = (WTextInput*)data;
545 GC gc;
547 /* And draw a quick little line for the cursor position */
548 if (wtext->blink_on) {
549 gc = wtext->regGC;
550 wtext->blink_on = 0;
551 } else {
552 gc = wtext->invGC;
553 wtext->blink_on = 1;
555 x = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
556 + wtext->xOffset;
557 XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3);
559 if (wtext->blinking)
560 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
562 #endif
564 /********************************************************************
565 * textEventHandler -- handles and dispatches all the events that *
566 * the text field class supports *
568 * Args: desc - all we need to know about this object *
569 * Return: none *
570 * Global: *
571 ********************************************************************/
572 static void
573 textEventHandler( WObjDescriptor *desc, XEvent *event )
575 WTextInput *wtext = desc->parent;
576 int handled = False; /* has the event been handled */
578 switch( event->type )
580 case MotionNotify:
581 /* If the button isn't down, we don't care about the
582 * event, but otherwise we want to adjust the selected
583 * text so we can wTextRefresh() */
584 if( event->xmotion.state & (Button1Mask|Button3Mask|Button2Mask) )
586 PDEBUG("MotionNotify");
587 handled = True;
588 wtext->text.endPos = textXtoPos( wtext, event->xmotion.x );
590 break;
592 case ButtonPress:
593 PDEBUG("ButtonPress");
594 handled = True;
595 wtext->text.startPos = textXtoPos( wtext, event->xbutton.x );
596 wtext->text.endPos = wtext->text.startPos;
597 break;
599 case ButtonRelease:
600 PDEBUG("ButtonRelease");
601 handled = True;
602 wtext->text.endPos = textXtoPos( wtext, event->xbutton.x );
603 break;
605 case KeyPress:
606 PDEBUG("KeyPress");
607 handled = handleKeyPress( wtext, &event->xkey );
608 break;
610 case EnterNotify:
611 PDEBUG("EnterNotify");
612 handled = True;
613 #if 0
614 if (!wtext->magic)
616 wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
617 wtext->blink_on = !wtext->blink_on;
618 blink(wtext);
619 wtext->blinking = 1;
621 #endif
622 break;
624 case LeaveNotify:
625 PDEBUG("LeaveNotify");
626 handled = True;
627 #if 0
628 wtext->blinking = 0;
629 if (wtext->blink_on)
630 blink(wtext);
631 if (wtext->magic)
632 wDeleteTimerHandler(wtext->magic);
633 wtext->magic = NULL;
634 #endif
635 break;
637 default:
638 break;
641 if( handled )
642 textRefresh(wtext);
643 else
644 WMHandleEvent(event);
646 return;
650 static void
651 handleExpose(WObjDescriptor *desc, XEvent *event)
653 wTextPaint(desc->parent);