Fixed a crashing bug related to textfield selections, and reordered the
[wmaker-crm.git] / WINGs / wtextfield.c
blob419ee250f1c6e9f6f8615e893811da7729d01867
5 #include "WINGsP.h"
7 #include <X11/keysym.h>
8 #include <X11/Xatom.h>
10 #include <ctype.h>
12 #define CURSOR_BLINK_ON_DELAY 600
13 #define CURSOR_BLINK_OFF_DELAY 300
17 char *WMTextDidChangeNotification = "WMTextDidChangeNotification";
18 char *WMTextDidBeginEditingNotification = "WMTextDidBeginEditingNotification";
19 char *WMTextDidEndEditingNotification = "WMTextDidEndEditingNotification";
22 typedef struct W_TextField {
23 W_Class widgetClass;
24 W_View *view;
26 #if 0
27 struct W_TextField *nextField; /* next textfield in the chain */
28 struct W_TextField *prevField;
29 #endif
31 char *text;
32 int textLen; /* size of text */
33 int bufferSize; /* memory allocated for text */
35 int viewPosition; /* position of text being shown */
37 int cursorPosition; /* position of the insertion cursor */
39 short usableWidth;
40 short offsetWidth; /* offset of text from border */
42 WMRange selection;
43 WMRange prevselection;
45 WMFont *font;
47 WMTextFieldDelegate *delegate;
49 #if 0
50 WMHandlerID timerID; /* for cursor blinking */
51 #endif
52 struct {
53 WMAlignment alignment:2;
55 unsigned int bordered:1;
57 unsigned int beveled:1;
59 unsigned int enabled:1;
61 unsigned int focused:1;
63 unsigned int cursorOn:1;
65 unsigned int secure:1; /* password entry style */
67 unsigned int pointerGrabbed:1;
69 /**/
70 unsigned int notIllegalMovement:1;
71 } flags;
72 } TextField;
75 #define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\
76 if ((T)->delegate && (T)->delegate->C)\
77 (*(T)->delegate->C)((T)->delegate,notif);\
78 WMPostNotification(notif);\
79 WMReleaseNotification(notif);}
82 #define MIN_TEXT_BUFFER 2
83 #define TEXT_BUFFER_INCR 8
86 #define WM_EMACSKEYMASK ControlMask
88 #define WM_EMACSKEY_LEFT XK_b
89 #define WM_EMACSKEY_RIGHT XK_f
90 #define WM_EMACSKEY_HOME XK_a
91 #define WM_EMACSKEY_END XK_e
92 #define WM_EMACSKEY_BS XK_h
93 #define WM_EMACSKEY_DEL XK_d
97 #define DEFAULT_WIDTH 60
98 #define DEFAULT_HEIGHT 20
99 #define DEFAULT_BORDERED True
100 #define DEFAULT_ALIGNMENT WALeft
104 static void destroyTextField(TextField *tPtr);
105 static void paintTextField(TextField *tPtr);
107 static void handleEvents(XEvent *event, void *data);
108 static void handleTextFieldActionEvents(XEvent *event, void *data);
109 static void resizeTextField();
111 struct W_ViewProcedureTable _TextFieldViewProcedures = {
112 NULL,
113 resizeTextField,
114 NULL
118 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
119 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
121 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
122 &((tPtr)->text[(start)]), (end) - (start) + 1))
125 static void
126 memmv(char *dest, char *src, int size)
128 int i;
130 if (dest > src) {
131 for (i=size-1; i>=0; i--) {
132 dest[i] = src[i];
134 } else if (dest < src) {
135 for (i=0; i<size; i++) {
136 dest[i] = src[i];
142 static int
143 incrToFit(TextField *tPtr)
145 int vp = tPtr->viewPosition;
147 while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
148 tPtr->viewPosition++;
150 return vp!=tPtr->viewPosition;
154 static int
155 incrToFit2(TextField *tPtr)
157 int vp = tPtr->viewPosition;
158 while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
159 >= tPtr->usableWidth)
160 tPtr->viewPosition++;
163 return vp!=tPtr->viewPosition;
167 static void
168 decrToFit(TextField *tPtr)
170 while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->usableWidth
171 && tPtr->viewPosition>0)
172 tPtr->viewPosition--;
175 #undef TEXT_WIDTH
176 #undef TEXT_WIDTH2
178 static Bool
179 requestHandler(WMWidget *w, Atom selection, Atom target, Atom *type,
180 void **value, unsigned *length, int *format)
182 TextField *tPtr = w;
183 int count,count2;
184 Display *dpy = tPtr->view->screen->display;
185 Atom _TARGETS;
186 char *text;
187 text = XGetAtomName(tPtr->view->screen->display,target);
188 XFree(text);
189 text = XGetAtomName(tPtr->view->screen->display,selection);
190 XFree(text);
192 *format = 32;
193 *length = 0;
194 *value = NULL;
195 count = tPtr->selection.count < 0
196 ? tPtr->selection.position + tPtr->selection.count
197 : tPtr->selection.position;
199 if (target == XA_STRING ||
200 target == XInternAtom(dpy, "TEXT", False) ||
201 target == XInternAtom(dpy, "COMPOUND_TEXT", False)) {
202 *value = wstrdup(&(tPtr->text[count]));
203 *length = abs(tPtr->selection.count);
204 *format = 8;
205 *type = target;
206 return True;
209 _TARGETS = XInternAtom(dpy, "TARGETS", False);
210 if (target == _TARGETS) {
211 int *ptr;
213 *length = 4;
214 ptr = *value = (char *) wmalloc(4 * sizeof(Atom));
215 ptr[0] = _TARGETS;
216 ptr[1] = XA_STRING;
217 ptr[2] = XInternAtom(dpy, "TEXT", False);
218 ptr[3] = XInternAtom(dpy, "COMPOUND_TEXT", False);
219 *type = target;
220 return True;
223 *target = XA_PRIMARY;
225 return False;
230 static void
231 lostHandler(WMWidget *w, Atom selection)
233 TextField *tPtr = (WMTextField*)w;
235 tPtr->selection.count = 0;
236 paintTextField(tPtr);
240 WMTextField*
241 WMCreateTextField(WMWidget *parent)
243 TextField *tPtr;
245 tPtr = wmalloc(sizeof(TextField));
246 memset(tPtr, 0, sizeof(TextField));
248 tPtr->widgetClass = WC_TextField;
250 tPtr->view = W_CreateView(W_VIEW(parent));
251 if (!tPtr->view) {
252 free(tPtr);
253 return NULL;
255 tPtr->view->self = tPtr;
257 tPtr->view->attribFlags |= CWCursor;
258 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
260 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
262 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
263 tPtr->text[0] = 0;
264 tPtr->textLen = 0;
265 tPtr->bufferSize = MIN_TEXT_BUFFER;
267 tPtr->flags.enabled = 1;
269 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
270 |FocusChangeMask, handleEvents, tPtr);
272 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
274 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
276 tPtr->flags.bordered = DEFAULT_BORDERED;
277 tPtr->flags.beveled = True;
278 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
279 tPtr->offsetWidth =
280 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
282 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
283 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
284 handleTextFieldActionEvents, tPtr);
286 WMCreateSelectionHandler(tPtr, XA_PRIMARY, CurrentTime, requestHandler,
287 lostHandler, NULL);
290 tPtr->flags.cursorOn = 1;
292 return tPtr;
296 void
297 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
299 tPtr->delegate = delegate;
303 void
304 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
306 int len;
308 CHECK_CLASS(tPtr, WC_TextField);
310 if (!text)
311 return;
313 len = strlen(text);
315 /* check if buffer will hold the text */
316 if (len + tPtr->textLen >= tPtr->bufferSize) {
317 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
318 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
321 if (position < 0 || position >= tPtr->textLen) {
322 /* append the text at the end */
323 strcat(tPtr->text, text);
325 incrToFit(tPtr);
327 tPtr->textLen += len;
328 tPtr->cursorPosition += len;
329 } else {
330 /* insert text at position */
331 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
332 tPtr->textLen-position+1);
334 memcpy(&(tPtr->text[position]), text, len);
336 tPtr->textLen += len;
337 if (position >= tPtr->cursorPosition) {
338 tPtr->cursorPosition += len;
339 incrToFit2(tPtr);
340 } else {
341 incrToFit(tPtr);
345 paintTextField(tPtr);
349 void
350 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
352 CHECK_CLASS(tPtr, WC_TextField);
354 if (range.position >= tPtr->textLen)
355 return;
357 if (range.count < 1) {
358 if (range.position < 0)
359 range.position = 0;
360 tPtr->text[range.position] = 0;
361 tPtr->textLen = range.position;
363 tPtr->cursorPosition = 0;
364 tPtr->viewPosition = 0;
365 } else {
366 if (range.position + range.count > tPtr->textLen)
367 range.count = tPtr->textLen - range.position;
368 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
369 tPtr->textLen - (range.position+range.count) + 1);
370 tPtr->textLen -= range.count;
372 if (tPtr->cursorPosition > range.position)
373 tPtr->cursorPosition -= range.count;
375 decrToFit(tPtr);
378 paintTextField(tPtr);
383 char*
384 WMGetTextFieldText(WMTextField *tPtr)
386 CHECK_CLASS(tPtr, WC_TextField);
388 return wstrdup(tPtr->text);
392 void
393 WMSetTextFieldText(WMTextField *tPtr, char *text)
395 if ((text && strcmp(tPtr->text, text) == 0) ||
396 (!text && tPtr->textLen == 0))
397 return;
399 if (text==NULL) {
400 tPtr->text[0] = 0;
401 tPtr->textLen = 0;
402 } else {
403 tPtr->textLen = strlen(text);
405 if (tPtr->textLen >= tPtr->bufferSize) {
406 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
407 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
409 strcpy(tPtr->text, text);
412 if (tPtr->textLen < tPtr->cursorPosition)
413 tPtr->cursorPosition = tPtr->textLen;
415 tPtr->cursorPosition = tPtr->textLen;
416 tPtr->viewPosition = 0;
417 tPtr->selection.count = 0;
419 if (tPtr->view->flags.realized)
420 paintTextField(tPtr);
424 void
425 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
427 /* TODO: update font change after field is mapped */
428 WMReleaseFont(tPtr->font);
429 tPtr->font = WMRetainFont(font);
433 void
434 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
436 tPtr->flags.alignment = alignment;
437 if (alignment!=WALeft) {
438 wwarning("only left alignment is supported in textfields");
439 return;
442 if (tPtr->view->flags.realized) {
443 paintTextField(tPtr);
448 void
449 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
451 tPtr->flags.bordered = bordered;
453 if (tPtr->view->flags.realized) {
454 paintTextField(tPtr);
459 void
460 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
462 tPtr->flags.beveled = flag;
464 if (tPtr->view->flags.realized) {
465 paintTextField(tPtr);
471 void
472 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
474 tPtr->flags.secure = flag;
476 if (tPtr->view->flags.realized) {
477 paintTextField(tPtr);
482 Bool
483 WMGetTextFieldEditable(WMTextField *tPtr)
485 return tPtr->flags.enabled;
489 void
490 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
492 tPtr->flags.enabled = flag;
494 if (tPtr->view->flags.realized) {
495 paintTextField(tPtr);
500 void
501 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
503 if (tPtr->flags.enabled) {
504 if (range.position < 0) {
505 range.count += range.position;
506 range.count = (range.count < 0) ? 0 : range.count;
507 range.position = 0;
508 } else if (range.position > tPtr->textLen) {
509 range.position = tPtr->textLen;
510 range.count = 0;
513 if (range.position + range.count > tPtr->textLen)
514 range.count = tPtr->textLen - range.position;
516 tPtr->prevselection = tPtr->selection; /* check if this is needed */
518 tPtr->selection = range;
520 if (tPtr->view->flags.realized) {
521 paintTextField(tPtr);
527 void
528 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
530 if (tPtr->flags.enabled) {
531 if (position > tPtr->textLen)
532 position = tPtr->textLen;
534 tPtr->cursorPosition = position;
535 if (tPtr->view->flags.realized) {
536 paintTextField(tPtr);
542 void
543 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
545 CHECK_CLASS(tPtr, WC_TextField);
546 if (next == NULL) {
547 if (tPtr->view->nextFocusChain)
548 tPtr->view->nextFocusChain->prevFocusChain = NULL;
549 tPtr->view->nextFocusChain = NULL;
550 return;
553 CHECK_CLASS(next, WC_TextField);
555 if (tPtr->view->nextFocusChain)
556 tPtr->view->nextFocusChain->prevFocusChain = NULL;
557 if (next->view->prevFocusChain)
558 next->view->prevFocusChain->nextFocusChain = NULL;
560 tPtr->view->nextFocusChain = next->view;
561 next->view->prevFocusChain = tPtr->view;
565 void
566 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
568 CHECK_CLASS(tPtr, WC_TextField);
569 if (prev == NULL) {
570 if (tPtr->view->prevFocusChain)
571 tPtr->view->prevFocusChain->nextFocusChain = NULL;
572 tPtr->view->prevFocusChain = NULL;
573 return;
576 CHECK_CLASS(prev, WC_TextField);
578 if (tPtr->view->prevFocusChain)
579 tPtr->view->prevFocusChain->nextFocusChain = NULL;
580 if (prev->view->nextFocusChain)
581 prev->view->nextFocusChain->prevFocusChain = NULL;
583 tPtr->view->prevFocusChain = prev->view;
584 prev->view->nextFocusChain = tPtr->view;
588 static void
589 resizeTextField(WMTextField *tPtr, unsigned int width, unsigned int height)
591 W_ResizeView(tPtr->view, width, height);
593 tPtr->offsetWidth =
594 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
596 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
600 static char*
601 makeHiddenString(int length)
603 char *data = wmalloc(length+1);
605 memset(data, '*', length);
606 data[length] = '\0';
607 return data;
611 static void
612 paintCursor(TextField *tPtr)
614 int cx;
615 WMScreen *screen = tPtr->view->screen;
616 int textWidth;
617 char *text;
619 if (tPtr->flags.secure)
620 text = makeHiddenString(strlen(tPtr->text));
621 else
622 text = tPtr->text;
624 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
625 tPtr->cursorPosition-tPtr->viewPosition);
627 switch (tPtr->flags.alignment) {
628 case WARight:
629 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
630 if (textWidth < tPtr->usableWidth)
631 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
632 else
633 cx += tPtr->offsetWidth + 1;
634 break;
635 case WALeft:
636 cx += tPtr->offsetWidth + 1;
637 break;
638 case WAJustified:
639 /* not supported */
640 case WACenter:
641 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
642 if (textWidth < tPtr->usableWidth)
643 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
644 else
645 cx += tPtr->offsetWidth;
646 break;
649 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
650 cx, tPtr->offsetWidth, 1,
651 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
652 printf("%d %d\n",cx,tPtr->cursorPosition);
654 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
655 cx, tPtr->offsetWidth, cx,
656 tPtr->view->size.height - tPtr->offsetWidth - 1);
658 if (tPtr->flags.secure)
659 free(text);
664 static void
665 drawRelief(WMView *view, Bool beveled)
667 WMScreen *scr = view->screen;
668 Display *dpy = scr->display;
669 GC wgc;
670 GC lgc;
671 GC dgc;
672 int width = view->size.width;
673 int height = view->size.height;
675 dgc = WMColorGC(scr->darkGray);
677 if (!beveled) {
678 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
680 return;
682 wgc = WMColorGC(scr->white);
683 lgc = WMColorGC(scr->gray);
685 /* top left */
686 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
687 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
689 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
690 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
692 /* bottom right */
693 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
694 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
696 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
697 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
701 static void
702 paintTextField(TextField *tPtr)
704 W_Screen *screen = tPtr->view->screen;
705 W_View *view = tPtr->view;
706 W_View viewbuffer;
707 int tx, ty, tw, th;
708 int rx;
709 int bd;
710 int totalWidth;
711 char *text;
712 Pixmap drawbuffer;
715 if (!view->flags.realized || !view->flags.mapped)
716 return;
718 if (!tPtr->flags.bordered) {
719 bd = 0;
720 } else {
721 bd = 2;
724 if (tPtr->flags.secure) {
725 text = makeHiddenString(strlen(tPtr->text));
726 } else {
727 text = tPtr->text;
730 totalWidth = tPtr->view->size.width - 2*bd;
732 drawbuffer = XCreatePixmap(screen->display, view->window,
733 view->size.width, view->size.height, screen->depth);
734 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
735 0,0, view->size.width,view->size.height);
736 /* this is quite dirty */
737 viewbuffer.screen = view->screen;
738 viewbuffer.size = view->size;
739 viewbuffer.window = drawbuffer;
742 if (tPtr->textLen > 0) {
743 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
744 tPtr->textLen - tPtr->viewPosition);
746 th = WMFontHeight(tPtr->font);
748 ty = tPtr->offsetWidth;
749 switch (tPtr->flags.alignment) {
750 case WALeft:
751 tx = tPtr->offsetWidth + 1;
752 if (tw < tPtr->usableWidth)
753 XFillRectangle(screen->display, drawbuffer,
754 WMColorGC(screen->white),
755 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
756 break;
758 case WACenter:
759 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
760 if (tw < tPtr->usableWidth)
761 XClearArea(screen->display, view->window, bd, bd,
762 totalWidth, view->size.height-2*bd, False);
763 break;
765 default:
766 case WARight:
767 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
768 if (tw < tPtr->usableWidth)
769 XClearArea(screen->display, view->window, bd, bd,
770 totalWidth-tw, view->size.height-2*bd, False);
771 break;
774 if (!tPtr->flags.enabled)
775 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
777 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
778 tPtr->font, tx, ty,
779 &(text[tPtr->viewPosition]),
780 tPtr->textLen - tPtr->viewPosition);
782 if (tPtr->selection.count) {
783 int count,count2;
785 count = tPtr->selection.count < 0
786 ? tPtr->selection.position + tPtr->selection.count
787 : tPtr->selection.position;
788 count2 = abs(tPtr->selection.count);
789 if (count < tPtr->viewPosition) {
790 count2 = abs(count2 - abs(tPtr->viewPosition - count));
791 count = tPtr->viewPosition;
795 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
796 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
798 XSetBackground(screen->display, screen->textFieldGC,
799 screen->gray->color.pixel);
801 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
802 tPtr->font, rx, ty, &(text[count]),
803 count2);
805 XSetBackground(screen->display, screen->textFieldGC,
806 screen->white->color.pixel);
809 if (!tPtr->flags.enabled)
810 WMSetColorInGC(screen->black, screen->textFieldGC);
811 } else {
812 XFillRectangle(screen->display, drawbuffer,
813 WMColorGC(screen->white),
814 bd,bd, totalWidth,view->size.height-2*bd);
817 /* draw relief */
818 if (tPtr->flags.bordered) {
819 drawRelief(&viewbuffer, tPtr->flags.beveled);
822 if (tPtr->flags.secure)
823 free(text);
824 XCopyArea(screen->display, drawbuffer, view->window,
825 screen->copyGC, 0,0, view->size.width,
826 view->size.height,0,0);
827 XFreePixmap(screen->display, drawbuffer);
829 /* draw cursor */
830 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
831 paintCursor(tPtr);
836 #if 0
837 static void
838 blinkCursor(void *data)
840 TextField *tPtr = (TextField*)data;
842 if (tPtr->flags.cursorOn) {
843 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
844 data);
845 } else {
846 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
847 data);
849 paintCursor(tPtr);
850 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
852 #endif
855 static void
856 handleEvents(XEvent *event, void *data)
858 TextField *tPtr = (TextField*)data;
860 CHECK_CLASS(data, WC_TextField);
863 switch (event->type) {
864 case FocusIn:
865 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
866 return;
867 tPtr->flags.focused = 1;
868 #if 0
869 if (!tPtr->timerID) {
870 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
871 blinkCursor, tPtr);
873 #endif
874 paintTextField(tPtr);
876 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
878 tPtr->flags.notIllegalMovement = 0;
879 break;
881 case FocusOut:
882 tPtr->flags.focused = 0;
883 #if 0
884 if (tPtr->timerID)
885 WMDeleteTimerHandler(tPtr->timerID);
886 tPtr->timerID = NULL;
887 #endif
889 paintTextField(tPtr);
890 if (!tPtr->flags.notIllegalMovement) {
891 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
892 (void*)WMIllegalTextMovement);
894 break;
896 case Expose:
897 if (event->xexpose.count!=0)
898 break;
899 paintTextField(tPtr);
900 break;
902 case DestroyNotify:
903 destroyTextField(tPtr);
904 break;
909 static void
910 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
912 char buffer[64];
913 KeySym ksym;
914 int count, refresh = 0;
915 int control_pressed = 0;
917 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK) {
918 control_pressed = 1;
921 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
922 buffer[count] = '\0';
924 if (!(event->xkey.state & ShiftMask)) {
925 if (tPtr->selection.count)
926 refresh = 1;
927 tPtr->prevselection = tPtr->selection;
928 tPtr->selection.position = tPtr->cursorPosition;
929 tPtr->selection.count = 0;
932 /* Be careful in any case in this switch statement, never to call
933 * to more than a function that can generate text change notifications.
934 * Only one text change notification should be sent in any case.
935 * Else hazardous things can happen.
936 * Maybe we need a better solution than the function wrapper to inform
937 * functions that change text in text fields, if they need to send a
938 * change notification or not. -Dan
940 switch (ksym) {
941 case XK_Tab:
942 case XK_ISO_Left_Tab:
943 if (event->xkey.state & ShiftMask) {
944 if (tPtr->view->prevFocusChain) {
945 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
946 tPtr->view->prevFocusChain);
947 tPtr->flags.notIllegalMovement = 1;
949 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
950 (void*)WMBacktabTextMovement);
951 } else {
952 if (tPtr->view->nextFocusChain) {
953 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
954 tPtr->view->nextFocusChain);
955 tPtr->flags.notIllegalMovement = 1;
957 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
958 (void*)WMTabTextMovement);
960 break;
962 case XK_Return:
963 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
964 (void*)WMReturnTextMovement);
965 break;
967 case WM_EMACSKEY_LEFT:
968 if (!control_pressed) {
969 goto normal_key;
971 case XK_KP_Left:
972 case XK_Left:
973 if (tPtr->cursorPosition > 0) {
974 paintCursor(tPtr);
975 if (event->xkey.state & ControlMask) {
976 int i;
977 for (i = tPtr->cursorPosition - 1; i >= 0; i--)
978 if (tPtr->text[i] == ' ' || i == 0) {
979 tPtr->cursorPosition = i;
980 break;
982 } else {
983 tPtr->cursorPosition--;
985 if (tPtr->cursorPosition < tPtr->viewPosition) {
986 tPtr->viewPosition = tPtr->cursorPosition;
987 refresh = 1;
988 } else {
989 paintCursor(tPtr);
992 break;
994 case WM_EMACSKEY_RIGHT:
995 if (!control_pressed) {
996 goto normal_key;
998 case XK_KP_Right:
999 case XK_Right:
1000 if (tPtr->cursorPosition < tPtr->textLen) {
1001 paintCursor(tPtr);
1002 if (event->xkey.state & ControlMask) {
1003 int i;
1004 for (i = tPtr->cursorPosition + 1; i <= tPtr->textLen; i++)
1005 if (tPtr->text[i] == ' ' || i == tPtr->textLen) {
1006 tPtr->cursorPosition = i;
1007 break;
1009 } else {
1010 tPtr->cursorPosition++;
1012 while (WMWidthOfString(tPtr->font,
1013 &(tPtr->text[tPtr->viewPosition]),
1014 tPtr->cursorPosition-tPtr->viewPosition)
1015 > tPtr->usableWidth) {
1016 tPtr->viewPosition++;
1017 refresh = 1;
1019 if (!refresh)
1020 paintCursor(tPtr);
1022 break;
1024 case WM_EMACSKEY_HOME:
1025 if (!control_pressed) {
1026 goto normal_key;
1028 case XK_KP_Home:
1029 case XK_Home:
1030 if (tPtr->cursorPosition > 0) {
1031 paintCursor(tPtr);
1032 tPtr->cursorPosition = 0;
1033 if (tPtr->viewPosition > 0) {
1034 tPtr->viewPosition = 0;
1035 refresh = 1;
1036 } else {
1037 paintCursor(tPtr);
1040 break;
1042 case WM_EMACSKEY_END:
1043 if (!control_pressed) {
1044 goto normal_key;
1046 case XK_KP_End:
1047 case XK_End:
1048 if (tPtr->cursorPosition < tPtr->textLen) {
1049 paintCursor(tPtr);
1050 tPtr->cursorPosition = tPtr->textLen;
1051 tPtr->viewPosition = 0;
1052 while (WMWidthOfString(tPtr->font,
1053 &(tPtr->text[tPtr->viewPosition]),
1054 tPtr->textLen-tPtr->viewPosition)
1055 > tPtr->usableWidth) {
1056 tPtr->viewPosition++;
1057 refresh = 1;
1059 if (!refresh)
1060 paintCursor(tPtr);
1062 break;
1064 case WM_EMACSKEY_BS:
1065 if (!control_pressed) {
1066 goto normal_key;
1068 case XK_BackSpace:
1069 if (tPtr->cursorPosition > 0) {
1070 WMRange range;
1072 if (tPtr->prevselection.count) {
1073 range.position = tPtr->prevselection.count < 0
1074 ? tPtr->prevselection.position + tPtr->prevselection.count
1075 : tPtr->prevselection.position;
1077 range.count = abs(tPtr->prevselection.count);
1078 } else {
1079 range.position = tPtr->cursorPosition - 1;
1080 range.count = 1;
1082 WMDeleteTextFieldRange(tPtr, range);
1083 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1084 (void*)WMDeleteTextEvent);
1086 break;
1088 case WM_EMACSKEY_DEL:
1089 if (!control_pressed) {
1090 goto normal_key;
1092 case XK_KP_Delete:
1093 case XK_Delete:
1094 if (tPtr->cursorPosition < tPtr->textLen || tPtr->prevselection.count) {
1095 WMRange range;
1097 if (tPtr->prevselection.count) {
1098 range.position = tPtr->prevselection.count < 0
1099 ? tPtr->prevselection.position + tPtr->prevselection.count
1100 : tPtr->prevselection.position;
1102 range.count = abs(tPtr->prevselection.count);
1103 } else {
1104 range.position = tPtr->cursorPosition;
1105 range.count = 1;
1107 WMDeleteTextFieldRange(tPtr, range);
1108 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1109 (void*)WMDeleteTextEvent);
1111 break;
1113 normal_key:
1114 default:
1115 if (count > 0 && !iscntrl(buffer[0])) {
1116 WMRange range;
1118 if (tPtr->prevselection.count) {
1119 range.position = tPtr->prevselection.count < 0
1120 ? tPtr->prevselection.position + tPtr->prevselection.count
1121 : tPtr->prevselection.position;
1123 range.count = abs(tPtr->prevselection.count);
1124 } else {
1125 range.position = tPtr->cursorPosition;
1126 range.count = 1;
1128 if (tPtr->prevselection.count)
1129 WMDeleteTextFieldRange(tPtr, range);
1130 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1131 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1132 (void*)WMInsertTextEvent);
1133 } else {
1134 return;
1136 break;
1138 if (event->xkey.state & ShiftMask) {
1139 if (tPtr->selection.count == 0)
1140 tPtr->selection.position = tPtr->cursorPosition;
1141 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1142 refresh = 1;
1144 tPtr->prevselection.count = 0;
1145 if (refresh) {
1146 paintTextField(tPtr);
1151 static int
1152 pointToCursorPosition(TextField *tPtr, int x)
1154 int a, b, mid;
1155 int tw;
1157 if (tPtr->flags.bordered)
1158 x -= 2;
1160 a = tPtr->viewPosition;
1161 b = tPtr->viewPosition + tPtr->textLen;
1162 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1163 tPtr->textLen - tPtr->viewPosition) < x)
1164 return tPtr->textLen;
1166 while (a < b && b-a>1) {
1167 mid = (a+b)/2;
1168 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1169 mid - tPtr->viewPosition);
1170 if (tw > x)
1171 b = mid;
1172 else if (tw < x)
1173 a = mid;
1174 else
1175 return mid;
1177 return (a+b)/2;
1181 static void
1182 handleTextFieldActionEvents(XEvent *event, void *data)
1184 TextField *tPtr = (TextField*)data;
1185 static int move;
1187 CHECK_CLASS(data, WC_TextField);
1189 switch (event->type) {
1190 case KeyPress:
1191 if (tPtr->flags.enabled && tPtr->flags.focused) {
1192 handleTextFieldKeyPress(tPtr, event);
1193 XGrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen),
1194 W_VIEW(tPtr)->window, False,
1195 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1196 GrabModeAsync, GrabModeAsync, None,
1197 W_VIEW(tPtr)->screen->invisibleCursor,
1198 CurrentTime);
1199 tPtr->flags.pointerGrabbed = 1;
1201 break;
1203 case MotionNotify:
1205 if (tPtr->flags.pointerGrabbed) {
1206 tPtr->flags.pointerGrabbed = 0;
1207 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1210 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1212 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1213 tPtr->usableWidth) {
1214 if (WMWidthOfString(tPtr->font,
1215 &(tPtr->text[tPtr->viewPosition]),
1216 tPtr->cursorPosition-tPtr->viewPosition)
1217 > tPtr->usableWidth) {
1218 tPtr->viewPosition++;
1220 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1221 paintCursor(tPtr);
1222 tPtr->viewPosition--;
1225 if (!tPtr->selection.count) {
1226 tPtr->selection.position = tPtr->cursorPosition;
1229 tPtr->cursorPosition =
1230 pointToCursorPosition(tPtr, event->xmotion.x);
1232 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1235 printf("notify %d %d\n",event->xmotion.x,tPtr->usableWidth);
1238 paintCursor(tPtr);
1239 paintTextField(tPtr);
1242 if (move) {
1243 XSetSelectionOwner(tPtr->view->screen->display,
1244 XA_PRIMARY, tPtr->view->window, CurrentTime);
1246 break;
1248 case ButtonPress:
1249 if (tPtr->flags.pointerGrabbed) {
1250 tPtr->flags.pointerGrabbed = 0;
1251 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1252 break;
1255 move = 1;
1256 switch (tPtr->flags.alignment) {
1257 int textWidth;
1258 case WARight:
1259 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1260 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1261 WMSetFocusToWidget(tPtr);
1262 } else if (tPtr->flags.focused) {
1263 tPtr->selection.count = 0;
1265 if(textWidth < tPtr->usableWidth){
1266 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1267 event->xbutton.x - tPtr->usableWidth
1268 + textWidth);
1270 else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1271 event->xbutton.x);
1273 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1274 event->xbutton.x);
1275 tPtr->cursorPosition += tPtr->usableWidth - textWidth;
1278 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1279 event->xbutton.x);
1281 paintTextField(tPtr);
1282 break;
1284 case WALeft:
1285 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1286 WMSetFocusToWidget(tPtr);
1287 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1288 event->xbutton.x);
1289 paintTextField(tPtr);
1290 } else if (tPtr->flags.focused) {
1291 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1292 event->xbutton.x);
1293 tPtr->selection.count = 0;
1294 paintTextField(tPtr);
1296 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1297 char *text;
1299 text = W_GetTextSelection(tPtr->view->screen, XA_PRIMARY);
1301 if (!text) {
1302 text = W_GetTextSelection(tPtr->view->screen,
1303 tPtr->view->screen->clipboardAtom);
1305 if (!text) {
1306 text = W_GetTextSelection(tPtr->view->screen,
1307 XA_CUT_BUFFER0);
1309 if (text) {
1310 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1311 XFree(text);
1312 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1313 (void*)WMInsertTextEvent);
1316 break;
1318 break;
1320 case ButtonRelease:
1321 if (tPtr->flags.pointerGrabbed) {
1322 tPtr->flags.pointerGrabbed = 0;
1323 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1326 move = 0;
1327 break;
1332 static void
1333 destroyTextField(TextField *tPtr)
1335 #if 0
1336 if (tPtr->timerID)
1337 WMDeleteTimerHandler(tPtr->timerID);
1338 #endif
1340 WMReleaseFont(tPtr->font);
1342 if (tPtr->text)
1343 free(tPtr->text);
1345 free(tPtr);