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>
34 #include "WindowMaker.h"
39 /* X11R5 don't have this */
40 #ifndef IsPrivateKeypadKey
41 #define IsPrivateKeypadKey(keysym) \
42 (((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 PDEBUG(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 ********************************************************************/
83 static int handleKeyPress(WTextInput
* wtext
, XKeyEvent
* event
)
89 count
= XLookupString(event
, buffer
, 32, &ksym
, NULL
);
91 /* Ignore these keys: */
92 if (IsFunctionKey(ksym
) || IsKeypadKey(ksym
) ||
93 IsMiscFunctionKey(ksym
) || IsPFKey(ksym
) || IsPrivateKeypadKey(ksym
))
94 /* If we don't handle it, make sure it isn't a key that
95 * the window manager needs to see */
98 /* Take care of the cursor keys.. ignore up and down
100 else if (IsCursorKey(ksym
)) {
101 int length
= wtext
->text
.length
;
105 wtext
->text
.endPos
= 0;
108 wtext
->text
.endPos
--;
111 wtext
->text
.endPos
++;
114 wtext
->text
.endPos
= length
;
119 /* make sure that the startPos and endPos have values
120 * that make sense (ie the are in [0..length] ) */
121 if (wtext
->text
.endPos
< 0)
122 wtext
->text
.endPos
= 0;
123 if (wtext
->text
.endPos
> length
)
124 wtext
->text
.endPos
= length
;
125 wtext
->text
.startPos
= wtext
->text
.endPos
;
128 /* Ignore these keys: */
130 wtext
->canceled
= True
;
139 /* delete after cursor */
140 if ((wtext
->text
.endPos
== wtext
->text
.startPos
) &&
141 (wtext
->text
.endPos
< wtext
->text
.length
))
142 wtext
->text
.endPos
++;
143 textInsert(wtext
, "");
146 /* delete before cursor */
147 if ((wtext
->text
.endPos
== wtext
->text
.startPos
) && (wtext
->text
.startPos
> 0))
148 wtext
->text
.startPos
--;
149 textInsert(wtext
, "");
152 if (count
== 1 && !iscntrl(buffer
[0])) {
154 textInsert(wtext
, buffer
);
161 /********************************************************************
163 * given X coord, return position in array *
164 ********************************************************************/
165 static int textXtoPos(WTextInput
* wtext
, int x
)
170 for (pos
= 0; wtext
->text
.txt
[pos
] != '\0'; pos
++) {
174 x
-= WMWidthOfString(wtext
->font
, &(wtext
->text
.txt
[pos
]), 1);
180 /********************************************************************
182 * create an instance of a text class *
186 * Global: dpy - the display *
187 ********************************************************************/
188 WTextInput
*wTextCreate(WCoreWindow
* core
, int x
, int y
, int width
, int height
)
192 ENTER("wTextCreate");
194 wtext
= wmalloc(sizeof(WTextInput
));
195 wtext
->core
= wCoreCreate(core
, x
, y
, width
, height
);
196 wtext
->core
->descriptor
.handle_anything
= &textEventHandler
;
197 wtext
->core
->descriptor
.handle_expose
= &handleExpose
;
198 wtext
->core
->descriptor
.parent_type
= WCLASS_TEXT_INPUT
;
199 wtext
->core
->descriptor
.parent
= wtext
;
201 wtext
->font
= core
->screen_ptr
->menu_entry_font
;
203 XDefineCursor(dpy
, wtext
->core
->window
, wCursor
[WCUR_TEXT
]);
205 /* setup the text: */
206 wtext
->text
.txt
= (char *)wmalloc(sizeof(char));
207 wtext
->text
.txt
[0] = '\0';
208 wtext
->text
.length
= 0;
209 wtext
->text
.startPos
= 0;
210 wtext
->text
.endPos
= 0;
215 gcv
.foreground
= core
->screen_ptr
->black_pixel
;
216 gcv
.background
= core
->screen_ptr
->white_pixel
;
218 gcv
.function
= GXcopy
;
220 wtext
->gc
= XCreateGC(dpy
, wtext
->core
->window
,
221 (GCForeground
| GCBackground
| GCFunction
| GCLineWidth
), &gcv
);
223 /* set up the regular context */
224 gcv
.foreground
= core
->screen_ptr
->black_pixel
;
225 gcv
.background
= core
->screen_ptr
->white_pixel
;
227 gcv
.function
= GXcopy
;
229 wtext
->regGC
= XCreateGC(dpy
, wtext
->core
->window
,
230 (GCForeground
| GCBackground
| GCFunction
| GCLineWidth
), &gcv
);
232 /* set up the inverted context */
233 gcv
.function
= GXcopyInverted
;
235 wtext
->invGC
= XCreateGC(dpy
, wtext
->core
->window
,
236 (GCForeground
| GCBackground
| GCFunction
| GCLineWidth
), &gcv
);
238 /* and set the background! */
239 XSetWindowBackground(dpy
, wtext
->core
->window
, gcv
.background
);
242 /* Figure out the y-offset... */
243 wtext
->yOffset
= (height
- WMFontHeight(wtext
->font
)) / 2;
244 wtext
->xOffset
= wtext
->yOffset
;
246 wtext
->canceled
= False
;
247 wtext
->done
= False
; /* becomes True when the user *
248 * hits "Return" key */
250 XMapRaised(dpy
, wtext
->core
->window
);
252 LEAVE("wTextCreate");
257 /********************************************************************
260 * Args: wtext - the text field *
262 * Global: dpy - the display *
263 ********************************************************************/
264 void wTextDestroy(WTextInput
* wtext
)
266 ENTER("wTextDestroy")
269 wDeleteTimerHandler(wtext
->magic
);
272 XFreeGC(dpy
, wtext
->gc
);
273 XFreeGC(dpy
, wtext
->regGC
);
274 XFreeGC(dpy
, wtext
->invGC
);
275 wfree(wtext
->text
.txt
);
276 wCoreDestroy(wtext
->core
);
279 LEAVE("wTextDestroy");
282 /* The text-field consists of a frame drawn around the outside,
283 * and a textbox inside. The space between the frame and the
284 * text-box is the xOffset,yOffset. When the text needs refreshing,
285 * we only have to redraw the part inside the text-box, and we can
286 * leave the frame. If we get an expose event, or for some reason
287 * need to redraw the frame, wTextPaint will redraw the frame, and
288 * then call wTextRefresh to redraw the text-box */
290 /********************************************************************
292 * Redraw the text field. Call this after messing with the text *
293 * field. wTextRefresh re-draws the inside of the text field. If *
294 * the frame-area of the text-field needs redrawing, call *
297 * Args: wtext - the text field *
299 * Global: dpy - the display *
300 ********************************************************************/
301 static void textRefresh(WTextInput
* wtext
)
303 WScreen
*scr
= wtext
->core
->screen_ptr
;
304 char *ptr
= wtext
->text
.txt
;
307 /* x1,y1 is the upper left corner of the text box */
310 /* x2,y2 is the lower right corner of the text box */
311 x2
= wtext
->core
->width
- wtext
->xOffset
;
312 y2
= wtext
->core
->height
- wtext
->yOffset
;
314 /* Fill in the text field. Use the invGC to draw the rectangle,
315 * becuase then it will be the background color */
316 XFillRectangle(dpy
, wtext
->core
->window
, wtext
->invGC
, x1
, y1
, x2
- x1
, y2
- y1
);
318 /* Draw the text normally */
319 WMDrawImageString(scr
->wmscreen
, wtext
->core
->window
,
320 scr
->black
, scr
->white
, wtext
->font
, x1
, y1
, ptr
, wtext
->text
.length
);
322 /* Draw the selected text */
323 if (wtext
->text
.startPos
!= wtext
->text
.endPos
) {
325 /* we need sp < ep */
326 if (wtext
->text
.startPos
> wtext
->text
.endPos
) {
327 sp
= wtext
->text
.endPos
;
328 ep
= wtext
->text
.startPos
;
330 sp
= wtext
->text
.startPos
;
331 ep
= wtext
->text
.endPos
;
334 /* x1,y1 is now the upper-left of the selected area */
335 x1
+= WMWidthOfString(wtext
->font
, ptr
, sp
);
336 /* and x2,y2 is the lower-right of the selected area */
337 ptr
+= sp
* sizeof(char);
338 x2
= x1
+ WMWidthOfString(wtext
->font
, ptr
, (ep
- sp
));
339 /* Fill in the area where the selected text will go: *
340 * use the regGC to draw the rectangle, becuase then it *
341 * will be the color of the non-selected text */
342 XFillRectangle(dpy
, wtext
->core
->window
, wtext
->regGC
, x1
, y1
, x2
- x1
, y2
- y1
);
344 /* Draw the selected text. Inverse bg and fg colors for selection */
345 WMDrawImageString(scr
->wmscreen
, wtext
->core
->window
,
346 scr
->white
, scr
->black
, wtext
->font
, x1
, y1
, ptr
, (ep
- sp
));
349 /* And draw a quick little line for the cursor position */
350 x1
= WMWidthOfString(wtext
->font
, wtext
->text
.txt
, wtext
->text
.endPos
)
352 XDrawLine(dpy
, wtext
->core
->window
, wtext
->regGC
, x1
, 2, x1
, wtext
->core
->height
- 3);
355 /********************************************************************
358 * Args: wtext - the text field *
360 * Global: dpy - the display *
361 ********************************************************************/
362 void wTextPaint(WTextInput
* wtext
)
370 XDrawRectangle(dpy
, wtext
->core
->window
, wtext
->gc
, 0, 0, wtext
->core
->width
- 1, wtext
->core
->height
- 1);
375 /********************************************************************
377 * return the string in the text field wText. DO NOT FREE THE *
380 * Args: wtext - the text field *
381 * Return: the text in the text field (NULL terminated) *
383 ********************************************************************/
384 char *wTextGetText(WTextInput
* wtext
)
386 if (!wtext
->canceled
)
387 return wtext
->text
.txt
;
392 /********************************************************************
394 * Put the string txt in the text field wText. The text field *
395 * needs to be explicitly refreshed after wTextPutText by calling *
397 * The string txt is copied *
399 * Args: wtext - the text field *
400 * txt - the new text string... freed by the text field! *
403 ********************************************************************/
404 void wTextPutText(WTextInput
* wtext
, char *txt
)
406 int length
= strlen(txt
);
408 /* no memory leaks! free the old txt */
409 if (wtext
->text
.txt
!= NULL
)
410 wfree(wtext
->text
.txt
);
412 wtext
->text
.txt
= (char *)wmalloc((length
+ 1) * sizeof(char));
413 strcpy(wtext
->text
.txt
, txt
);
414 wtext
->text
.length
= length
;
415 /* By default No text is selected, and the cursor is at the end */
416 wtext
->text
.startPos
= length
;
417 wtext
->text
.endPos
= length
;
420 /********************************************************************
422 * Insert some text at the cursor. (if startPos != endPos, *
423 * replace the selected text, otherwise insert) *
424 * The string txt is copied. *
426 * Args: wText - the text field *
427 * txt - the new text string... freed by the text field! *
430 ********************************************************************/
431 static void textInsert(WTextInput
* wtext
, char *txt
)
434 int newLen
, txtLen
, i
, j
;
437 /* we need sp < ep */
438 if (wtext
->text
.startPos
> wtext
->text
.endPos
) {
439 sp
= wtext
->text
.endPos
;
440 ep
= wtext
->text
.startPos
;
442 sp
= wtext
->text
.startPos
;
443 ep
= wtext
->text
.endPos
;
446 txtLen
= strlen(txt
);
447 newLen
= wtext
->text
.length
+ txtLen
- (ep
- sp
) + 1;
449 newTxt
= (char *)malloc(newLen
* sizeof(char));
451 /* copy the old text up to sp */
452 for (i
= 0; i
< sp
; i
++)
453 newTxt
[i
] = wtext
->text
.txt
[i
];
455 /* insert new text */
456 for (j
= 0; j
< txtLen
; j
++, i
++)
459 /* copy old text after ep */
460 for (j
= ep
; j
< wtext
->text
.length
; j
++, i
++)
461 newTxt
[i
] = wtext
->text
.txt
[j
];
465 /* By default No text is selected, and the cursor is at the end
466 * of inserted text */
467 wtext
->text
.startPos
= sp
+ txtLen
;
468 wtext
->text
.endPos
= sp
+ txtLen
;
470 wfree(wtext
->text
.txt
);
471 wtext
->text
.txt
= newTxt
;
472 wtext
->text
.length
= newLen
- 1;
475 /********************************************************************
477 * Select some text. If start == end, then the cursor is moved *
478 * to that position. If end == -1, then the text from start to *
479 * the end of the text entered in the text field is selected. *
480 * The text field is not automatically re-drawn! You must call *
481 * wTextRefresh to re-draw the text field. *
483 * Args: wtext - the text field *
484 * start - the beginning of the selected text *
485 * end - the end of the selected text *
488 ********************************************************************/
489 void wTextSelect(WTextInput
* wtext
, int start
, int end
)
492 wtext
->text
.endPos
= wtext
->text
.length
;
494 wtext
->text
.endPos
= end
;
495 wtext
->text
.startPos
= start
;
499 static void blink(void *data
)
502 WTextInput
*wtext
= (WTextInput
*) data
;
505 /* And draw a quick little line for the cursor position */
506 if (wtext
->blink_on
) {
513 x
= WMWidthOfString(wtext
->font
, wtext
->text
.txt
, wtext
->text
.endPos
)
515 XDrawLine(dpy
, wtext
->core
->window
, gc
, x
, 2, x
, wtext
->core
->height
- 3);
518 wtext
->magic
= wAddTimerHandler(CURSOR_BLINK_RATE
, blink
, data
);
522 /********************************************************************
523 * textEventHandler -- handles and dispatches all the events that *
524 * the text field class supports *
526 * Args: desc - all we need to know about this object *
529 ********************************************************************/
530 static void textEventHandler(WObjDescriptor
* desc
, XEvent
* event
)
532 WTextInput
*wtext
= desc
->parent
;
533 int handled
= False
; /* has the event been handled */
535 switch (event
->type
) {
537 /* If the button isn't down, we don't care about the
538 * event, but otherwise we want to adjust the selected
539 * text so we can wTextRefresh() */
540 if (event
->xmotion
.state
& (Button1Mask
| Button3Mask
| Button2Mask
)) {
541 PDEBUG("MotionNotify");
543 wtext
->text
.endPos
= textXtoPos(wtext
, event
->xmotion
.x
);
548 PDEBUG("ButtonPress");
550 wtext
->text
.startPos
= textXtoPos(wtext
, event
->xbutton
.x
);
551 wtext
->text
.endPos
= wtext
->text
.startPos
;
555 PDEBUG("ButtonRelease");
557 wtext
->text
.endPos
= textXtoPos(wtext
, event
->xbutton
.x
);
562 handled
= handleKeyPress(wtext
, &event
->xkey
);
566 PDEBUG("EnterNotify");
570 wtext
->magic
= wAddTimerHandler(CURSOR_BLINK_RATE
, blink
, wtext
);
571 wtext
->blink_on
= !wtext
->blink_on
;
579 PDEBUG("LeaveNotify");
586 wDeleteTimerHandler(wtext
->magic
);
598 WMHandleEvent(event
);
603 static void handleExpose(WObjDescriptor
* desc
, XEvent
* event
)
605 wTextPaint(desc
->parent
);