/******************************************************************** * 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 #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 #ifdef DEBUG # define ENTER(X) fprintf(stderr,"Entering: %s()\n", X); # define LEAVE(X) fprintf(stderr,"Leaving: %s()\n", X); # define PDEBUG(X) fprintf(stderr,"debug: %s()\n", X); #else # define ENTER(X) # define LEAVE(X) # define PDEBUG(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; i < sp; i++) newTxt[i] = wtext->text.txt[i]; /* insert new text */ for (j = 0; j < txtLen; j++, i++) newTxt[i] = txt[j]; /* copy old text after ep */ for (j = ep; j < wtext->text.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)) { PDEBUG("MotionNotify"); handled = True; wtext->text.endPos = textXtoPos(wtext, event->xmotion.x); } break; case ButtonPress: PDEBUG("ButtonPress"); handled = True; wtext->text.startPos = textXtoPos(wtext, event->xbutton.x); wtext->text.endPos = wtext->text.startPos; break; case ButtonRelease: PDEBUG("ButtonRelease"); handled = True; wtext->text.endPos = textXtoPos(wtext, event->xbutton.x); break; case KeyPress: PDEBUG("KeyPress"); handled = handleKeyPress(wtext, &event->xkey); break; case EnterNotify: PDEBUG("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: PDEBUG("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); }