1 /********************************************************************\
2 * text.c -- a basic text field *
3 * Copyright (C) 1997 Robin D. Clark *
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. *
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. *
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. *
20 * Internet: rclark@cs.hmc.edu *
21 * Address: 609 8th Street *
22 * Huntington Beach, CA 92648-4632 *
23 \********************************************************************/
28 #include <X11/Xutil.h>
29 #include <X11/keysym.h>
33 #include "WindowMaker.h"
38 /* X11R5 don't have this */
39 #ifndef IsPrivateKeypadKey
40 #define IsPrivateKeypadKey(keysym) \
41 (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF))
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);
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
);
66 static void blink(void *data
);
69 /********************************************************************\
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 *
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 \********************************************************************/
84 handleKeyPress( WTextInput
*wtext
, XKeyEvent
*event
)
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 */
100 /* Take care of the cursor keys.. ignore up and down
102 else if( IsCursorKey(ksym
) )
104 int length
= wtext
->text
.length
;
109 wtext
->text
.endPos
= 0;
112 wtext
->text
.endPos
--;
115 wtext
->text
.endPos
++;
118 wtext
->text
.endPos
= length
;
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
;
135 /* Ignore these keys: */
137 wtext
->canceled
= True
;
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
, "" );
153 /* delete before cursor */
154 if( (wtext
->text
.endPos
== wtext
->text
.startPos
) &&
155 (wtext
->text
.startPos
> 0) )
156 wtext
->text
.startPos
--;
157 textInsert( wtext
, "" );
160 if (count
==1 && !iscntrl(buffer
[0])) {
162 textInsert( wtext
, buffer
);
171 /********************************************************************\
173 * given X coord, return position in array *
174 \********************************************************************/
176 textXtoPos( WTextInput
*wtext
, int x
)
181 for( pos
=0; wtext
->text
.txt
[pos
] != '\0'; pos
++ )
186 x
-= WMWidthOfString( wtext
->font
, &(wtext
->text
.txt
[pos
]), 1 );
192 /********************************************************************\
194 * create an instance of a text class *
198 * Global: dpy - the display *
199 \********************************************************************/
201 wTextCreate( WCoreWindow
*core
, int x
, int y
, int width
, int height
)
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;
228 gcv
.foreground
= core
->screen_ptr
->black_pixel
;
229 gcv
.background
= core
->screen_ptr
->white_pixel
;
231 gcv
.function
= GXcopy
;
233 wtext
->gc
= XCreateGC( dpy
, wtext
->core
->window
,
234 (GCForeground
|GCBackground
|
235 GCFunction
|GCLineWidth
),
238 /* set up the regular context */
239 gcv
.foreground
= core
->screen_ptr
->black_pixel
;
240 gcv
.background
= core
->screen_ptr
->white_pixel
;
242 gcv
.function
= GXcopy
;
244 wtext
->regGC
= XCreateGC( dpy
, wtext
->core
->window
,
245 (GCForeground
|GCBackground
|
246 GCFunction
|GCLineWidth
),
249 /* set up the inverted context */
250 gcv
.function
= GXcopyInverted
;
252 wtext
->invGC
= XCreateGC( dpy
, wtext
->core
->window
,
253 (GCForeground
|GCBackground
|
254 GCFunction
|GCLineWidth
),
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");
276 /********************************************************************\
279 * Args: wtext - the text field *
281 * Global: dpy - the display *
282 \********************************************************************/
284 wTextDestroy( WTextInput
*wtext
)
286 ENTER("wTextDestroy")
290 wDeleteTimerHandler(wtext
->magic
);
293 XFreeGC( dpy
, wtext
->gc
);
294 XFreeGC( dpy
, wtext
->regGC
);
295 XFreeGC( dpy
, wtext
->invGC
);
296 wfree( wtext
->text
.txt
);
297 wCoreDestroy( wtext
->core
);
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 /********************************************************************\
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 *
320 * Args: wtext - the text field *
322 * Global: dpy - the display *
323 \********************************************************************/
325 textRefresh( WTextInput
*wtext
)
328 char *ptr
= wtext
->text
.txt
;
329 WMColor
*black
, *white
;
331 /* x1,y1 is the upper left corner of the text box */
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
)
353 /* we need sp < ep */
354 if( wtext
->text
.startPos
> wtext
->text
.endPos
)
356 sp
= wtext
->text
.endPos
;
357 ep
= wtext
->text
.startPos
;
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
)
388 XDrawLine( dpy
, wtext
->core
->window
, wtext
->regGC
, x1
, 2, x1
,
389 wtext
->core
->height
- 3 );
393 /********************************************************************\
396 * Args: wtext - the text field *
398 * Global: dpy - the display *
399 \********************************************************************/
401 wTextPaint( WTextInput
*wtext
)
406 textRefresh( wtext
);
409 XDrawRectangle(dpy
, wtext
->core
->window
, wtext
->gc
, 0, 0,
410 wtext
->core
->width
-1, wtext
->core
->height
-1);
416 /********************************************************************\
418 * return the string in the text field wText. DO NOT FREE THE *
421 * Args: wtext - the text field *
422 * Return: the text in the text field (NULL terminated) *
424 \********************************************************************/
426 wTextGetText( WTextInput
*wtext
)
428 if (!wtext
->canceled
)
429 return wtext
->text
.txt
;
434 /********************************************************************\
436 * Put the string txt in the text field wText. The text field *
437 * needs to be explicitly refreshed after wTextPutText by calling *
439 * The string txt is copied *
441 * Args: wtext - the text field *
442 * txt - the new text string... freed by the text field! *
445 \********************************************************************/
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 /********************************************************************\
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! *
473 \********************************************************************/
475 textInsert( WTextInput
*wtext
, char *txt
)
478 int newLen
, txtLen
, i
,j
;
481 /* we need sp < ep */
482 if( wtext
->text
.startPos
> wtext
->text
.endPos
)
484 sp
= wtext
->text
.endPos
;
485 ep
= wtext
->text
.startPos
;
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
++ )
506 /* copy old text after ep */
507 for( j
=ep
; j
<wtext
->text
.length
; j
++,i
++ )
508 newTxt
[i
] = wtext
->text
.txt
[j
];
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 /********************************************************************\
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 *
535 \********************************************************************/
537 wTextSelect( WTextInput
*wtext
, int start
, int end
)
540 wtext
->text
.endPos
= wtext
->text
.length
;
542 wtext
->text
.endPos
= end
;
543 wtext
->text
.startPos
= start
;
551 WTextInput
*wtext
= (WTextInput
*)data
;
554 /* And draw a quick little line for the cursor position */
555 if (wtext
->blink_on
) {
562 x
= WMWidthOfString( wtext
->font
, wtext
->text
.txt
, wtext
->text
.endPos
)
564 XDrawLine( dpy
, wtext
->core
->window
, gc
, x
, 2, x
, wtext
->core
->height
-3);
567 wtext
->magic
= wAddTimerHandler(CURSOR_BLINK_RATE
, blink
, data
);
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 *
578 \********************************************************************/
580 textEventHandler( WObjDescriptor
*desc
, XEvent
*event
)
582 WTextInput
*wtext
= desc
->parent
;
583 int handled
= False
; /* has the event been handled */
585 switch( event
->type
)
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");
595 wtext
->text
.endPos
= textXtoPos( wtext
, event
->xmotion
.x
);
600 DEBUG("ButtonPress");
602 wtext
->text
.startPos
= textXtoPos( wtext
, event
->xbutton
.x
);
603 wtext
->text
.endPos
= wtext
->text
.startPos
;
607 DEBUG("ButtonRelease");
609 wtext
->text
.endPos
= textXtoPos( wtext
, event
->xbutton
.x
);
614 handled
= handleKeyPress( wtext
, &event
->xkey
);
618 DEBUG("EnterNotify");
623 wtext
->magic
= wAddTimerHandler(CURSOR_BLINK_RATE
, blink
, wtext
);
624 wtext
->blink_on
= !wtext
->blink_on
;
632 DEBUG("LeaveNotify");
639 wDeleteTimerHandler(wtext
->magic
);
651 WMHandleEvent(event
);
658 handleExpose(WObjDescriptor
*desc
, XEvent
*event
)
660 wTextPaint(desc
->parent
);