Improve dockapp recognition
[wmaker-crm.git] / src / text.c
blob3fc10e98f7667e7fe4d67eb51a4a8a88c6f75525
1 /********************************************************************
2 * text.c -- a basic text field *
3 * Copyright (C) 1997 Robin D. Clark *
4 * *
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. *
9 * *
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. *
14 * *
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. *
18 * *
19 * Author: Rob Clark *
20 * Internet: rclark@cs.hmc.edu *
21 * Address: 609 8th Street *
22 * Huntington Beach, CA 92648-4632 *
23 ********************************************************************/
25 #include "wconfig.h"
27 #include <X11/Xlib.h>
28 #include <X11/Xutil.h>
29 #include <X11/keysym.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h>
34 #include "WindowMaker.h"
35 #include "funcs.h"
36 #include "text.h"
37 #include "actions.h"
39 /* X11R5 don't have this */
40 #ifndef IsPrivateKeypadKey
41 #define IsPrivateKeypadKey(keysym) \
42 (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF))
43 #endif
45 extern Cursor wCursor[WCUR_LAST];
47 /********************************************************************
48 * The event handler for the text-field: *
49 ********************************************************************/
50 static void textEventHandler(WObjDescriptor * desc, XEvent * event);
52 static void handleExpose(WObjDescriptor * desc, XEvent * event);
54 static void textInsert(WTextInput * wtext, char *txt);
56 /********************************************************************
57 * handleKeyPress *
58 * handle cursor keys, regular keys, etc. Inserts characters in *
59 * text field, at cursor position, if it is a "Normal" key. If *
60 * ksym is the delete key, backspace key, etc., the appropriate *
61 * action is performed on the text in the text field. Does not *
62 * refresh the text field *
63 * *
64 * Args: wText - the text field *
65 * ksym - the key that was pressed *
66 * Return: True, unless the ksym is ignored *
67 * Global: modifier - the bitfield that keeps track of the modifier *
68 * keys that are down *
69 ********************************************************************/
70 static int handleKeyPress(WTextInput * wtext, XKeyEvent * event)
72 KeySym ksym;
73 char buffer[32];
74 int count;
76 count = XLookupString(event, buffer, 32, &ksym, NULL);
78 /* Ignore these keys: */
79 if (IsFunctionKey(ksym) || IsKeypadKey(ksym) ||
80 IsMiscFunctionKey(ksym) || IsPFKey(ksym) || IsPrivateKeypadKey(ksym))
81 /* If we don't handle it, make sure it isn't a key that
82 * the window manager needs to see */
83 return False;
85 /* Take care of the cursor keys.. ignore up and down
86 * cursor keys */
87 else if (IsCursorKey(ksym)) {
88 int length = wtext->text.length;
89 switch (ksym) {
90 case XK_Home:
91 case XK_Begin:
92 wtext->text.endPos = 0;
93 break;
94 case XK_Left:
95 wtext->text.endPos--;
96 break;
97 case XK_Right:
98 wtext->text.endPos++;
99 break;
100 case XK_End:
101 wtext->text.endPos = length;
102 break;
103 default:
104 return False;
106 /* make sure that the startPos and endPos have values
107 * that make sense (ie the are in [0..length] ) */
108 if (wtext->text.endPos < 0)
109 wtext->text.endPos = 0;
110 if (wtext->text.endPos > length)
111 wtext->text.endPos = length;
112 wtext->text.startPos = wtext->text.endPos;
113 } else {
114 switch (ksym) {
115 /* Ignore these keys: */
116 case XK_Escape:
117 wtext->canceled = True;
118 case XK_Return:
119 wtext->done = True;
120 break;
121 case XK_Tab:
122 case XK_Num_Lock:
123 break;
125 case XK_Delete:
126 /* delete after cursor */
127 if ((wtext->text.endPos == wtext->text.startPos) &&
128 (wtext->text.endPos < wtext->text.length))
129 wtext->text.endPos++;
130 textInsert(wtext, "");
131 break;
132 case XK_BackSpace:
133 /* delete before cursor */
134 if ((wtext->text.endPos == wtext->text.startPos) && (wtext->text.startPos > 0))
135 wtext->text.startPos--;
136 textInsert(wtext, "");
137 break;
138 default:
139 if (count == 1 && !iscntrl(buffer[0])) {
140 buffer[count] = 0;
141 textInsert(wtext, buffer);
145 return True;
148 /********************************************************************
149 * textXYtoPos *
150 * given X coord, return position in array *
151 ********************************************************************/
152 static int textXtoPos(WTextInput * wtext, int x)
154 int pos;
155 x -= wtext->xOffset;
157 for (pos = 0; wtext->text.txt[pos] != '\0'; pos++) {
158 if (x < 0)
159 break;
160 else
161 x -= WMWidthOfString(wtext->font, &(wtext->text.txt[pos]), 1);
164 return pos;
167 /********************************************************************
168 * wTextCreate *
169 * create an instance of a text class *
171 * Args: *
172 * Return: *
173 * Global: dpy - the display *
174 ********************************************************************/
175 WTextInput *wTextCreate(WCoreWindow * core, int x, int y, int width, int height)
177 WTextInput *wtext;
179 wtext = wmalloc(sizeof(WTextInput));
180 wtext->core = wCoreCreate(core, x, y, width, height);
181 wtext->core->descriptor.handle_anything = &textEventHandler;
182 wtext->core->descriptor.handle_expose = &handleExpose;
183 wtext->core->descriptor.parent_type = WCLASS_TEXT_INPUT;
184 wtext->core->descriptor.parent = wtext;
186 wtext->font = core->screen_ptr->menu_entry_font;
188 XDefineCursor(dpy, wtext->core->window, wCursor[WCUR_TEXT]);
190 /* setup the text: */
191 wtext->text.txt = (char *)wmalloc(sizeof(char));
192 wtext->text.txt[0] = '\0';
193 wtext->text.length = 0;
194 wtext->text.startPos = 0;
195 wtext->text.endPos = 0;
198 XGCValues gcv;
200 gcv.foreground = core->screen_ptr->black_pixel;
201 gcv.background = core->screen_ptr->white_pixel;
202 gcv.line_width = 1;
203 gcv.function = GXcopy;
205 wtext->gc = XCreateGC(dpy, wtext->core->window,
206 (GCForeground | GCBackground | GCFunction | GCLineWidth), &gcv);
208 /* set up the regular context */
209 gcv.foreground = core->screen_ptr->black_pixel;
210 gcv.background = core->screen_ptr->white_pixel;
211 gcv.line_width = 1;
212 gcv.function = GXcopy;
214 wtext->regGC = XCreateGC(dpy, wtext->core->window,
215 (GCForeground | GCBackground | GCFunction | GCLineWidth), &gcv);
217 /* set up the inverted context */
218 gcv.function = GXcopyInverted;
220 wtext->invGC = XCreateGC(dpy, wtext->core->window,
221 (GCForeground | GCBackground | GCFunction | GCLineWidth), &gcv);
223 /* and set the background! */
224 XSetWindowBackground(dpy, wtext->core->window, gcv.background);
227 /* Figure out the y-offset... */
228 wtext->yOffset = (height - WMFontHeight(wtext->font)) / 2;
229 wtext->xOffset = wtext->yOffset;
231 wtext->canceled = False;
232 wtext->done = False; /* becomes True when the user *
233 * hits "Return" key */
235 XMapRaised(dpy, wtext->core->window);
236 return wtext;
239 /********************************************************************
240 * wTextDestroy *
242 * Args: wtext - the text field *
243 * Return: *
244 * Global: dpy - the display *
245 ********************************************************************/
246 void wTextDestroy(WTextInput * wtext)
248 XFreeGC(dpy, wtext->gc);
249 XFreeGC(dpy, wtext->regGC);
250 XFreeGC(dpy, wtext->invGC);
251 wfree(wtext->text.txt);
252 wCoreDestroy(wtext->core);
253 wfree(wtext);
256 /* The text-field consists of a frame drawn around the outside,
257 * and a textbox inside. The space between the frame and the
258 * text-box is the xOffset,yOffset. When the text needs refreshing,
259 * we only have to redraw the part inside the text-box, and we can
260 * leave the frame. If we get an expose event, or for some reason
261 * need to redraw the frame, wTextPaint will redraw the frame, and
262 * then call wTextRefresh to redraw the text-box */
264 /********************************************************************
265 * textRefresh *
266 * Redraw the text field. Call this after messing with the text *
267 * field. wTextRefresh re-draws the inside of the text field. If *
268 * the frame-area of the text-field needs redrawing, call *
269 * wTextPaint() *
271 * Args: wtext - the text field *
272 * Return: none *
273 * Global: dpy - the display *
274 ********************************************************************/
275 static void textRefresh(WTextInput * wtext)
277 WScreen *scr = wtext->core->screen_ptr;
278 char *ptr = wtext->text.txt;
279 int x1, x2, y1, y2;
281 /* x1,y1 is the upper left corner of the text box */
282 x1 = wtext->xOffset;
283 y1 = wtext->yOffset;
284 /* x2,y2 is the lower right corner of the text box */
285 x2 = wtext->core->width - wtext->xOffset;
286 y2 = wtext->core->height - wtext->yOffset;
288 /* Fill in the text field. Use the invGC to draw the rectangle,
289 * becuase then it will be the background color */
290 XFillRectangle(dpy, wtext->core->window, wtext->invGC, x1, y1, x2 - x1, y2 - y1);
292 /* Draw the text normally */
293 WMDrawImageString(scr->wmscreen, wtext->core->window,
294 scr->black, scr->white, wtext->font, x1, y1, ptr, wtext->text.length);
296 /* Draw the selected text */
297 if (wtext->text.startPos != wtext->text.endPos) {
298 int sp, ep;
299 /* we need sp < ep */
300 if (wtext->text.startPos > wtext->text.endPos) {
301 sp = wtext->text.endPos;
302 ep = wtext->text.startPos;
303 } else {
304 sp = wtext->text.startPos;
305 ep = wtext->text.endPos;
308 /* x1,y1 is now the upper-left of the selected area */
309 x1 += WMWidthOfString(wtext->font, ptr, sp);
310 /* and x2,y2 is the lower-right of the selected area */
311 ptr += sp * sizeof(char);
312 x2 = x1 + WMWidthOfString(wtext->font, ptr, (ep - sp));
313 /* Fill in the area where the selected text will go: *
314 * use the regGC to draw the rectangle, becuase then it *
315 * will be the color of the non-selected text */
316 XFillRectangle(dpy, wtext->core->window, wtext->regGC, x1, y1, x2 - x1, y2 - y1);
318 /* Draw the selected text. Inverse bg and fg colors for selection */
319 WMDrawImageString(scr->wmscreen, wtext->core->window,
320 scr->white, scr->black, wtext->font, x1, y1, ptr, (ep - sp));
323 /* And draw a quick little line for the cursor position */
324 x1 = WMWidthOfString(wtext->font, wtext->text.txt, wtext->text.endPos)
325 + wtext->xOffset;
326 XDrawLine(dpy, wtext->core->window, wtext->regGC, x1, 2, x1, wtext->core->height - 3);
329 /********************************************************************
330 * wTextPaint *
332 * Args: wtext - the text field *
333 * Return: *
334 * Global: dpy - the display *
335 ********************************************************************/
336 void wTextPaint(WTextInput * wtext)
338 /* refresh */
339 textRefresh(wtext);
341 /* Draw box */
342 XDrawRectangle(dpy, wtext->core->window, wtext->gc, 0, 0,
343 wtext->core->width - 1, wtext->core->height - 1);
346 /********************************************************************
347 * wTextGetText *
348 * return the string in the text field wText. DO NOT FREE THE *
349 * RETURNED STRING! *
351 * Args: wtext - the text field *
352 * Return: the text in the text field (NULL terminated) *
353 * Global: *
354 ********************************************************************/
355 char *wTextGetText(WTextInput * wtext)
357 if (!wtext->canceled)
358 return wtext->text.txt;
359 else
360 return NULL;
363 /********************************************************************
364 * wTextPutText *
365 * Put the string txt in the text field wText. The text field *
366 * needs to be explicitly refreshed after wTextPutText by calling *
367 * wTextRefresh(). *
368 * The string txt is copied *
370 * Args: wtext - the text field *
371 * txt - the new text string... freed by the text field! *
372 * Return: none *
373 * Global: *
374 ********************************************************************/
375 void wTextPutText(WTextInput * wtext, char *txt)
377 int length = strlen(txt);
379 /* no memory leaks! free the old txt */
380 if (wtext->text.txt != NULL)
381 wfree(wtext->text.txt);
383 wtext->text.txt = (char *)wmalloc((length + 1) * sizeof(char));
384 strcpy(wtext->text.txt, txt);
385 wtext->text.length = length;
386 /* By default No text is selected, and the cursor is at the end */
387 wtext->text.startPos = length;
388 wtext->text.endPos = length;
391 /********************************************************************
392 * textInsert *
393 * Insert some text at the cursor. (if startPos != endPos, *
394 * replace the selected text, otherwise insert) *
395 * The string txt is copied. *
397 * Args: wText - the text field *
398 * txt - the new text string... freed by the text field! *
399 * Return: none *
400 * Global: *
401 ********************************************************************/
402 static void textInsert(WTextInput * wtext, char *txt)
404 char *newTxt;
405 int newLen, txtLen, i, j;
406 int sp, ep;
408 /* we need sp < ep */
409 if (wtext->text.startPos > wtext->text.endPos) {
410 sp = wtext->text.endPos;
411 ep = wtext->text.startPos;
412 } else {
413 sp = wtext->text.startPos;
414 ep = wtext->text.endPos;
417 txtLen = strlen(txt);
418 newLen = wtext->text.length + txtLen - (ep - sp) + 1;
420 newTxt = (char *)malloc(newLen * sizeof(char));
422 /* copy the old text up to sp */
423 for (i = 0; i < sp; i++)
424 newTxt[i] = wtext->text.txt[i];
426 /* insert new text */
427 for (j = 0; j < txtLen; j++, i++)
428 newTxt[i] = txt[j];
430 /* copy old text after ep */
431 for (j = ep; j < wtext->text.length; j++, i++)
432 newTxt[i] = wtext->text.txt[j];
434 newTxt[i] = '\0';
436 /* By default No text is selected, and the cursor is at the end
437 * of inserted text */
438 wtext->text.startPos = sp + txtLen;
439 wtext->text.endPos = sp + txtLen;
441 wfree(wtext->text.txt);
442 wtext->text.txt = newTxt;
443 wtext->text.length = newLen - 1;
446 /********************************************************************
447 * wTextSelect *
448 * Select some text. If start == end, then the cursor is moved *
449 * to that position. If end == -1, then the text from start to *
450 * the end of the text entered in the text field is selected. *
451 * The text field is not automatically re-drawn! You must call *
452 * wTextRefresh to re-draw the text field. *
454 * Args: wtext - the text field *
455 * start - the beginning of the selected text *
456 * end - the end of the selected text *
457 * Return: none *
458 * Global: *
459 ********************************************************************/
460 void wTextSelect(WTextInput * wtext, int start, int end)
462 if (end == -1)
463 wtext->text.endPos = wtext->text.length;
464 else
465 wtext->text.endPos = end;
466 wtext->text.startPos = start;
469 /********************************************************************
470 * textEventHandler -- handles and dispatches all the events that *
471 * the text field class supports *
473 * Args: desc - all we need to know about this object *
474 * Return: none *
475 * Global: *
476 ********************************************************************/
477 static void textEventHandler(WObjDescriptor * desc, XEvent * event)
479 WTextInput *wtext = desc->parent;
480 int handled = False; /* has the event been handled */
482 switch (event->type) {
483 case MotionNotify:
484 /* If the button isn't down, we don't care about the
485 * event, but otherwise we want to adjust the selected
486 * text so we can wTextRefresh() */
487 if (event->xmotion.state & (Button1Mask | Button3Mask | Button2Mask)) {
488 handled = True;
489 wtext->text.endPos = textXtoPos(wtext, event->xmotion.x);
491 break;
493 case ButtonPress:
494 handled = True;
495 wtext->text.startPos = textXtoPos(wtext, event->xbutton.x);
496 wtext->text.endPos = wtext->text.startPos;
497 break;
499 case ButtonRelease:
500 handled = True;
501 wtext->text.endPos = textXtoPos(wtext, event->xbutton.x);
502 break;
504 case KeyPress:
505 handled = handleKeyPress(wtext, &event->xkey);
506 break;
508 case EnterNotify:
509 handled = True;
510 break;
512 case LeaveNotify:
513 handled = True;
514 break;
516 default:
517 break;
520 if (handled)
521 textRefresh(wtext);
522 else
523 WMHandleEvent(event);
525 return;
528 static void handleExpose(WObjDescriptor * desc, XEvent * event)
530 wTextPaint(desc->parent);