Rewrote idle and input handlers using WMBag to avoid a functional problem of
[wmaker-crm.git] / WINGs / wtextfield.c
blobde9e692509634a9e92c1a5a8f27c88be3d8c1f7b
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;
44 WMFont *font;
46 WMTextFieldDelegate *delegate;
48 #if 0
49 WMHandlerID timerID; /* for cursor blinking */
50 #endif
51 struct {
52 WMAlignment alignment:2;
54 unsigned int bordered:1;
56 unsigned int beveled:1;
58 unsigned int enabled:1;
60 unsigned int focused:1;
62 unsigned int cursorOn:1;
64 unsigned int secure:1; /* password entry style */
66 unsigned int pointerGrabbed:1;
68 /**/
69 unsigned int notIllegalMovement:1;
70 } flags;
71 } TextField;
74 #define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\
75 if ((T)->delegate && (T)->delegate->C)\
76 (*(T)->delegate->C)((T)->delegate,notif);\
77 WMPostNotification(notif);\
78 WMReleaseNotification(notif);}
81 #define MIN_TEXT_BUFFER 2
82 #define TEXT_BUFFER_INCR 8
85 #define WM_EMACSKEYMASK ControlMask
87 #define WM_EMACSKEY_LEFT XK_b
88 #define WM_EMACSKEY_RIGHT XK_f
89 #define WM_EMACSKEY_HOME XK_a
90 #define WM_EMACSKEY_END XK_e
91 #define WM_EMACSKEY_BS XK_h
92 #define WM_EMACSKEY_DEL XK_d
96 #define DEFAULT_WIDTH 60
97 #define DEFAULT_HEIGHT 20
98 #define DEFAULT_BORDERED True
99 #define DEFAULT_ALIGNMENT WALeft
103 static void destroyTextField(TextField *tPtr);
104 static void paintTextField(TextField *tPtr);
106 static void handleEvents(XEvent *event, void *data);
107 static void handleTextFieldActionEvents(XEvent *event, void *data);
108 static void didResizeTextField();
110 struct W_ViewDelegate _TextFieldViewDelegate = {
111 NULL,
112 NULL,
113 didResizeTextField,
114 NULL,
115 NULL
119 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
120 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
122 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
123 &((tPtr)->text[(start)]), (end) - (start) + 1))
126 static void
127 normalizeRange(TextField *tPtr, WMRange *range)
129 if (range->position < 0 && range->count < 0)
130 range->count = 0;
132 if (range->count == 0) {
133 /*range->position = 0; why is this?*/
134 return;
137 /* (1,-2) ~> (0,1) ; (1,-1) ~> (0,1) ; (2,-1) ~> (1,1) */
138 if (range->count < 0) { /* && range->position >= 0 */
139 if (range->position + range->count < 0) {
140 range->count = range->position;
141 range->position = 0;
142 } else {
143 range->count = -range->count;
144 range->position -= range->count;
146 /* (-2,1) ~> (0,0) ; (-1,1) ~> (0,0) ; (-1,2) ~> (0,1) */
147 } else if (range->position < 0) { /* && range->count > 0 */
148 if (range->position + range->count < 0) {
149 range->position = range->count = 0;
150 } else {
151 range->count += range->position;
152 range->position = 0;
156 if (range->position + range->count > tPtr->textLen)
157 range->count = tPtr->textLen - range->position;
160 static void
161 memmv(char *dest, char *src, int size)
163 int i;
165 if (dest > src) {
166 for (i=size-1; i>=0; i--) {
167 dest[i] = src[i];
169 } else if (dest < src) {
170 for (i=0; i<size; i++) {
171 dest[i] = src[i];
177 static int
178 incrToFit(TextField *tPtr)
180 int vp = tPtr->viewPosition;
182 while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
183 tPtr->viewPosition++;
185 return vp!=tPtr->viewPosition;
189 static int
190 incrToFit2(TextField *tPtr)
192 int vp = tPtr->viewPosition;
193 while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
194 >= tPtr->usableWidth)
195 tPtr->viewPosition++;
198 return vp!=tPtr->viewPosition;
202 static void
203 decrToFit(TextField *tPtr)
205 while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->usableWidth
206 && tPtr->viewPosition>0)
207 tPtr->viewPosition--;
210 #undef TEXT_WIDTH
211 #undef TEXT_WIDTH2
213 static Bool
214 requestHandler(WMWidget *w, Atom selection, Atom target, Atom *type,
215 void **value, unsigned *length, int *format)
217 TextField *tPtr = w;
218 int count;
219 Display *dpy = tPtr->view->screen->display;
220 Atom _TARGETS;
221 char *text;
222 text = XGetAtomName(tPtr->view->screen->display,target);
223 XFree(text);
224 text = XGetAtomName(tPtr->view->screen->display,selection);
225 XFree(text);
227 *format = 32;
228 *length = 0;
229 *value = NULL;
230 count = tPtr->selection.count < 0
231 ? tPtr->selection.position + tPtr->selection.count
232 : tPtr->selection.position;
234 if (target == XA_STRING ||
235 target == XInternAtom(dpy, "TEXT", False) ||
236 target == XInternAtom(dpy, "COMPOUND_TEXT", False)) {
237 *value = wstrdup(&(tPtr->text[count]));
238 *length = abs(tPtr->selection.count);
239 *format = 8;
240 *type = target;
241 return True;
244 _TARGETS = XInternAtom(dpy, "TARGETS", False);
245 if (target == _TARGETS) {
246 int *ptr;
248 *length = 4;
249 ptr = *value = (char *) wmalloc(4 * sizeof(Atom));
250 ptr[0] = _TARGETS;
251 ptr[1] = XA_STRING;
252 ptr[2] = XInternAtom(dpy, "TEXT", False);
253 ptr[3] = XInternAtom(dpy, "COMPOUND_TEXT", False);
254 *type = target;
255 return True;
258 *target = XA_PRIMARY;
260 return False;
265 static void
266 lostHandler(WMWidget *w, Atom selection)
268 TextField *tPtr = (WMTextField*)w;
270 tPtr->selection.count = 0;
271 paintTextField(tPtr);
274 static void
275 _notification(void *observerData, WMNotification *notification)
277 WMTextField *to = (WMTextField*)observerData;
278 WMTextField *tw = (WMTextField*)WMGetNotificationClientData(notification);
279 if (to != tw) lostHandler(to, 0);
282 WMTextField*
283 WMCreateTextField(WMWidget *parent)
285 TextField *tPtr;
287 tPtr = wmalloc(sizeof(TextField));
288 memset(tPtr, 0, sizeof(TextField));
290 tPtr->widgetClass = WC_TextField;
292 tPtr->view = W_CreateView(W_VIEW(parent));
293 if (!tPtr->view) {
294 wfree(tPtr);
295 return NULL;
297 tPtr->view->self = tPtr;
299 tPtr->view->delegate = &_TextFieldViewDelegate;
301 tPtr->view->attribFlags |= CWCursor;
302 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
304 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
306 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
307 tPtr->text[0] = 0;
308 tPtr->textLen = 0;
309 tPtr->bufferSize = MIN_TEXT_BUFFER;
311 tPtr->flags.enabled = 1;
313 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
314 |FocusChangeMask, handleEvents, tPtr);
316 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
318 tPtr->flags.bordered = DEFAULT_BORDERED;
319 tPtr->flags.beveled = True;
320 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
321 tPtr->offsetWidth =
322 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
324 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
326 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
327 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
328 handleTextFieldActionEvents, tPtr);
330 WMCreateSelectionHandler(tPtr, XA_PRIMARY, CurrentTime, requestHandler,
331 lostHandler, NULL);
332 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
335 tPtr->flags.cursorOn = 1;
337 return tPtr;
341 void
342 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
344 CHECK_CLASS(tPtr, WC_TextField);
346 tPtr->delegate = delegate;
350 void
351 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
353 int len;
355 CHECK_CLASS(tPtr, WC_TextField);
357 if (!text)
358 return;
360 len = strlen(text);
362 /* check if buffer will hold the text */
363 if (len + tPtr->textLen >= tPtr->bufferSize) {
364 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
365 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
368 if (position < 0 || position >= tPtr->textLen) {
369 /* append the text at the end */
370 strcat(tPtr->text, text);
372 incrToFit(tPtr);
374 tPtr->textLen += len;
375 tPtr->cursorPosition += len;
376 } else {
377 /* insert text at position */
378 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
379 tPtr->textLen-position+1);
381 memcpy(&(tPtr->text[position]), text, len);
383 tPtr->textLen += len;
384 if (position >= tPtr->cursorPosition) {
385 tPtr->cursorPosition += len;
386 incrToFit2(tPtr);
387 } else {
388 incrToFit(tPtr);
392 paintTextField(tPtr);
395 void
396 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
398 CHECK_CLASS(tPtr, WC_TextField);
400 normalizeRange(tPtr, &range);
402 if (!range.count)
403 return;
405 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
406 tPtr->textLen - (range.position+range.count) + 1);
408 tPtr->textLen -= range.count;
410 /* try to keep cursorPosition at the same place */
411 tPtr->viewPosition -= range.count;
412 if (tPtr->viewPosition < 0)
413 tPtr->viewPosition = 0;
414 tPtr->cursorPosition = range.position;
416 decrToFit(tPtr);
418 paintTextField(tPtr);
423 char*
424 WMGetTextFieldText(WMTextField *tPtr)
426 CHECK_CLASS(tPtr, WC_TextField);
428 return wstrdup(tPtr->text);
432 void
433 WMSetTextFieldText(WMTextField *tPtr, char *text)
435 CHECK_CLASS(tPtr, WC_TextField);
437 if ((text && strcmp(tPtr->text, text) == 0) ||
438 (!text && tPtr->textLen == 0))
439 return;
441 if (text==NULL) {
442 tPtr->text[0] = 0;
443 tPtr->textLen = 0;
444 } else {
445 tPtr->textLen = strlen(text);
447 if (tPtr->textLen >= tPtr->bufferSize) {
448 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
449 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
451 strcpy(tPtr->text, text);
454 tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
455 tPtr->viewPosition = 0;
456 tPtr->selection.count = 0;
458 if (tPtr->view->flags.realized)
459 paintTextField(tPtr);
463 void
464 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
466 CHECK_CLASS(tPtr, WC_TextField);
468 tPtr->flags.alignment = alignment;
470 if (alignment!=WALeft) {
471 wwarning("only left alignment is supported in textfields");
472 return;
475 if (tPtr->view->flags.realized) {
476 paintTextField(tPtr);
481 void
482 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
484 CHECK_CLASS(tPtr, WC_TextField);
486 tPtr->flags.bordered = bordered;
488 if (tPtr->view->flags.realized) {
489 paintTextField(tPtr);
494 void
495 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
497 CHECK_CLASS(tPtr, WC_TextField);
499 tPtr->flags.beveled = flag;
501 if (tPtr->view->flags.realized) {
502 paintTextField(tPtr);
508 void
509 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
511 CHECK_CLASS(tPtr, WC_TextField);
513 tPtr->flags.secure = flag;
515 if (tPtr->view->flags.realized) {
516 paintTextField(tPtr);
521 Bool
522 WMGetTextFieldEditable(WMTextField *tPtr)
524 CHECK_CLASS(tPtr, WC_TextField);
526 return tPtr->flags.enabled;
530 void
531 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
533 CHECK_CLASS(tPtr, WC_TextField);
535 tPtr->flags.enabled = flag;
537 if (tPtr->view->flags.realized) {
538 paintTextField(tPtr);
543 void
544 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
546 CHECK_CLASS(tPtr, WC_TextField);
548 if (tPtr->flags.enabled) {
549 normalizeRange(tPtr, &range);
551 tPtr->selection = range;
553 tPtr->cursorPosition = range.position + range.count;
555 if (tPtr->view->flags.realized) {
556 paintTextField(tPtr);
562 void
563 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
565 CHECK_CLASS(tPtr, WC_TextField);
567 if (tPtr->flags.enabled) {
568 if (position > tPtr->textLen)
569 position = tPtr->textLen;
571 tPtr->cursorPosition = position;
572 if (tPtr->view->flags.realized) {
573 paintTextField(tPtr);
579 void
580 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
582 CHECK_CLASS(tPtr, WC_TextField);
583 if (next == NULL) {
584 if (tPtr->view->nextFocusChain)
585 tPtr->view->nextFocusChain->prevFocusChain = NULL;
586 tPtr->view->nextFocusChain = NULL;
587 return;
590 CHECK_CLASS(next, WC_TextField);
592 if (tPtr->view->nextFocusChain)
593 tPtr->view->nextFocusChain->prevFocusChain = NULL;
594 if (next->view->prevFocusChain)
595 next->view->prevFocusChain->nextFocusChain = NULL;
597 tPtr->view->nextFocusChain = next->view;
598 next->view->prevFocusChain = tPtr->view;
602 void
603 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
605 CHECK_CLASS(tPtr, WC_TextField);
606 if (prev == NULL) {
607 if (tPtr->view->prevFocusChain)
608 tPtr->view->prevFocusChain->nextFocusChain = NULL;
609 tPtr->view->prevFocusChain = NULL;
610 return;
613 CHECK_CLASS(prev, WC_TextField);
615 if (tPtr->view->prevFocusChain)
616 tPtr->view->prevFocusChain->nextFocusChain = NULL;
617 if (prev->view->nextFocusChain)
618 prev->view->nextFocusChain->prevFocusChain = NULL;
620 tPtr->view->prevFocusChain = prev->view;
621 prev->view->nextFocusChain = tPtr->view;
625 void
626 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
628 CHECK_CLASS(tPtr, WC_TextField);
630 if (tPtr->font)
631 WMReleaseFont(tPtr->font);
632 tPtr->font = WMRetainFont(font);
634 tPtr->offsetWidth =
635 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
637 if (tPtr->view->flags.realized) {
638 paintTextField(tPtr);
644 WMFont*
645 WMGetTextFieldFont(WMTextField *tPtr)
647 return tPtr->font;
651 static void
652 didResizeTextField(W_ViewDelegate *self, WMView *view)
654 WMTextField *tPtr = (WMTextField*)view->self;
656 tPtr->offsetWidth =
657 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
659 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
663 static char*
664 makeHiddenString(int length)
666 char *data = wmalloc(length+1);
668 memset(data, '*', length);
669 data[length] = '\0';
671 return data;
675 static void
676 paintCursor(TextField *tPtr)
678 int cx;
679 WMScreen *screen = tPtr->view->screen;
680 int textWidth;
681 char *text;
683 if (tPtr->flags.secure)
684 text = makeHiddenString(strlen(tPtr->text));
685 else
686 text = tPtr->text;
688 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
689 tPtr->cursorPosition-tPtr->viewPosition);
691 switch (tPtr->flags.alignment) {
692 case WARight:
693 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
694 if (textWidth < tPtr->usableWidth)
695 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
696 else
697 cx += tPtr->offsetWidth + 1;
698 break;
699 case WALeft:
700 cx += tPtr->offsetWidth + 1;
701 break;
702 case WAJustified:
703 /* not supported */
704 case WACenter:
705 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
706 if (textWidth < tPtr->usableWidth)
707 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
708 else
709 cx += tPtr->offsetWidth;
710 break;
713 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
714 cx, tPtr->offsetWidth, 1,
715 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
716 printf("%d %d\n",cx,tPtr->cursorPosition);
718 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
719 cx, tPtr->offsetWidth, cx,
720 tPtr->view->size.height - tPtr->offsetWidth - 1);
722 if (tPtr->flags.secure)
723 wfree(text);
728 static void
729 drawRelief(WMView *view, Bool beveled)
731 WMScreen *scr = view->screen;
732 Display *dpy = scr->display;
733 GC wgc;
734 GC lgc;
735 GC dgc;
736 int width = view->size.width;
737 int height = view->size.height;
739 dgc = WMColorGC(scr->darkGray);
741 if (!beveled) {
742 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
744 return;
746 wgc = WMColorGC(scr->white);
747 lgc = WMColorGC(scr->gray);
749 /* top left */
750 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
751 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
753 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
754 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
756 /* bottom right */
757 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
758 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
760 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
761 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
765 static void
766 paintTextField(TextField *tPtr)
768 W_Screen *screen = tPtr->view->screen;
769 W_View *view = tPtr->view;
770 W_View viewbuffer;
771 int tx, ty, tw, th;
772 int rx;
773 int bd;
774 int totalWidth;
775 char *text;
776 Pixmap drawbuffer;
779 if (!view->flags.realized || !view->flags.mapped)
780 return;
782 if (!tPtr->flags.bordered) {
783 bd = 0;
784 } else {
785 bd = 2;
788 if (tPtr->flags.secure) {
789 text = makeHiddenString(strlen(tPtr->text));
790 } else {
791 text = tPtr->text;
794 totalWidth = tPtr->view->size.width - 2*bd;
796 drawbuffer = XCreatePixmap(screen->display, view->window,
797 view->size.width, view->size.height, screen->depth);
798 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
799 0,0, view->size.width,view->size.height);
800 /* this is quite dirty */
801 viewbuffer.screen = view->screen;
802 viewbuffer.size = view->size;
803 viewbuffer.window = drawbuffer;
806 if (tPtr->textLen > 0) {
807 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
808 tPtr->textLen - tPtr->viewPosition);
810 th = WMFontHeight(tPtr->font);
812 ty = tPtr->offsetWidth;
813 switch (tPtr->flags.alignment) {
814 case WALeft:
815 tx = tPtr->offsetWidth + 1;
816 if (tw < tPtr->usableWidth)
817 XFillRectangle(screen->display, drawbuffer,
818 WMColorGC(screen->white),
819 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
820 break;
822 case WACenter:
823 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
824 if (tw < tPtr->usableWidth)
825 XClearArea(screen->display, view->window, bd, bd,
826 totalWidth, view->size.height-2*bd, False);
827 break;
829 default:
830 case WARight:
831 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
832 if (tw < tPtr->usableWidth)
833 XClearArea(screen->display, view->window, bd, bd,
834 totalWidth-tw, view->size.height-2*bd, False);
835 break;
838 if (!tPtr->flags.enabled)
839 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
841 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
842 tPtr->font, tx, ty,
843 &(text[tPtr->viewPosition]),
844 tPtr->textLen - tPtr->viewPosition);
846 if (tPtr->selection.count) {
847 int count,count2;
849 count = tPtr->selection.count < 0
850 ? tPtr->selection.position + tPtr->selection.count
851 : tPtr->selection.position;
852 count2 = abs(tPtr->selection.count);
853 if (count < tPtr->viewPosition) {
854 count2 = abs(count2 - abs(tPtr->viewPosition - count));
855 count = tPtr->viewPosition;
859 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
860 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
862 XSetBackground(screen->display, screen->textFieldGC,
863 screen->gray->color.pixel);
865 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
866 tPtr->font, rx, ty, &(text[count]),
867 count2);
869 XSetBackground(screen->display, screen->textFieldGC,
870 screen->white->color.pixel);
873 if (!tPtr->flags.enabled)
874 WMSetColorInGC(screen->black, screen->textFieldGC);
875 } else {
876 XFillRectangle(screen->display, drawbuffer,
877 WMColorGC(screen->white),
878 bd,bd, totalWidth,view->size.height-2*bd);
881 /* draw relief */
882 if (tPtr->flags.bordered) {
883 drawRelief(&viewbuffer, tPtr->flags.beveled);
886 if (tPtr->flags.secure)
887 wfree(text);
888 XCopyArea(screen->display, drawbuffer, view->window,
889 screen->copyGC, 0,0, view->size.width,
890 view->size.height,0,0);
891 XFreePixmap(screen->display, drawbuffer);
893 /* draw cursor */
894 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
895 paintCursor(tPtr);
900 #if 0
901 static void
902 blinkCursor(void *data)
904 TextField *tPtr = (TextField*)data;
906 if (tPtr->flags.cursorOn) {
907 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
908 data);
909 } else {
910 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
911 data);
913 paintCursor(tPtr);
914 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
916 #endif
919 static void
920 handleEvents(XEvent *event, void *data)
922 TextField *tPtr = (TextField*)data;
924 CHECK_CLASS(data, WC_TextField);
927 switch (event->type) {
928 case FocusIn:
929 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
930 return;
931 tPtr->flags.focused = 1;
932 #if 0
933 if (!tPtr->timerID) {
934 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
935 blinkCursor, tPtr);
937 #endif
938 paintTextField(tPtr);
940 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
942 tPtr->flags.notIllegalMovement = 0;
943 break;
945 case FocusOut:
946 tPtr->flags.focused = 0;
947 #if 0
948 if (tPtr->timerID)
949 WMDeleteTimerHandler(tPtr->timerID);
950 tPtr->timerID = NULL;
951 #endif
953 paintTextField(tPtr);
954 if (!tPtr->flags.notIllegalMovement) {
955 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
956 (void*)WMIllegalTextMovement);
958 break;
960 case Expose:
961 if (event->xexpose.count!=0)
962 break;
963 paintTextField(tPtr);
964 break;
966 case DestroyNotify:
967 destroyTextField(tPtr);
968 break;
973 static void
974 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
976 char buffer[64];
977 KeySym ksym;
978 char *textEvent = NULL;
979 void *data = NULL;
980 int count, refresh = 0;
981 int control_pressed = 0;
982 int cancelSelection = 1;
984 /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
985 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
986 control_pressed = 1;
988 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
989 buffer[count] = '\0';
991 switch (ksym) {
992 case XK_Tab:
993 #ifdef XK_ISO_Left_Tab
994 case XK_ISO_Left_Tab:
995 #endif
996 if (event->xkey.state & ShiftMask) {
997 if (tPtr->view->prevFocusChain) {
998 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
999 tPtr->view->prevFocusChain);
1000 tPtr->flags.notIllegalMovement = 1;
1002 data = (void*)WMBacktabTextMovement;
1003 } else {
1004 if (tPtr->view->nextFocusChain) {
1005 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1006 tPtr->view->nextFocusChain);
1007 tPtr->flags.notIllegalMovement = 1;
1009 data = (void*)WMTabTextMovement;
1011 textEvent = WMTextDidEndEditingNotification;
1012 break;
1014 case XK_Return:
1015 data = (void*)WMReturnTextMovement;
1016 textEvent = WMTextDidEndEditingNotification;
1017 break;
1019 case WM_EMACSKEY_LEFT:
1020 if (!control_pressed) {
1021 goto normal_key;
1023 #ifdef XK_KP_Left
1024 case XK_KP_Left:
1025 #endif
1026 case XK_Left:
1027 if (tPtr->cursorPosition > 0) {
1028 paintCursor(tPtr);
1029 if (event->xkey.state & ControlMask) {
1030 int i = tPtr->cursorPosition - 1;
1032 while (i > 0 && tPtr->text[i] != ' ') i--;
1033 while (i > 0 && tPtr->text[i] == ' ') i--;
1035 tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
1036 } else
1037 tPtr->cursorPosition--;
1039 if (tPtr->cursorPosition < tPtr->viewPosition) {
1040 tPtr->viewPosition = tPtr->cursorPosition;
1041 refresh = 1;
1042 } else
1043 paintCursor(tPtr);
1045 if (event->xkey.state & ShiftMask)
1046 cancelSelection = 0;
1047 break;
1049 case WM_EMACSKEY_RIGHT:
1050 if (!control_pressed) {
1051 goto normal_key;
1053 #ifdef XK_KP_Right
1054 case XK_KP_Right:
1055 #endif
1056 case XK_Right:
1057 if (tPtr->cursorPosition < tPtr->textLen) {
1058 paintCursor(tPtr);
1059 if (event->xkey.state & ControlMask) {
1060 int i = tPtr->cursorPosition;
1062 while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
1063 while (tPtr->text[i] == ' ') i++;
1065 tPtr->cursorPosition = i;
1066 } else {
1067 tPtr->cursorPosition++;
1069 while (WMWidthOfString(tPtr->font,
1070 &(tPtr->text[tPtr->viewPosition]),
1071 tPtr->cursorPosition-tPtr->viewPosition)
1072 > tPtr->usableWidth) {
1073 tPtr->viewPosition++;
1074 refresh = 1;
1076 if (!refresh)
1077 paintCursor(tPtr);
1079 if (event->xkey.state & ShiftMask)
1080 cancelSelection = 0;
1081 break;
1083 case WM_EMACSKEY_HOME:
1084 if (!control_pressed) {
1085 goto normal_key;
1087 #ifdef XK_KP_Home
1088 case XK_KP_Home:
1089 #endif
1090 case XK_Home:
1091 if (tPtr->cursorPosition > 0) {
1092 paintCursor(tPtr);
1093 tPtr->cursorPosition = 0;
1094 if (tPtr->viewPosition > 0) {
1095 tPtr->viewPosition = 0;
1096 refresh = 1;
1097 } else
1098 paintCursor(tPtr);
1100 if (event->xkey.state & ShiftMask)
1101 cancelSelection = 0;
1102 break;
1104 case WM_EMACSKEY_END:
1105 if (!control_pressed) {
1106 goto normal_key;
1108 #ifdef XK_KP_End
1109 case XK_KP_End:
1110 #endif
1111 case XK_End:
1112 if (tPtr->cursorPosition < tPtr->textLen) {
1113 paintCursor(tPtr);
1114 tPtr->cursorPosition = tPtr->textLen;
1115 tPtr->viewPosition = 0;
1116 while (WMWidthOfString(tPtr->font,
1117 &(tPtr->text[tPtr->viewPosition]),
1118 tPtr->textLen-tPtr->viewPosition)
1119 > tPtr->usableWidth) {
1120 tPtr->viewPosition++;
1121 refresh = 1;
1123 if (!refresh)
1124 paintCursor(tPtr);
1126 if (event->xkey.state & ShiftMask)
1127 cancelSelection = 0;
1128 break;
1130 case WM_EMACSKEY_BS:
1131 if (!control_pressed) {
1132 goto normal_key;
1134 case XK_BackSpace:
1135 if (tPtr->selection.count) {
1136 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1137 data = (void*)WMDeleteTextEvent;
1138 textEvent = WMTextDidChangeNotification;
1139 } else if (tPtr->cursorPosition > 0) {
1140 WMRange range;
1141 range.position = tPtr->cursorPosition - 1;
1142 range.count = 1;
1143 WMDeleteTextFieldRange(tPtr, range);
1144 data = (void*)WMDeleteTextEvent;
1145 textEvent = WMTextDidChangeNotification;
1147 break;
1149 case WM_EMACSKEY_DEL:
1150 if (!control_pressed) {
1151 goto normal_key;
1153 #ifdef XK_KP_Delete
1154 case XK_KP_Delete:
1155 #endif
1156 case XK_Delete:
1157 if (tPtr->selection.count) {
1158 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1159 data = (void*)WMDeleteTextEvent;
1160 textEvent = WMTextDidChangeNotification;
1161 } else if (tPtr->cursorPosition < tPtr->textLen) {
1162 WMRange range;
1163 range.position = tPtr->cursorPosition;
1164 range.count = 1;
1165 WMDeleteTextFieldRange(tPtr, range);
1166 data = (void*)WMDeleteTextEvent;
1167 textEvent = WMTextDidChangeNotification;
1169 break;
1171 normal_key:
1172 default:
1173 if (count > 0 && !iscntrl(buffer[0])) {
1174 if (tPtr->selection.count)
1175 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1176 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1177 data = (void*)WMInsertTextEvent;
1178 textEvent = WMTextDidChangeNotification;
1179 } else {
1180 /* should we rather break and goto cancel selection below? -Dan */
1181 return;
1183 break;
1186 if (!cancelSelection) {
1187 if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
1188 WMNotification *notif;
1190 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1192 XSetSelectionOwner(tPtr->view->screen->display,
1193 XA_PRIMARY, tPtr->view->window,
1194 event->xbutton.time);
1195 notif = WMCreateNotification("_lostOwnership", NULL, tPtr);
1196 WMPostNotification(notif);
1197 WMReleaseNotification(notif);
1199 refresh = 1;
1201 } else {
1202 if (tPtr->selection.count) {
1203 tPtr->selection.count = 0;
1204 refresh = 1;
1206 tPtr->selection.position = tPtr->cursorPosition;
1209 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1211 if (textEvent) {
1212 WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);
1214 if (tPtr->delegate) {
1215 if (textEvent==WMTextDidBeginEditingNotification &&
1216 tPtr->delegate->didBeginEditing)
1217 (*tPtr->delegate->didBeginEditing)(tPtr->delegate, notif);
1218 else if (textEvent==WMTextDidEndEditingNotification &&
1219 tPtr->delegate->didEndEditing)
1220 (*tPtr->delegate->didEndEditing)(tPtr->delegate, notif);
1221 else if (textEvent==WMTextDidChangeNotification &&
1222 tPtr->delegate->didChange)
1223 (*tPtr->delegate->didChange)(tPtr->delegate, notif);
1226 WMPostNotification(notif);
1227 WMReleaseNotification(notif);
1230 if (refresh)
1231 paintTextField(tPtr);
1233 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1237 static int
1238 pointToCursorPosition(TextField *tPtr, int x)
1240 int a, b, mid;
1241 int tw;
1243 if (tPtr->flags.bordered)
1244 x -= 2;
1246 a = tPtr->viewPosition;
1247 b = tPtr->viewPosition + tPtr->textLen;
1248 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1249 tPtr->textLen - tPtr->viewPosition) < x)
1250 return tPtr->textLen;
1252 while (a < b && b-a>1) {
1253 mid = (a+b)/2;
1254 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1255 mid - tPtr->viewPosition);
1256 if (tw > x)
1257 b = mid;
1258 else if (tw < x)
1259 a = mid;
1260 else
1261 return mid;
1263 return (a+b)/2;
1267 static void
1268 handleTextFieldActionEvents(XEvent *event, void *data)
1270 TextField *tPtr = (TextField*)data;
1271 static int move = 0;
1272 static Time lastButtonReleasedEvent = 0;
1274 CHECK_CLASS(data, WC_TextField);
1276 switch (event->type) {
1277 case KeyPress:
1278 if (tPtr->flags.enabled && tPtr->flags.focused) {
1279 handleTextFieldKeyPress(tPtr, event);
1280 XGrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen),
1281 W_VIEW(tPtr)->window, False,
1282 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1283 GrabModeAsync, GrabModeAsync, None,
1284 W_VIEW(tPtr)->screen->invisibleCursor,
1285 CurrentTime);
1286 tPtr->flags.pointerGrabbed = 1;
1288 break;
1290 case MotionNotify:
1292 if (tPtr->flags.pointerGrabbed) {
1293 tPtr->flags.pointerGrabbed = 0;
1294 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1297 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1299 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1300 tPtr->usableWidth) {
1301 if (WMWidthOfString(tPtr->font,
1302 &(tPtr->text[tPtr->viewPosition]),
1303 tPtr->cursorPosition-tPtr->viewPosition)
1304 > tPtr->usableWidth) {
1305 tPtr->viewPosition++;
1307 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1308 paintCursor(tPtr);
1309 tPtr->viewPosition--;
1312 /*if (!tPtr->selection.count) {
1313 tPtr->selection.position = tPtr->cursorPosition;
1316 tPtr->cursorPosition =
1317 pointToCursorPosition(tPtr, event->xmotion.x);
1319 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1320 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1323 printf("notify %d %d\n",event->xmotion.x,tPtr->usableWidth);
1326 paintCursor(tPtr);
1327 paintTextField(tPtr);
1330 if (move) {
1331 XSetSelectionOwner(tPtr->view->screen->display,
1332 XA_PRIMARY, tPtr->view->window, event->xmotion.time);
1334 WMNotification *notif = WMCreateNotification("_lostOwnership",
1335 NULL,tPtr);
1336 WMPostNotification(notif);
1337 WMReleaseNotification(notif);
1340 break;
1342 case ButtonPress:
1343 if (tPtr->flags.pointerGrabbed) {
1344 tPtr->flags.pointerGrabbed = 0;
1345 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1346 break;
1349 move = 1;
1350 switch (tPtr->flags.alignment) {
1351 int textWidth;
1352 case WARight:
1353 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1354 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1355 WMSetFocusToWidget(tPtr);
1356 } else if (tPtr->flags.focused) {
1357 tPtr->selection.position = tPtr->cursorPosition;
1358 tPtr->selection.count = 0;
1360 if(textWidth < tPtr->usableWidth){
1361 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1362 event->xbutton.x - tPtr->usableWidth
1363 + textWidth);
1365 else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1366 event->xbutton.x);
1368 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1369 event->xbutton.x);
1370 tPtr->cursorPosition += tPtr->usableWidth - textWidth;
1373 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1374 event->xbutton.x);
1376 paintTextField(tPtr);
1377 break;
1379 case WALeft:
1380 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1381 WMSetFocusToWidget(tPtr);
1382 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1383 event->xbutton.x);
1384 paintTextField(tPtr);
1385 } else if (tPtr->flags.focused) {
1386 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1387 event->xbutton.x);
1388 tPtr->selection.position = tPtr->cursorPosition;
1389 tPtr->selection.count = 0;
1390 paintTextField(tPtr);
1392 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1393 char *text;
1395 text = W_GetTextSelection(tPtr->view->screen, XA_PRIMARY);
1397 if (!text) {
1398 text = W_GetTextSelection(tPtr->view->screen,
1399 tPtr->view->screen->clipboardAtom);
1401 if (!text) {
1402 text = W_GetTextSelection(tPtr->view->screen,
1403 XA_CUT_BUFFER0);
1405 if (text) {
1406 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1407 XFree(text);
1408 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1409 (void*)WMInsertTextEvent);
1412 break;
1414 break;
1416 case ButtonRelease:
1417 if (tPtr->flags.pointerGrabbed) {
1418 tPtr->flags.pointerGrabbed = 0;
1419 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1422 move = 0;
1424 if (event->xbutton.time - lastButtonReleasedEvent
1425 <= WINGsConfiguration.doubleClickDelay) {
1426 tPtr->selection.position = 0;
1427 tPtr->selection.count = tPtr->textLen;
1428 paintTextField(tPtr);
1429 XSetSelectionOwner(tPtr->view->screen->display,
1430 XA_PRIMARY, tPtr->view->window, event->xbutton.time);
1432 WMNotification *notif = WMCreateNotification("_lostOwnership",
1433 NULL,tPtr);
1434 WMPostNotification(notif);
1435 WMReleaseNotification(notif);
1438 lastButtonReleasedEvent = event->xbutton.time;
1440 break;
1445 static void
1446 destroyTextField(TextField *tPtr)
1448 #if 0
1449 if (tPtr->timerID)
1450 WMDeleteTimerHandler(tPtr->timerID);
1451 #endif
1453 WMReleaseFont(tPtr->font);
1454 WMDeleteSelectionHandler(tPtr, XA_PRIMARY);
1455 WMRemoveNotificationObserver(tPtr);
1457 if (tPtr->text)
1458 wfree(tPtr->text);
1460 wfree(tPtr);