/********************************************************************\ * text.c -- a basic text field * * Copyright (C) 1997 Robin D. Clark * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, write to the Free Software * * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * * * Author: Rob Clark * * Internet: rclark@cs.hmc.edu * * Address: 609 8th Street * * Huntington Beach, CA 92648-4632 * \********************************************************************/ #include "wconfig.h" #include #include #include #include #include #include "WindowMaker.h" #include "funcs.h" #include "text.h" #include "actions.h" /* X11R5 don't have this */ #ifndef IsPrivateKeypadKey #define IsPrivateKeypadKey(keysym) \ (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF)) #endif #if 0 # define ENTER(X) fprintf(stderr,"Entering: %s()\n", X); # define LEAVE(X) fprintf(stderr,"Leaving: %s()\n", X); # define DEBUG(X) fprintf(stderr,"debug: %s()\n", X); #else # define ENTER(X) # define LEAVE(X) # define DEBUG(X) #endif extern Cursor wCursor[WCUR_LAST]; /********************************************************************\ * The event handler for the text-field: * \********************************************************************/ static void textEventHandler( WObjDescriptor *desc, XEvent *event ); static void handleExpose( WObjDescriptor *desc, XEvent *event ); static void textInsert( WTextInput *wtext, char *txt ); #if 0 static void blink(void *data); #endif /********************************************************************\ * handleKeyPress * * handle cursor keys, regular keys, etc. Inserts characters in * * text field, at cursor position, if it is a "Normal" key. If * * ksym is the delete key, backspace key, etc., the appropriate * * action is performed on the text in the text field. Does not * * refresh the text field * * * * Args: wText - the text field * * ksym - the key that was pressed * * Return: True, unless the ksym is ignored * * Global: modifier - the bitfield that keeps track of the modifier * * keys that are down * \********************************************************************/ static int handleKeyPress( WTextInput *wtext, XKeyEvent *event ) { KeySym ksym; char buffer[32]; int count; count = XLookupString(event, buffer, 32, &ksym, NULL); /* Ignore these keys: */ if( IsFunctionKey(ksym) || IsKeypadKey(ksym) || IsMiscFunctionKey(ksym) || IsPFKey(ksym) || IsPrivateKeypadKey(ksym) ) /* If we don't handle it, make sure it isn't a key that * the window manager needs to see */ return False; /* Take care of the cursor keys.. ignore up and down * cursor keys */ else if( IsCursorKey(ksym) ) { int length = wtext->text.length; switch(ksym) { case XK_Home: case XK_Begin: wtext->text.endPos = 0; break; case XK_Left: wtext->text.endPos--; break; case XK_Right: wtext->text.endPos++; break; case XK_End: wtext->text.endPos = length; break; default: return False; } /* make sure that the startPos and endPos have values * that make sense (ie the are in [0..length] ) */ if( wtext->text.endPos < 0 ) wtext->text.endPos = 0; if( wtext->text.endPos > length ) wtext->text.endPos = length; wtext->text.startPos = wtext->text.endPos; } else { switch(ksym) { /* Ignore these keys: */ case XK_Escape: wtext->canceled = True; case XK_Return: wtext->done = True; break; case XK_Tab: case XK_Num_Lock: break; case XK_Delete: /* delete after cursor */ if( (wtext->text.endPos == wtext->text.startPos) && (wtext->text.endPos < wtext->text.length) ) wtext->text.endPos++; textInsert( wtext, "" ); break; case XK_BackSpace: /* delete before cursor */ if( (wtext->text.endPos == wtext->text.startPos) && (wtext->text.startPos > 0) ) wtext->text.startPos--; textInsert( wtext, "" ); break; default: if (count==1 && !iscntrl(buffer[0])) { buffer[count] = 0; textInsert( wtext, buffer); } } } return True; } /********************************************************************\ * textXYtoPos * * given X coord, return position in array * \********************************************************************/ static int textXtoPos( WTextInput *wtext, int x ) { int pos; x -= wtext->xOffset; for( pos=0; wtext->text.txt[pos] != '\0'; pos++ ) { if( x < 0 ) break; else x -= WMWidthOfString( wtext->font, &(wtext->text.txt[pos]), 1 ); } return pos; } /********************************************************************\ * wTextCreate * * create an instance of a text class * * * * Args: * * Return: * * Global: dpy - the display * \********************************************************************/ WTextInput * wTextCreate( WCoreWindow *core, int x, int y, int width, int height ) { WTextInput *wtext; ENTER("wTextCreate"); wtext = wmalloc(sizeof(WTextInput)); wtext->core = wCoreCreate( core, x, y, width, height ); wtext->core->descriptor.handle_anything = &textEventHandler; wtext->core->descriptor.handle_expose = &handleExpose; wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT; wtext->core->descriptor.parent = wtext; wtext->font = core->screen_ptr->menu_entry_font; XDefineCursor( dpy, wtext->core->window, wCursor[WCUR_TEXT] ); /* setup the text: */ wtext->text.txt = (char *)wmalloc(sizeof(char)); wtext->text.txt[0] = '\0'; wtext->text.length = 0; wtext->text.startPos = 0; wtext->text.endPos = 0; { XGCValues gcv; gcv.foreground = core->screen_ptr->black_pixel; gcv.background = core->screen_ptr->white_pixel; gcv.line_width = 1; gcv.function = GXcopy; wtext->gc = XCreateGC( dpy, wtext->core->window, (GCForeground|GCBackground| GCFunction|GCLineWidth), &gcv ); /* set up the regular context */ gcv.foreground = core->screen_ptr->black_pixel; gcv.background = core->screen_ptr->white_pixel; gcv.line_width = 1; gcv.function = GXcopy; wtext->regGC = XCreateGC( dpy, wtext->core->window, (GCForeground|GCBackground| GCFunction|GCLineWidth), &gcv ); /* set up the inverted context */ gcv.function = GXcopyInverted; wtext->invGC = XCreateGC( dpy, wtext->core->window, (GCForeground|GCBackground| GCFunction|GCLineWidth), &gcv ); /* and set the background! */ XSetWindowBackground( dpy, wtext->core->window, gcv.background ); } /* Figure out the y-offset... */ wtext->yOffset = (height - WMFontHeight(wtext->font))/2; wtext->xOffset = wtext->yOffset; wtext->canceled = False; wtext->done = False; /* becomes True when the user * * hits "Return" key */ XMapRaised(dpy, wtext->core->window); LEAVE("wTextCreate"); return wtext; } /********************************************************************\ * wTextDestroy * * * * Args: wtext - the text field * * Return: * * Global: dpy - the display * \********************************************************************/ void wTextDestroy( WTextInput *wtext ) { ENTER("wTextDestroy") #if 0 if (wtext->magic) wDeleteTimerHandler(wtext->magic); wtext->magic = NULL; #endif XFreeGC( dpy, wtext->gc ); XFreeGC( dpy, wtext->regGC ); XFreeGC( dpy, wtext->invGC ); wfree( wtext->text.txt ); wCoreDestroy( wtext->core ); wfree( wtext ); LEAVE("wTextDestroy"); } /* The text-field consists of a frame drawn around the outside, * and a textbox inside. The space between the frame and the * text-box is the xOffset,yOffset. When the text needs refreshing, * we only have to redraw the part inside the text-box, and we can * leave the frame. If we get an expose event, or for some reason * need to redraw the frame, wTextPaint will redraw the frame, and * then call wTextRefresh to redraw the text-box */ /********************************************************************\ * textRefresh * * Redraw the text field. Call this after messing with the text * * field. wTextRefresh re-draws the inside of the text field. If * * the frame-area of the text-field needs redrawing, call * * wTextPaint() * * * * Args: wtext - the text field * * Return: none * * Global: dpy - the display * \********************************************************************/ static void textRefresh(WTextInput *wtext) { WScreen *scr = wtext->core->screen_ptr; char *ptr = wtext->text.txt; int x1,x2,y1,y2; /* x1,y1 is the upper left corner of the text box */ x1 = wtext->xOffset; y1 = wtext->yOffset; /* x2,y2 is the lower right corner of the text box */ x2 = wtext->core->width - wtext->xOffset; y2 = wtext->core->height - wtext->yOffset; /* Fill in the text field. Use the invGC to draw the rectangle, * becuase then it will be the background color */ XFillRectangle(dpy, wtext->core->window, wtext->invGC, x1, y1, x2-x1, y2-y1); /* Draw the text normally */ WMDrawImageString(scr->wmscreen, wtext->core->window, scr->black, scr->white, wtext->font, x1, y1, ptr, wtext->text.length); /* Draw the selected text */ if (wtext->text.startPos != wtext->text.endPos) { int sp,ep; /* we need sp < ep */ if (wtext->text.startPos > wtext->text.endPos) { sp = wtext->text.endPos; ep = wtext->text.startPos; } else { sp = wtext->text.startPos; ep = wtext->text.endPos; } /* x1,y1 is now the upper-left of the selected area */ x1 += WMWidthOfString(wtext->font, ptr, sp); /* and x2,y2 is the lower-right of the selected area */ ptr += sp * sizeof(char); x2 = x1 + WMWidthOfString(wtext->font, ptr, (ep - sp)); /* Fill in the area where the selected text will go: * * use the regGC to draw the rectangle, becuase then it * * will be the color of the non-selected text */ XFillRectangle(dpy, wtext->core->window, wtext->regGC, x1, y1, x2-x1, y2-y1); /* Draw the selected text. Inverse bg and fg colors for selection */ WMDrawImageString(scr->wmscreen, wtext->core->window, scr->white, scr->black, wtext->font, x1, y1, ptr, (ep - sp)); } /* And draw a quick little line for the cursor position */ x1 = WMWidthOfString(wtext->font, wtext->text.txt, wtext->text.endPos) + wtext->xOffset; XDrawLine(dpy, wtext->core->window, wtext->regGC, x1, 2, x1, wtext->core->height - 3); } /********************************************************************\ * wTextPaint * * * * Args: wtext - the text field * * Return: * * Global: dpy - the display * \********************************************************************/ void wTextPaint( WTextInput *wtext ) { ENTER("wTextPaint"); /* refresh */ textRefresh( wtext ); /* Draw box */ XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0, wtext->core->width-1, wtext->core->height-1); LEAVE("wTextPaint"); } /********************************************************************\ * wTextGetText * * return the string in the text field wText. DO NOT FREE THE * * RETURNED STRING! * * * * Args: wtext - the text field * * Return: the text in the text field (NULL terminated) * * Global: * \********************************************************************/ char * wTextGetText( WTextInput *wtext ) { if (!wtext->canceled) return wtext->text.txt; else return NULL; } /********************************************************************\ * wTextPutText * * Put the string txt in the text field wText. The text field * * needs to be explicitly refreshed after wTextPutText by calling * * wTextRefresh(). * * The string txt is copied * * * * Args: wtext - the text field * * txt - the new text string... freed by the text field! * * Return: none * * Global: * \********************************************************************/ void wTextPutText( WTextInput *wtext, char *txt ) { int length = strlen(txt); /* no memory leaks! free the old txt */ if( wtext->text.txt != NULL ) wfree( wtext->text.txt ); wtext->text.txt = (char *)wmalloc((length+1)*sizeof(char)); strcpy(wtext->text.txt, txt ); wtext->text.length = length; /* By default No text is selected, and the cursor is at the end */ wtext->text.startPos = length; wtext->text.endPos = length; } /********************************************************************\ * textInsert * * Insert some text at the cursor. (if startPos != endPos, * * replace the selected text, otherwise insert) * * The string txt is copied. * * * * Args: wText - the text field * * txt - the new text string... freed by the text field! * * Return: none * * Global: * \********************************************************************/ static void textInsert( WTextInput *wtext, char *txt ) { char *newTxt; int newLen, txtLen, i,j; int sp,ep; /* we need sp < ep */ if( wtext->text.startPos > wtext->text.endPos ) { sp = wtext->text.endPos; ep = wtext->text.startPos; } else { sp = wtext->text.startPos; ep = wtext->text.endPos; } txtLen = strlen(txt); newLen = wtext->text.length + txtLen - (ep - sp) + 1; newTxt = (char *)malloc(newLen*sizeof(char)); /* copy the old text up to sp */ for( i=0; itext.txt[i]; /* insert new text */ for( j=0; jtext.length; j++,i++ ) newTxt[i] = wtext->text.txt[j]; newTxt[i] = '\0'; /* By default No text is selected, and the cursor is at the end * of inserted text */ wtext->text.startPos = sp+txtLen; wtext->text.endPos = sp+txtLen; wfree(wtext->text.txt); wtext->text.txt = newTxt; wtext->text.length = newLen-1; } /********************************************************************\ * wTextSelect * * Select some text. If start == end, then the cursor is moved * * to that position. If end == -1, then the text from start to * * the end of the text entered in the text field is selected. * * The text field is not automatically re-drawn! You must call * * wTextRefresh to re-draw the text field. * * * * Args: wtext - the text field * * start - the beginning of the selected text * * end - the end of the selected text * * Return: none * * Global: * \********************************************************************/ void wTextSelect( WTextInput *wtext, int start, int end ) { if( end == -1 ) wtext->text.endPos = wtext->text.length; else wtext->text.endPos = end; wtext->text.startPos = start; } #if 0 static void blink(void *data) { int x; WTextInput *wtext = (WTextInput*)data; GC gc; /* And draw a quick little line for the cursor position */ if (wtext->blink_on) { gc = wtext->regGC; wtext->blink_on = 0; } else { gc = wtext->invGC; wtext->blink_on = 1; } x = WMWidthOfString( wtext->font, wtext->text.txt, wtext->text.endPos ) + wtext->xOffset; XDrawLine( dpy, wtext->core->window, gc, x, 2, x, wtext->core->height-3); if (wtext->blinking) wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, data); } #endif /********************************************************************\ * textEventHandler -- handles and dispatches all the events that * * the text field class supports * * * * Args: desc - all we need to know about this object * * Return: none * * Global: * \********************************************************************/ static void textEventHandler( WObjDescriptor *desc, XEvent *event ) { WTextInput *wtext = desc->parent; int handled = False; /* has the event been handled */ switch( event->type ) { case MotionNotify: /* If the button isn't down, we don't care about the * event, but otherwise we want to adjust the selected * text so we can wTextRefresh() */ if( event->xmotion.state & (Button1Mask|Button3Mask|Button2Mask) ) { DEBUG("MotionNotify"); handled = True; wtext->text.endPos = textXtoPos( wtext, event->xmotion.x ); } break; case ButtonPress: DEBUG("ButtonPress"); handled = True; wtext->text.startPos = textXtoPos( wtext, event->xbutton.x ); wtext->text.endPos = wtext->text.startPos; break; case ButtonRelease: DEBUG("ButtonRelease"); handled = True; wtext->text.endPos = textXtoPos( wtext, event->xbutton.x ); break; case KeyPress: DEBUG("KeyPress"); handled = handleKeyPress( wtext, &event->xkey ); break; case EnterNotify: DEBUG("EnterNotify"); handled = True; #if 0 if (!wtext->magic) { wtext->magic = wAddTimerHandler(CURSOR_BLINK_RATE, blink, wtext); wtext->blink_on = !wtext->blink_on; blink(wtext); wtext->blinking = 1; } #endif break; case LeaveNotify: DEBUG("LeaveNotify"); handled = True; #if 0 wtext->blinking = 0; if (wtext->blink_on) blink(wtext); if (wtext->magic) wDeleteTimerHandler(wtext->magic); wtext->magic = NULL; #endif break; default: break; } if( handled ) textRefresh(wtext); else WMHandleEvent(event); return; } static void handleExpose(WObjDescriptor *desc, XEvent *event) { wTextPaint(desc->parent); }