1 /*
2  * wmslib/src/but/textin.c, part of wmslib (Library functions)
3  * Copyright (C) 1994-1997 William Shubert.
4  * See "configure.h.in" for more copyright information.
5  */
6 
7 /*
8  * I don't really like this button.
9  *   It is slower than it has to be (this may or may not matter).
10  */
11 
12 #include <configure.h>
13 
14 #ifdef  X11_DISP
15 
16 #ifdef  STDC_HEADERS
17 #include <stdlib.h>
18 #include <unistd.h>
19 #endif  /* STDC_HEADERS */
20 #include <ctype.h>
21 #include <stdio.h>
22 #include <X11/Xlib.h>
23 #include <X11/Xutil.h>
24 #include <X11/cursorfont.h>
25 #include <X11/Xatom.h>
26 #include <X11/keysym.h>
27 #include <sys/time.h>
28 #ifdef  HAVE_SYS_SELECT_H
29 #include <sys/select.h>
30 #endif
31 #include <wms.h>
32 #include <but/but.h>
33 #include <but/textin.h>
34 #include <but/box.h>
35 #include <but/timer.h>
36 
37 
38 /**********************************************************************
39  * Data Types
40  **********************************************************************/
41 typedef struct SpecKey_struct {
42   struct SpecKey_struct *next;
43   KeySym keysym;
44   uint keyModifiers, modMask;
45   ButOut (*callback)(But *but, KeySym keysym, uint keyModifiers,
46 		     void *context);
47   void *context;
48   MAGIC_STRUCT
49 } SpecKey;
50 
51 typedef struct Txtin_struct  {
52   ButOut (*callback)(But *but, const char *value);
53   bool  hidden;
54   char  *str, *dispStr;  /* dispStr is only used if hidden is TRUE. */
55   int  maxlen, len, loc, cutend;
56   int  mousePress, origMousePress, clicks;
57   int  xoffset;
58   Pixmap  pm;
59   int  pm_w, pm_h, slideDir, slideFreq;
60   int  but3StartMouse, but3StartXoff;
61   bool  mouseInBut, cursorVisible;
62   ButTimer  *cTimer, *sTimer;
63   SpecKey  *specKeys;
64   MAGIC_STRUCT
65 } Txtin;
66 
67 
68 /**********************************************************************
69  * Forward Declarations
70  **********************************************************************/
71 static int  locateMouse(XFontStruct *fs, char *text, int len, int x,
72 			bool *rightSide);
73 static void  ti_cursor(But *but);
74 static void  ti_redrawCursor(But *but);
75 static ButOut  mmove(But *but, int x, int y);
76 static ButOut  mleave(But *but);
77 static ButOut  mpress(But *but, int butnum, int x, int y);
78 static ButOut  mrelease(But *but, int butnum, int x, int y);
79 static ButOut  kpress(But *but, const char *keystr, KeySym sym);
80 static void  draw(But *but, int x,int y, int w,int h);
81 static ButOut  destroy(But *but);
82 static bool  insert(ButEnv *env, const char *src, Txtin *but);
83 static void  cut(ButEnv *env, char *cutstr, int cutlen, But *but);
84 static void  paste(ButEnv *env, But *but);
85 static void  enableCTimer(But *but);
86 static void  disableCTimer(But *but);
87 static void  curInView(But *but);
88 static void  textInView(But *but, bool maxInView);
89 
90 static bool  sreq(ButEnv *env, XSelectionRequestEvent *xsre);
91 static int   sclear(ButEnv *env);
92 static int   snotify(ButEnv *env, XSelectionEvent *xsre);
93 static void  flags(But *but, uint flags);
94 
95 static void  wordsel_adjust(Txtin *but);
96 static void  startSlide(But  *but, int dir, bool fast);
97 static ButOut  slide(ButTimer *timer);
98 
99 
100 /**********************************************************************
101  * Globals
102  **********************************************************************/
103 static int  cutstr_maxlen = 0, cutstr_len;
104 static char  *cutdata = NULL;
105 static But  *cut_butnum = NULL;
106 static But  *paste_butnum = NULL;
107 static const ButAction  action = {
108   mmove, mleave, mpress, mrelease,
109   kpress, NULL, draw, destroy, flags, NULL};
110 
111 
112 /**********************************************************************
113  * Functions
114  **********************************************************************/
butTextin_create(ButOut (* callback)(But * but,const char * value),void * packet,ButWin * win,int layer,int flags,const char * text,int maxlen)115 But  *butTextin_create(ButOut (*callback)(But *but, const char *value),
116 		       void *packet, ButWin *win, int layer, int flags,
117 		       const char *text, int maxlen)  {
118   But  *but;
119   Txtin  *ti;
120 
121   ti = wms_malloc(sizeof(Txtin));
122   MAGIC_SET(ti);
123   but = but_create(win, ti, &action);
124   but->uPacket = packet;
125   but->layer = layer;
126   but->flags = flags | BUT_OPAQUE;
127 
128   if (text == NULL)
129     text = "";
130   ti->callback = callback;
131   ti->hidden = FALSE;
132   ti->maxlen = maxlen;
133   ti->str = (char *)wms_malloc(ti->maxlen + 1);
134   ti->dispStr = ti->str;
135   ti->xoffset = 0;
136   ti->pm_w = ti->pm_h = 0;
137   ti->pm = None;
138   ti->mousePress = -1;
139   ti->mouseInBut = FALSE;
140   strcpy(ti->str, text);
141   ti->len = ti->loc = ti->cutend = strlen(text);
142   ti->cTimer = NULL;
143   if (flags & BUT_KEYED)  {
144     ti->cursorVisible = TRUE;
145   } else
146     ti->cursorVisible = FALSE;
147   ti->sTimer = NULL;
148   ti->but3StartXoff = -1;
149   ti->specKeys = NULL;
150   but_init(but);
151   return(but);
152 }
153 
154 
butTextin_get(But * but)155 const char  *butTextin_get(But *but)  {
156   Txtin  *ti = but->iPacket;
157 
158   assert(but->action == &action);
159   assert(MAGIC(ti));
160   return(ti->str);
161 }
162 
163 
destroy(But * but)164 static ButOut  destroy(But *but)  {
165   Txtin  *ti = but->iPacket;
166   ButEnv  *env = but->win->env;
167   SpecKey  *spec, *nextSpec;
168 
169   assert(but->action == &action);
170   assert(MAGIC(ti));
171   spec = ti->specKeys;
172   while (spec != NULL) {
173     assert(MAGIC(spec));
174     nextSpec = spec->next;
175     MAGIC_UNSET(spec);
176     wms_free(spec);
177     spec = nextSpec;
178   }
179   if (cut_butnum == but)  {
180     if (env->sClear == sclear)
181       env->sClear = NULL;
182     if (env->sReq == sreq)
183       env->sReq = NULL;
184     cut_butnum = NULL;
185   }
186   if (paste_butnum == but)  {
187     if (env->sNotify == snotify)
188       env->sNotify = NULL;
189     paste_butnum = NULL;
190   }
191   if (ti->pm != None)
192     XFreePixmap(but->win->env->dpy, ti->pm);
193   wms_free(ti->str);
194   if (ti->hidden)
195     wms_free(ti->dispStr);
196   else  {
197     assert(ti->dispStr == ti->str);
198   }
199   MAGIC_UNSET(ti);
200   wms_free(ti);
201   return(0);
202 }
203 
204 
draw(But * but,int x,int y,int w,int h)205 static void  draw(But *but, int x,int y, int w,int h)  {
206   ButWin  *win = but->win;
207   ButEnv  *env = win->env;
208   Txtin  *ti = but->iPacket;
209   int  txtw;
210   int  butbw = env->stdButBw;
211   XFontStruct  *fs = env->fonts[0];
212 
213   assert(but->action == &action);
214   assert(MAGIC(ti));
215   XSetFont(env->dpy, env->gc2, env->fonts[0]->fid);
216   if ((ti->pm_w != but->w - 2*butbw) || (ti->pm_h != but->h - 2*butbw))  {
217     if (ti->pm != None)
218       XFreePixmap(env->dpy, ti->pm);
219     ti->pm = XCreatePixmap(env->dpy, win->win,
220 			   ti->pm_w = but->w - 2*butbw,
221 			   ti->pm_h = but->h - 2*butbw,
222 			   DefaultDepth(env->dpy, DefaultScreen(env->dpy)));
223   }
224   but_drawBox(win, but->x,but->y, but->w,but->h, 1, butbw,
225 	      BUT_SRIGHT|BUT_SLEFT, BUT_LIT, BUT_SHAD, None, None);
226   txtw = XTextWidth(fs, ti->dispStr, ti->loc);
227   butEnv_setXFg2(env, BUT_HIBG);
228   XFillRectangle(env->dpy, ti->pm, env->gc2,
229 		 0,0, ti->pm_w,ti->pm_h);
230   if (but->flags & BUT_PRESSABLE)
231     butEnv_setXFg2(env, BUT_FG);
232   else  {
233     XSetFillStyle(env->dpy, env->gc2, FillStippled);
234     XSetForeground(env->dpy, env->gc2, env->colors[BUT_FG]);
235   }
236   if (ti->loc == ti->cutend)  {
237     XDrawString(env->dpy, ti->pm, env->gc2,
238 		x = butbw - ti->xoffset,
239 		y = (fs->ascent + ti->pm_h - fs->descent) / 2,
240 		ti->dispStr, ti->len);
241   } else  {
242     int  tw1, tw2;
243     int  cl1, cl2;
244 
245     if (ti->cutend >= ti->loc)  {
246       cl1 = ti->loc;
247       cl2 = ti->cutend;
248     } else  {
249       cl1 = ti->cutend;
250       cl2 = ti->loc;
251     }
252     XDrawString(env->dpy, ti->pm, env->gc2,
253 		x = butbw - ti->xoffset,
254 		y = (fs->ascent + ti->pm_h - fs->descent) / 2,
255 		ti->dispStr, cl1);
256     tw1 = XTextWidth(fs, ti->dispStr, cl1);
257     tw2 = XTextWidth(fs, ti->dispStr + cl1, cl2 - cl1);
258     x += tw1 + tw2;
259     XDrawString(env->dpy, ti->pm, env->gc2, x,y,
260 		ti->dispStr + cl2, ti->len - cl2);
261     x -= tw2;
262     XSetBackground(env->dpy, env->gc2, env->colors[BUT_SELBG]);
263     if (!env->colorp)  {
264       XSetTile(env->dpy, env->gc2, env->colorPmaps[BUT_WHITE]);
265       XSetForeground(env->dpy, env->gc2, env->colors[BUT_WHITE]);
266     }
267     XDrawImageString(env->dpy, ti->pm, env->gc2, x,y,
268 		     ti->dispStr + cl1, cl2 - cl1);
269 
270   }
271   if (!(but->flags & BUT_PRESSABLE))  {
272     butEnv_stdFill2(env);
273   }
274   XCopyArea(env->dpy, ti->pm, win->win, env->gc,
275 	    0,0, ti->pm_w,ti->pm_h,
276 	    but->x + butbw - win->xOff, but->y + butbw - win->yOff);
277   if ((ti->loc == ti->cutend) && ti->cursorVisible &&
278       (but->flags & BUT_PRESSABLE))
279     ti_cursor(but);
280 }
281 
282 
mmove(But * but,int x,int y)283 static ButOut  mmove(But *but, int x, int y)  {
284   Txtin  *ti = but->iPacket;
285   ButWin  *win = but->win;
286   ButEnv  *env = win->env;
287   int  newMousePress;
288   bool  rightSide;
289 
290   if (!(but->flags & BUT_PRESSABLE))
291     return(BUTOUT_CAUGHT);
292   if (!ti->mouseInBut)  {
293     ti->mouseInBut = TRUE;
294     butEnv_setCursor(env, but, butCur_text);
295   }
296   if (ti->mousePress != -1)  {
297     if (ti->clicks < 2)  {
298       if (x < but->x)  {
299 	startSlide(but, -1, (x < but->x - but->h + env->stdButBw*2));
300 	newMousePress = 0;
301       } else if (x > but->x + but->w)  {
302 	startSlide(but, 1, (x > but->x + but->w + but->h - env->stdButBw*2));
303 	newMousePress = ti->len;
304       } else  {
305 	if (ti->sTimer != NULL)  {
306 	  butTimer_destroy(ti->sTimer);
307 	  ti->sTimer = NULL;
308 	}
309 	if (y < but->y + env->stdButBw*2)
310 	  newMousePress = 0;
311 	else if (y > but->y+but->h - env->stdButBw*2)
312 	  newMousePress = ti->len;
313 	else
314 	  newMousePress = locateMouse(env->fonts[0], ti->dispStr, ti->len,
315 				      x - (but->x + 2*env->stdButBw -
316 					   ti->xoffset), &rightSide);
317       }
318       if (ti->clicks == 1)  {
319 	if (rightSide && (newMousePress >= ti->origMousePress) &&
320 	    (newMousePress < ti->len))
321 	  ++newMousePress;
322 	else if (!rightSide && (newMousePress <= ti->origMousePress)
323 		 && (newMousePress > 0))
324 	  --newMousePress;
325       }
326       if (newMousePress != ti->mousePress)  {
327 	ti->mousePress = ti->loc = newMousePress;
328 	if (ti->clicks == 1)  {
329 	  ti->cutend = ti->origMousePress;
330 	  wordsel_adjust(ti);
331 	}
332 	but_draw(but);
333       }
334     }
335   }
336   if (ti->but3StartXoff != -1)  {
337     int  oldXoff = ti->xoffset;
338 
339     ti->xoffset = ti->but3StartXoff - 10*(x - ti->but3StartMouse);
340     textInView(but, FALSE);
341     if (oldXoff != ti->xoffset)
342       but_draw(but);
343   }
344   return(BUTOUT_CAUGHT);
345 }
346 
347 
mleave(But * but)348 static ButOut  mleave(But *but)  {
349   Txtin  *ti = but->iPacket;
350 
351   if (ti->mouseInBut)  {
352     ti->mouseInBut = FALSE;
353     butEnv_setCursor(but->win->env, but, butCur_idle);
354   }
355   return(BUTOUT_CAUGHT);
356 }
357 
358 
mpress(But * but,int butnum,int x,int y)359 static ButOut  mpress(But *but, int butnum, int x, int y)  {
360   Txtin  *ti = but->iPacket;
361   ButEnv  *env = but->win->env;
362   static Time  lastPressTime = -1;
363   static int   lastPressNum = -2;
364   int  rightSide;
365 
366   if (ti->cTimer)  {
367     butTimer_reset(ti->cTimer);
368     ti->cursorVisible = TRUE;
369   }
370   switch (butnum)  {
371   case 1:
372     /* Button 1 selects, drags, etc. */
373     if (ti->but3StartXoff != -1)
374       return(BUTOUT_ERR);
375     if (!(but->flags & BUT_KEYED))
376       but_newFlags(but, but->flags | BUT_KEYED);
377     but_newFlags(but, but->flags | BUT_LOCKED);
378     ti->mousePress = locateMouse(env->fonts[0], ti->dispStr, ti->len,
379 				 x - (but->x + 2*env->stdButBw -
380 				      ti->xoffset), &rightSide);
381     ti->loc = ti->cutend = ti->origMousePress = ti->mousePress;
382     if ((lastPressTime + BUT_DCLICK > env->eventTime) &&
383 	(lastPressNum + 1 == env->eventNum))  {
384       if (ti->clicks == 0)  {
385 	/* It's a double click! */
386 	++ti->clicks;
387 	if (rightSide)
388 	  ++ti->loc;
389 	else
390 	  --ti->loc;
391 	wordsel_adjust(ti);
392       } else if (ti->clicks == 1)  {
393 	/* It's a triple click! */
394 	++ti->clicks;
395 	ti->loc = 0;
396 	ti->cutend = ti->len;
397       } else  {
398 	/* Quadruple click!  Restart! */
399 	ti->clicks = 0;
400       }
401     } else
402       ti->clicks = 0;
403     lastPressTime = env->eventTime;
404     lastPressNum = env->eventNum;
405     but_draw(but);
406     break;
407   case 2:
408     /* Paste! */
409     ti->cutend = ti->loc = locateMouse(env->fonts[0], ti->dispStr, ti->len,
410 				       x - (but->x + 2*env->stdButBw -
411 					    ti->xoffset), NULL);
412     paste(env, but);
413     break;
414   case 3:
415     /*
416      * Button 3 scrolls as if this input window was a mini-canvas.  I'm not
417      *   sure how useful this is but I'm doing it anyway just to make my
418      *   interface more consistent.
419      */
420     if (ti->mousePress != -1)
421       return(BUTOUT_ERR);
422     but_newFlags(but, but->flags | BUT_LOCKED);
423     butEnv_setCursor(env, but, butCur_lr);
424     ti->but3StartMouse = x;
425     ti->but3StartXoff = ti->xoffset;
426     break;
427   default:
428     /*
429      * Weird.  I don't know what to do with mice with more than 3 buttons
430      *   so I guess I might as well beep.  I like beeping.
431      */
432     return(BUTOUT_ERR);
433     break;
434   }
435   return(BUTOUT_CAUGHT);
436 }
437 
438 
mrelease(But * but,int butnum,int x,int y)439 static ButOut  mrelease(But *but, int butnum, int x, int y)  {
440   Txtin  *ti = but->iPacket;
441   char  *cutstr;
442   int  cutlen;
443 
444   if (ti->cTimer)  {
445     butTimer_reset(ti->cTimer);
446     ti->cursorVisible = TRUE;
447   }
448   if ((butnum == 1) && (ti->mousePress != -1))  {
449     if (ti->sTimer != NULL)  {
450       butTimer_destroy(ti->sTimer);
451       ti->sTimer = NULL;
452     }
453     if (ti->loc != ti->cutend)  {
454       if (ti->loc < ti->cutend)  {
455 	cutstr = ti->str + ti->loc;
456 	cutlen = ti->cutend - ti->loc;
457       } else  {
458 	cutstr = ti->str + ti->cutend;
459 	cutlen = ti->loc - ti->cutend;
460       }
461       cut(but->win->env, cutstr, cutlen, but);
462     }
463     ti->mousePress = -1;
464     but_newFlags(but, but->flags & ~BUT_LOCKED);
465   } else if ((butnum == 3) && (ti->but3StartXoff != -1))  {
466     butEnv_setCursor(but->win->env, but, butCur_text);
467     ti->but3StartXoff = -1;
468     but_newFlags(but, but->flags & ~BUT_LOCKED);
469   }
470   return(BUTOUT_CAUGHT);
471 }
472 
473 
kpress(But * but,const char * newtext,KeySym keysym)474 static ButOut  kpress(But *but, const char *newtext, KeySym keysym)  {
475   Txtin  *ti = but->iPacket;
476   ButEnv  *env = but->win->env;
477   bool  need_draw = FALSE, needCallback = FALSE;
478   int  i;
479   ButOut  result = 0;
480   SpecKey  *spec;
481 
482   butTimer_reset(ti->cTimer);
483   ti->cursorVisible = TRUE;
484   for (spec = ti->specKeys;  spec != NULL;  spec = spec->next) {
485     if ((keysym == spec->keysym) &&
486 	((env->keyModifiers & spec->modMask) == spec->keyModifiers)) {
487       return(spec->callback(but, keysym, env->keyModifiers, spec->context) |
488 	     BUTOUT_CAUGHT);
489     }
490   }
491   if (((keysym >= XK_KP_Space) && (keysym <= XK_KP_9)) ||
492       ((keysym >= XK_space) && (keysym <= XK_ydiaeresis)))  {
493     need_draw = TRUE;
494     switch(newtext[0])  {
495     case '\001':  /* Ctrl-A: Beginning of line. */
496     case '\020':  /* Ctrl-P: Up a line. */
497       ti->loc = ti->cutend = 0;
498       break;
499     case '\002':  /* Ctrl-B: Back a character. */
500       if (ti->loc != ti->cutend)  {
501 	ti->cutend = ti->loc;
502       } else if (ti->loc)  {
503 	ti->cutend = --ti->loc;
504       } else  {
505 	need_draw = FALSE;
506 	result |= BUTOUT_ERR | BUTOUT_CAUGHT;
507       }
508       break;
509     case '\004':  /* Ctrl-D: Delete right. */
510       if (ti->loc != ti->cutend)
511 	need_draw = insert(env, "", ti);
512       else if (ti->loc < ti->len)  {
513 	for (i = ti->loc;  ti->str[i];  ++i)
514 	  ti->str[i] = ti->str[i+1];
515 	--ti->len;
516 	need_draw = TRUE;
517       } else
518 	result |= BUTOUT_ERR | BUTOUT_CAUGHT;
519       break;
520     case '\005':  /* Ctrl-E: End of line. */
521     case '\016':  /* Ctrl-N: Down a line. */
522       ti->loc = ti->cutend = ti->len;
523       break;
524     case '\006':  /* Ctrl-F: Forward a character. */
525       if (ti->loc != ti->cutend)  {
526 	ti->loc = ti->cutend;
527       } else if (ti->loc < ti->len)  {
528 	ti->cutend = ++ti->loc;
529       } else  {
530 	result |= BUTOUT_ERR | BUTOUT_CAUGHT;
531 	need_draw = FALSE;
532       }
533       break;
534     case '\013':  /* Ctrl-K: Kill to end of line. */
535       ti->str[ti->len = ti->loc = ti->cutend] = '\0';
536       break;
537     case '\025':  /* Ctrl-U: Kill from beginning of line. */
538       for (i = ti->loc;  i < ti->len;  ++i)
539 	ti->str[i - ti->loc] = ti->str[i];
540       ti->str[ti->len -= ti->loc] = '\0';
541       ti->loc = ti->cutend = 0;
542       break;
543     default:
544       if (!(need_draw = insert(env, newtext, ti)))
545 	result |= BUTOUT_ERR | BUTOUT_CAUGHT;
546       break;
547     }
548   } else if ((keysym >= XK_Shift_L) && (keysym <= XK_Hyper_R))  {
549   } else if ((keysym >= XK_F1) && (keysym <= XK_F35))  {
550     if (!(need_draw = insert(env, newtext, ti)))
551       result |= BUTOUT_ERR | BUTOUT_CAUGHT;
552   } else if ((keysym == XK_BackSpace) || (keysym == XK_Delete))  {
553     if (ti->loc != ti->cutend)
554       need_draw = insert(env, "", ti);
555     else if (ti->loc > 0)  {
556       for (i = ti->cutend = --ti->loc;  ti->str[i];  ++i)
557 	ti->str[i] = ti->str[i+1];
558       --ti->len;
559       need_draw = TRUE;
560     } else
561       result |= BUTOUT_ERR | BUTOUT_CAUGHT;
562 #if  0  /* People don't like it when delete works the way I like it.  :-( */
563   } else if (keysym == XK_Delete)  {
564     if (ti->loc != ti->cutend)
565       need_draw = insert(env, "", ti);
566     else if (ti->loc < ti->len)  {
567       for (i = ti->loc;  ti->str[i];  ++i)
568 	ti->str[i] = ti->str[i+1];
569       --ti->len;
570       need_draw = TRUE;
571     } else
572       result |= BUTOUT_ERR | BUTOUT_CAUGHT;
573 #endif
574   }	else if (keysym == XK_Left)  {
575     if (ti->loc != ti->cutend)  {
576       if (ti->loc < ti->cutend)
577 	ti->cutend = ti->loc;
578       else
579 	ti->loc = ti->cutend;
580       need_draw = TRUE;
581     } else if (ti->loc)  {
582       ti->cutend = --ti->loc;
583       need_draw = TRUE;
584     } else
585       result |= BUTOUT_ERR | BUTOUT_CAUGHT;
586   } else if (keysym == XK_Right)  {
587     if (ti->loc != ti->cutend)  {
588       if (ti->loc > ti->cutend)
589 	ti->cutend = ti->loc;
590       else
591 	ti->loc = ti->cutend;
592       need_draw = TRUE;
593     } else if (ti->loc < ti->len)  {
594       ti->cutend = ++ti->loc;
595       need_draw = TRUE;
596     } else
597       result |= BUTOUT_ERR | BUTOUT_CAUGHT;
598   } else if (keysym == XK_Up)  {
599     ti->loc = ti->cutend = 0;
600     need_draw = TRUE;
601   } else if (keysym == XK_Down)  {
602     ti->loc = ti->cutend = ti->len;
603     need_draw = TRUE;
604   } else if ((keysym == XK_Return) || (keysym == XK_Linefeed) ||
605 	     (keysym == XK_KP_Enter))  {
606     result |= BUTOUT_CAUGHT;
607     if (ti->callback == NULL)  {
608       but_setFlags(but, BUT_NOKEY);
609     } else  {
610       needCallback = TRUE;
611     }
612   }
613   /* Void all mouse movement until it is pressed again. */
614   if (ti->mousePress != -1)  {
615     ti->mousePress = -1;
616     if (but->flags & BUT_LOCKED)
617       but_newFlags(but, but->flags & ~BUT_LOCKED);
618   }
619   if (need_draw)  {
620     result |= BUTOUT_CAUGHT;
621     curInView(but);
622     but_draw(but);
623   }
624   if (needCallback)
625     result |= ti->callback(but, ti->str);
626   return(result);
627 }
628 
629 
insert(ButEnv * env,const char * src,Txtin * ti)630 static bool  insert(ButEnv *env, const char *src, Txtin *ti)  {
631   int  i, j, ntLen, cl1, cl2;
632   XFontStruct  *fs = env->fonts[0];
633 
634   if (src == NULL)  {
635     return(FALSE);
636   }
637   if (ti->loc <= ti->cutend)  {
638     cl1 = ti->loc;
639     cl2 = ti->cutend;
640   } else  {
641     cl1 = ti->cutend;
642     cl2 = ti->loc;
643   }
644   for (i = 0, ntLen = 0;  src[i];  ++i)  {
645     if ((((uchar)src[i] >= fs->min_char_or_byte2) &&
646 	 ((uchar)src[i] <= fs->max_char_or_byte2)) ||
647 	(fs->all_chars_exist &&
648 	 fs->per_char[(uchar)src[i] - fs->min_char_or_byte2].width))
649       ++ntLen;
650   }
651   if ((ntLen == 0) && src[0])
652     return(FALSE);
653   if (ntLen + ti->len + cl1 - cl2 > ti->maxlen)  {
654     return(FALSE);
655   }
656   if (cl1 != cl2)  {
657     for (i = cl1;  i < ti->len;  ++i)
658       ti->str[i] = ti->str[i + cl2 - cl1];
659   }
660   for (i = ti->len + ntLen;  i >= cl1 + ntLen;  --i)
661     ti->str[i] = ti->str[i - ntLen];
662   for (i = 0, j = cl1;  src[i];  ++i)  {
663     if ((((uchar)src[i] >= fs->min_char_or_byte2) &&
664 	 ((uchar)src[i] <= fs->max_char_or_byte2)) ||
665 	(fs->all_chars_exist &&
666 	 fs->per_char[(uchar)src[i] - fs->min_char_or_byte2].width))  {
667       ti->str[j++] = src[i];
668     }
669   }
670   ti->len += cl1 - cl2 + ntLen;
671   ti->loc = ti->cutend = cl1 + ntLen;
672   return(TRUE);
673 }
674 
675 
ti_cursor(But * but)676 static void  ti_cursor(But *but)  {
677   Txtin  *ti = but->iPacket;
678   ButWin  *win = but->win;
679   ButEnv  *env = win->env;
680   XFontStruct  *fs = env->fonts[0];
681   int  x, y;
682   int  rw;
683 
684   if (ti->loc != ti->cutend)
685     return;
686   x = but->x - ti->xoffset + env->stdButBw*2 +
687     XTextWidth(fs, ti->dispStr, ti->loc);
688   y = but->y + env->stdButBw*2;
689   if ((rw = (fs->ascent + fs->descent + 10) / 20) < 1)
690     rw = 1;
691   x -= rw/2;
692   if (x < but->x + env->stdButBw)  {
693     rw -= (but->x + env->stdButBw - x);
694     x = but->x + env->stdButBw;
695   } else if (x+rw > but->x + but->w - env->stdButBw)  {
696     rw = but->x + but->w - env->stdButBw - x;
697   }
698   if (rw <= 0)
699     return;
700   if (ti->cursorVisible)
701     butEnv_setXFg2(env, BUT_FG);
702   else
703     butEnv_setXFg2(env, BUT_HIBG);
704   XFillRectangle(env->dpy, win->win, env->gc2, x - win->xOff, y - win->yOff,
705 		 rw, ti->pm_h - env->stdButBw*2);
706 }
707 
708 
ti_redrawCursor(But * but)709 static void  ti_redrawCursor(But *but)  {
710   Txtin  *ti = but->iPacket;
711   ButWin  *win = but->win;
712   ButEnv  *env = win->env;
713   XFontStruct  *fs = env->fonts[0];
714   int  x, y;
715   int  rw;
716 
717   if (ti->loc != ti->cutend)
718     return;
719   x = but->x - ti->xoffset + env->stdButBw*2 +
720     XTextWidth(fs, ti->dispStr, ti->loc);
721   y = but->y + env->stdButBw*2;
722   if ((rw = (fs->ascent + fs->descent + 10) / 20) < 1)
723     rw = 1;
724   x -= rw/2;
725   if (x < but->x + env->stdButBw)  {
726     rw -= (but->x + env->stdButBw - x);
727     x = but->x + env->stdButBw;
728   } else if (x+rw > but->x + but->w - env->stdButBw)  {
729     rw = but->x + but->w - env->stdButBw - x;
730   }
731   if (rw <= 0)
732     return;
733   butWin_redraw(win, x, y,
734 		rw, ti->pm_h - env->stdButBw*2);
735 }
736 
737 
cut(ButEnv * env,char * cutstr,int cutlen,But * but)738 static void  cut(ButEnv *env, char *cutstr, int cutlen, But *but)  {
739   ButWin  *realWin;
740 
741   if (cutstr_maxlen < cutlen)  {
742     if (cutdata != NULL)
743       wms_free(cutdata);
744     cutdata = (char *)wms_malloc(cutlen);
745     cutstr_maxlen = cutlen;
746   }
747   XStoreBytes(env->dpy, cutstr, cutlen);
748   memcpy(cutdata, cutstr, cutlen);
749   cutstr_len = cutlen;
750   realWin = but->win;
751   while (realWin->parent != NULL)
752     realWin = realWin->parent;
753   if ((env->sReq == NULL) ||
754       ((env->sReq == sreq) && (cut_butnum == but)))
755     XSetSelectionOwner(env->dpy, XA_PRIMARY, realWin->win, 0);
756   else
757     env->sClear(env);
758   if (XGetSelectionOwner(env->dpy, XA_PRIMARY) == realWin->win)  {
759     env->sReq = sreq;
760     env->sClear = sclear;
761     cut_butnum = but;
762   }
763 }
764 
765 
sreq(ButEnv * env,XSelectionRequestEvent * xsre)766 static bool  sreq(ButEnv *env, XSelectionRequestEvent *xsre)  {
767   if (xsre->target != XA_STRING)
768     return(FALSE);
769   XChangeProperty(env->dpy, xsre->requestor, xsre->property, xsre->target,
770 		  8, PropModeReplace, cutdata, cutstr_len);
771   return(TRUE);
772 }
773 
774 
sclear(ButEnv * env)775 static int  sclear(ButEnv *env)  {
776   Txtin  *ti;
777 
778   assert(MAGIC(cut_butnum));
779   ti = cut_butnum->iPacket;
780   assert(MAGIC(ti));
781   ti->loc = ti->cutend;
782   but_draw(cut_butnum);
783   env->sReq = NULL;
784   env->sClear = NULL;
785   return(0);
786 }
787 
788 
paste(ButEnv * env,But * but)789 static void  paste(ButEnv *env, But *but)  {
790   ButWin  *topWin;
791 
792   paste_butnum = but;
793   env->sNotify = snotify;
794   topWin = but->win;
795   while (topWin->parent != NULL)
796     topWin = topWin->parent;
797   XConvertSelection(env->dpy, XA_PRIMARY, XA_STRING, env->prop,
798 		    topWin->win, 0);
799 }
800 
801 
snotify(ButEnv * env,XSelectionEvent * xsnot)802 static int  snotify(ButEnv *env, XSelectionEvent *xsnot)  {
803   unsigned char  *propbuf;
804   unsigned long  proplen, propleft;
805   int  blen;
806   int  pformat;
807   Atom  ptype;
808   Txtin  *ti;
809   char  *pb2;
810 
811   ti = paste_butnum->iPacket;
812   if (xsnot->property != None)  {
813     XGetWindowProperty(env->dpy, xsnot->requestor,
814 		       xsnot->property, 0, ti->maxlen, True, AnyPropertyType,
815 		       &ptype, &pformat, &proplen, &propleft,
816 		       &propbuf);
817     pb2 = (char *)wms_malloc(proplen * pformat + 1);
818     pb2[proplen * pformat] = '\0';
819     memcpy(pb2, propbuf, proplen * pformat);
820     XFree(propbuf);
821     if (insert(env, pb2, ti))  {
822       textInView(paste_butnum, FALSE);
823       but_draw(paste_butnum);
824     } else
825       XBell(env->dpy, 0);
826     wms_free(pb2);
827   } else  {
828     propbuf = (unsigned char *)XFetchBytes(env->dpy, &blen);
829     if (blen > 0)  {
830       pb2 = (char *)wms_malloc(blen + 1);
831       pb2[blen] = '\0';
832       memcpy(pb2, propbuf, blen);
833       XFree(propbuf);
834       if (insert(env, pb2, ti))  {
835 	textInView(paste_butnum, FALSE);
836 	but_draw(paste_butnum);
837       } else
838 	XBell(env->dpy, 0);
839       wms_free(pb2);
840     } else
841       XBell(env->dpy, 0);
842   }
843   env->sNotify = NULL;
844   return(0);
845 }
846 
847 
848 /*
849  * This adjusts your loc and your cutend for when you're in word select mode.
850  */
wordsel_adjust(Txtin * but)851 static void  wordsel_adjust(Txtin *but)  {
852   int  a, b;
853 
854   if (but->loc < but->cutend)  {
855     a = but->loc;
856     b = but->cutend;
857   } else  {
858     a = but->cutend;
859     b = but->loc;
860   }
861   for (;;)  {
862     if (a <= 0)  {
863       a = 0;
864       break;
865     }
866     if (((isalnum(but->str[a]) || (but->str[a] == '_')) &&
867 	 (isalnum(but->str[a-1]) || (but->str[a-1] == '_'))) ||
868 	(but->str[a] == but->str[a-1]) ||
869 	(isdigit(but->str[a]) && (but->str[a-1] == '.') &&
870 	 (a >= 2) && isdigit(but->str[a-2])) ||
871 	(isdigit(but->str[a+1]) && (but->str[a] == '.') &&
872 	  isdigit(but->str[a-1])))
873       --a;
874     else
875       break;
876   }
877   if (b <= 0)
878     b = 1;
879   for (;;)  {
880     if (b >= but->len)  {
881       b = but->len;
882       break;
883     }
884     if (((isalnum(but->str[b-1]) || (but->str[b-1] == '_')) &&
885 	 (isalnum(but->str[b]) || (but->str[b] == '_'))) ||
886 	(but->str[b-1] == but->str[b]) ||
887 	(isdigit(but->str[b-1]) && (but->str[b] == '.') &&
888 	 isdigit(but->str[b+1])) ||
889 	((b >= 2) && isdigit(but->str[b-2]) && (but->str[b-1] == '.') &&
890 	 isdigit(but->str[b])))
891       ++b;
892     else
893       break;
894   }
895   but->loc = a;
896   but->cutend = b;
897 }
898 
899 
900 /* Returns the index of the character just to the right of the cursor. */
locateMouse(XFontStruct * fs,char * text,int len,int x,bool * rightSide)901 static int  locateMouse(XFontStruct *fs, char *text, int len, int x,
902 			bool  *rightSide)  {
903   int  i;
904   int prev_x = x;
905   bool  dummy;
906 
907   if (rightSide == NULL)
908     rightSide = &dummy;
909   for (i = 0;  (x > 0) && (i < len);  ++i)  {
910     prev_x = x;
911     if (fs->per_char == NULL)
912       /* Monospace font. */
913       x -= fs->min_bounds.width;
914     else
915       x -= fs->per_char[text[i] - fs->min_char_or_byte2].width;
916   }
917   if ((*rightSide = ((i > 0) && (x < 0) && (prev_x < -x))))
918     --i;
919   if (i == len)
920     *rightSide = FALSE;
921   return(i);
922 }
923 
924 
925 /* Set up xoffset so that the cursor will be in view. */
curInView(But * but)926 static void  curInView(But *but)  {
927   Txtin  *ti = but->iPacket;
928   ButEnv  *env = but->win->env;
929   int  curIn;
930 
931   curIn = XTextWidth(env->fonts[0], ti->dispStr, ti->loc);
932   if (curIn < ti->xoffset)
933     ti->xoffset = curIn;
934   if (curIn - ti->xoffset > ti->pm_w - env->stdButBw * 2)
935     ti->xoffset = curIn - ti->pm_w + env->stdButBw * 2;
936 }
937 
938 
939 /*
940  * Set up xoffset so that the text will be in view.  If maxInView is set,
941  *   then make sure that as much text is in the window as possible (that is,
942  *   there is no unnecessary empty space to the right of the text).
943  */
textInView(But * but,bool maxInView)944 static void  textInView(But *but, bool maxInView)  {
945   Txtin  *ti = but->iPacket;
946   ButEnv  *env = but->win->env;
947   int  curIn;
948 
949   curIn = XTextWidth(env->fonts[0], ti->dispStr, ti->len);
950   if (maxInView)  {
951     if (ti->xoffset > curIn - (but->w - env->stdButBw*4))
952       ti->xoffset = curIn - (but->w - env->stdButBw * 4);
953   }
954   if (ti->xoffset < 0)
955     ti->xoffset = 0;
956   if (ti->xoffset > curIn)
957     ti->xoffset = curIn;
958 }
959 
960 
flags(But * but,uint flags)961 static void  flags(But *but, uint flags)  {
962   Txtin  *ti = but->iPacket;
963 
964   if ((but->flags & BUT_KEYED) != (flags & BUT_KEYED))  {
965     if (flags & BUT_KEYED)  {
966       enableCTimer(but);
967       ti->cursorVisible = TRUE;
968     } else  {
969       ti->cursorVisible = FALSE;
970       disableCTimer(but);
971     }
972   }
973   but->flags = flags;
974   assert((but->flags & BUT_PRESSABLE) || !(but->flags & BUT_KEYED));
975   but_draw(but);
976 }
977 
978 
blinkCursor(ButTimer * timer)979 static ButOut  blinkCursor(ButTimer *timer)  {
980   But  *but = butTimer_packet(timer);
981   Txtin  *ti = but->iPacket;
982 
983   ti->cursorVisible = !(butTimer_ticks(timer) & 1);
984   ti_redrawCursor(but);
985   return(0);
986 }
987 
988 
slide(ButTimer * timer)989 static ButOut  slide(ButTimer *timer)  {
990   But  *but = butTimer_packet(timer);
991   Txtin  *ti = but->iPacket;
992   int  newXoff, oldXoff;
993 
994   if (ti->slideDir > 0)  {
995     oldXoff = ti->xoffset;
996     textInView(but, TRUE);
997     if (oldXoff != ti->xoffset)  {
998       ti->xoffset = oldXoff;
999       butTimer_destroy(ti->sTimer);
1000       ti->sTimer = NULL;
1001       return(0);
1002     }
1003   }
1004   newXoff = ti->xoffset + ti->slideDir * butTimer_ticks(timer);
1005   ti->xoffset = newXoff;
1006   textInView(but, ti->slideDir > 0);
1007   if (ti->xoffset != newXoff)  {
1008     butTimer_destroy(ti->sTimer);
1009     ti->sTimer = NULL;
1010   }
1011   butTimer_setTicks(timer, 0);
1012   but_draw(but);
1013   return(0);
1014 }
1015 
1016 
startSlide(But * but,int dir,bool fast)1017 static void  startSlide(But  *but, int dir, bool fast)  {
1018   struct timeval  zero;
1019   Txtin  *ti = but->iPacket;
1020   int  freq;
1021 
1022   zero.tv_sec = 0;
1023   zero.tv_usec = 0;
1024   freq = but->h;
1025   if (fast)
1026     freq *= 10;
1027   if (ti->sTimer != NULL)  {
1028     if ((ti->slideFreq == freq) && (ti->slideDir == dir))
1029       return;
1030     butTimer_destroy(ti->sTimer);
1031   }
1032   ti->slideDir = dir;
1033   ti->slideFreq = freq;
1034   ti->sTimer = butTimer_fCreate(but, but, zero, freq, FALSE, slide);
1035 }
1036 
1037 
enableCTimer(But * but)1038 static void  enableCTimer(But *but)  {
1039   Txtin *ti = but->iPacket;
1040   struct timeval  halfSec;
1041 
1042   assert(ti->cTimer == NULL);
1043   halfSec.tv_sec = 0;
1044   halfSec.tv_usec = 500000;
1045   ti->cTimer = butTimer_create(but, but, halfSec, halfSec, TRUE,
1046 			       blinkCursor);
1047 }
1048 
1049 
disableCTimer(But * but)1050 static void  disableCTimer(But *but)  {
1051   Txtin *ti = but->iPacket;
1052 
1053   if (ti->cTimer != NULL)  {
1054     butTimer_destroy(ti->cTimer);
1055     ti->cTimer = NULL;
1056   }
1057 }
1058 
1059 
butTextin_set(But * but,const char * newStr,bool propagate)1060 void  butTextin_set(But *but, const char *newStr, bool propagate)  {
1061   Txtin  *ti;
1062 
1063   assert(MAGIC(but));
1064   assert(but->action == &action);
1065   ti = but->iPacket;
1066   assert(MAGIC(ti));
1067   assert(strlen(newStr) <= ti->maxlen);
1068   strcpy(ti->str, newStr);
1069   ti->len = ti->loc = ti->cutend = strlen(newStr);
1070   textInView(but, TRUE);
1071   but_draw(but);
1072   if (propagate)
1073     ti->callback(but, ti->str);
1074 }
1075 
1076 
butTextin_setHidden(But * but,bool hidden)1077 void  butTextin_setHidden(But *but, bool hidden)  {
1078   Txtin  *ti;
1079   int  i;
1080 
1081   assert(MAGIC(but));
1082   assert(but->action == &action);
1083   ti = but->iPacket;
1084   assert(MAGIC(ti));
1085   if (hidden == ti->hidden)
1086     return;
1087   ti->hidden = hidden;
1088   if (hidden)  {
1089     ti->dispStr = wms_malloc(ti->maxlen);
1090     for (i = 0;  i < ti->maxlen;  ++i)  {
1091       ti->dispStr[i] = '*';
1092     }
1093   } else  {
1094     wms_free(ti->dispStr);
1095     ti->dispStr = ti->str;
1096   }
1097 }
1098 
butTextin_setSpecialKey(But * but,KeySym keysym,uint keyModifiers,uint modMask,ButOut callback (But * but,KeySym keysym,uint keyModifiers,void * context),void * context)1099 void  butTextin_setSpecialKey(But *but, KeySym keysym,
1100 			      uint keyModifiers, uint modMask,
1101 			      ButOut callback(But *but,
1102 					      KeySym keysym, uint keyModifiers,
1103 					      void *context),
1104 			      void *context) {
1105   Txtin  *ti;
1106   SpecKey *newSpec;
1107 
1108   assert(MAGIC(but));
1109   assert(but->action == &action);
1110   ti = but->iPacket;
1111   assert(MAGIC(ti));
1112   assert((keyModifiers & modMask) == keyModifiers);
1113   newSpec = wms_malloc(sizeof(SpecKey));
1114   MAGIC_SET(newSpec);
1115   newSpec->next = ti->specKeys;
1116   ti->specKeys = newSpec;
1117   newSpec->keysym = keysym;
1118   newSpec->keyModifiers = keyModifiers;
1119   newSpec->modMask = modMask;
1120   newSpec->callback = callback;
1121   newSpec->context = context;
1122 }
1123 
1124 #endif  /* X11_DISP */
1125