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))
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);
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 );
67
static void blink(void *data);
70
/********************************************************************
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 *
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
********************************************************************/
85
handleKeyPress( WTextInput *wtext, XKeyEvent *event )
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 */
101
/* Take care of the cursor keys.. ignore up and down
103
else if( IsCursorKey(ksym) )
105
int length = wtext->text.length;
110
wtext->text.endPos = 0;
113
wtext->text.endPos--;
116
wtext->text.endPos++;
119
wtext->text.endPos = length;
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;
136
/* Ignore these keys: */
138
wtext->canceled = True;
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, "" );
154
/* delete before cursor */
155
if( (wtext->text.endPos == wtext->text.startPos) &&
156
(wtext->text.startPos > 0) )
157
wtext->text.startPos--;
158
textInsert( wtext, "" );
161
if (count==1 && !iscntrl(buffer[0])) {
163
textInsert( wtext, buffer);
172
/********************************************************************
174
* given X coord, return position in array *
175
********************************************************************/
177
textXtoPos( WTextInput *wtext, int x )
182
for( pos=0; wtext->text.txt[pos] != '\0'; pos++ )
187
x -= WMWidthOfString( wtext->font, &(wtext->text.txt[pos]), 1 );
193
/********************************************************************
195
* create an instance of a text class *
199
* Global: dpy - the display *
200
********************************************************************/
202
wTextCreate( WCoreWindow *core, int x, int y, int width, int height )
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;
229
gcv.foreground = core->screen_ptr->black_pixel;
230
gcv.background = core->screen_ptr->white_pixel;
232
gcv.function = GXcopy;
234
wtext->gc = XCreateGC( dpy, wtext->core->window,
235
(GCForeground|GCBackground|
236
GCFunction|GCLineWidth),
239
/* set up the regular context */
240
gcv.foreground = core->screen_ptr->black_pixel;
241
gcv.background = core->screen_ptr->white_pixel;
243
gcv.function = GXcopy;
245
wtext->regGC = XCreateGC( dpy, wtext->core->window,
246
(GCForeground|GCBackground|
247
GCFunction|GCLineWidth),
250
/* set up the inverted context */
251
gcv.function = GXcopyInverted;
253
wtext->invGC = XCreateGC( dpy, wtext->core->window,
254
(GCForeground|GCBackground|
255
GCFunction|GCLineWidth),
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");
277
/********************************************************************
280
* Args: wtext - the text field *
282
* Global: dpy - the display *
283
********************************************************************/
285
wTextDestroy( WTextInput *wtext )
287
ENTER("wTextDestroy")
291
wDeleteTimerHandler(wtext->magic);
294
XFreeGC( dpy, wtext->gc );
295
XFreeGC( dpy, wtext->regGC );
296
XFreeGC( dpy, wtext->invGC );
297
wfree( wtext->text.txt );
298
wCoreDestroy( wtext->core );
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
/********************************************************************
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 *
321
* Args: wtext - the text field *
323
* Global: dpy - the display *
324
********************************************************************/
326
textRefresh(WTextInput *wtext)
328
WScreen *scr = wtext->core->screen_ptr;
329
char *ptr = wtext->text.txt;
332
/* x1,y1 is the upper left corner of the text box */
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,
349
/* Draw the selected text */
350
if (wtext->text.startPos != wtext->text.endPos) {
352
/* we need sp < ep */
353
if (wtext->text.startPos > wtext->text.endPos) {
354
sp = wtext->text.endPos;
355
ep = wtext->text.startPos;
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,
378
/* And draw a quick little line for the cursor position */
379
x1 = WMWidthOfString(wtext->font, wtext->text.txt, wtext->text.endPos)
381
XDrawLine(dpy, wtext->core->window, wtext->regGC, x1, 2, x1,
382
wtext->core->height - 3);
386
/********************************************************************
389
* Args: wtext - the text field *
391
* Global: dpy - the display *
392
********************************************************************/
394
wTextPaint( WTextInput *wtext )
399
textRefresh( wtext );
402
XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
403
wtext->core->width-1, wtext->core->height-1);
409
/********************************************************************
411
* return the string in the text field wText. DO NOT FREE THE *
414
* Args: wtext - the text field *
415
* Return: the text in the text field (NULL terminated) *
417
********************************************************************/
419
wTextGetText( WTextInput *wtext )
421
if (!wtext->canceled)
422
return wtext->text.txt;
427
/********************************************************************
429
* Put the string txt in the text field wText. The text field *
430
* needs to be explicitly refreshed after wTextPutText by calling *
432
* The string txt is copied *
434
* Args: wtext - the text field *
435
* txt - the new text string... freed by the text field! *
438
********************************************************************/
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
/********************************************************************
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! *
466
********************************************************************/
468
textInsert( WTextInput *wtext, char *txt )
471
int newLen, txtLen, i,j;
474
/* we need sp < ep */
475
if( wtext->text.startPos > wtext->text.endPos )
477
sp = wtext->text.endPos;
478
ep = wtext->text.startPos;
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++ )
499
/* copy old text after ep */
500
for( j=ep; j<wtext->text.length; j++,i++ )
501
newTxt[i] = wtext->text.txt[j];
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
/********************************************************************
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 *
528
********************************************************************/
530
wTextSelect( WTextInput *wtext, int start, int end )
533
wtext->text.endPos = wtext->text.length;
535
wtext->text.endPos = end;
536
wtext->text.startPos = start;
544
WTextInput *wtext = (WTextInput*)data;
547
/* And draw a quick little line for the cursor position */
548
if (wtext->blink_on) {
555
x = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos )
557
XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3);
560
wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data);
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 *
571
********************************************************************/
573
textEventHandler( WObjDescriptor *desc, XEvent *event )
575
WTextInput *wtext = desc->parent;
576
int handled = False; /* has the event been handled */
578
switch( event->type )
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");
588
wtext->text.endPos = textXtoPos( wtext, event->xmotion.x );
593
PDEBUG("ButtonPress");
595
wtext->text.startPos = textXtoPos( wtext, event->xbutton.x );
596
wtext->text.endPos = wtext->text.startPos;
600
PDEBUG("ButtonRelease");
602
wtext->text.endPos = textXtoPos( wtext, event->xbutton.x );
607
handled = handleKeyPress( wtext, &event->xkey );
611
PDEBUG("EnterNotify");
616
wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext);
617
wtext->blink_on = !wtext->blink_on;
625
PDEBUG("LeaveNotify");
632
wDeleteTimerHandler(wtext->magic);
644
WMHandleEvent(event);
651
handleExpose(WObjDescriptor *desc, XEvent *event)
653
wTextPaint(desc->parent);