Fixed some problems in 0.60.0
[wmaker-crm.git] / WINGs / wtextfield.c
blob504c628ebc7f749418897a81e65f539558b7a5bc
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 didResizeTextField();
111 struct W_ViewDelegate _TextFieldViewDelegate = {
112 NULL,
113 NULL,
114 didResizeTextField,
115 NULL,
116 NULL
120 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
121 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
123 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
124 &((tPtr)->text[(start)]), (end) - (start) + 1))
127 static void
128 memmv(char *dest, char *src, int size)
130 int i;
132 if (dest > src) {
133 for (i=size-1; i>=0; i--) {
134 dest[i] = src[i];
136 } else if (dest < src) {
137 for (i=0; i<size; i++) {
138 dest[i] = src[i];
144 static int
145 incrToFit(TextField *tPtr)
147 int vp = tPtr->viewPosition;
149 while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
150 tPtr->viewPosition++;
152 return vp!=tPtr->viewPosition;
156 static int
157 incrToFit2(TextField *tPtr)
159 int vp = tPtr->viewPosition;
160 while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
161 >= tPtr->usableWidth)
162 tPtr->viewPosition++;
165 return vp!=tPtr->viewPosition;
169 static void
170 decrToFit(TextField *tPtr)
172 while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->usableWidth
173 && tPtr->viewPosition>0)
174 tPtr->viewPosition--;
177 #undef TEXT_WIDTH
178 #undef TEXT_WIDTH2
180 static Bool
181 requestHandler(WMWidget *w, Atom selection, Atom target, Atom *type,
182 void **value, unsigned *length, int *format)
184 TextField *tPtr = w;
185 int count;
186 Display *dpy = tPtr->view->screen->display;
187 Atom _TARGETS;
188 char *text;
189 text = XGetAtomName(tPtr->view->screen->display,target);
190 XFree(text);
191 text = XGetAtomName(tPtr->view->screen->display,selection);
192 XFree(text);
194 *format = 32;
195 *length = 0;
196 *value = NULL;
197 count = tPtr->selection.count < 0
198 ? tPtr->selection.position + tPtr->selection.count
199 : tPtr->selection.position;
201 if (target == XA_STRING ||
202 target == XInternAtom(dpy, "TEXT", False) ||
203 target == XInternAtom(dpy, "COMPOUND_TEXT", False)) {
204 *value = wstrdup(&(tPtr->text[count]));
205 *length = abs(tPtr->selection.count);
206 *format = 8;
207 *type = target;
208 return True;
211 _TARGETS = XInternAtom(dpy, "TARGETS", False);
212 if (target == _TARGETS) {
213 int *ptr;
215 *length = 4;
216 ptr = *value = (char *) wmalloc(4 * sizeof(Atom));
217 ptr[0] = _TARGETS;
218 ptr[1] = XA_STRING;
219 ptr[2] = XInternAtom(dpy, "TEXT", False);
220 ptr[3] = XInternAtom(dpy, "COMPOUND_TEXT", False);
221 *type = target;
222 return True;
225 *target = XA_PRIMARY;
227 return False;
232 static void
233 lostHandler(WMWidget *w, Atom selection)
235 TextField *tPtr = (WMTextField*)w;
237 tPtr->selection.count = 0;
238 paintTextField(tPtr);
241 static void
242 _notification(void *observerData, WMNotification *notification)
244 WMTextField *to = (WMTextField*)observerData;
245 WMTextField *tw = (WMTextField*)WMGetNotificationClientData(notification);
246 if (to != tw) lostHandler(to, 0);
249 WMTextField*
250 WMCreateTextField(WMWidget *parent)
252 TextField *tPtr;
254 tPtr = wmalloc(sizeof(TextField));
255 memset(tPtr, 0, sizeof(TextField));
257 tPtr->widgetClass = WC_TextField;
259 tPtr->view = W_CreateView(W_VIEW(parent));
260 if (!tPtr->view) {
261 free(tPtr);
262 return NULL;
264 tPtr->view->self = tPtr;
266 tPtr->view->delegate = &_TextFieldViewDelegate;
268 tPtr->view->attribFlags |= CWCursor;
269 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
271 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
273 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
274 tPtr->text[0] = 0;
275 tPtr->textLen = 0;
276 tPtr->bufferSize = MIN_TEXT_BUFFER;
278 tPtr->flags.enabled = 1;
280 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
281 |FocusChangeMask, handleEvents, tPtr);
283 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
285 tPtr->flags.bordered = DEFAULT_BORDERED;
286 tPtr->flags.beveled = True;
287 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
288 tPtr->offsetWidth =
289 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
291 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
293 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
294 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
295 handleTextFieldActionEvents, tPtr);
297 WMCreateSelectionHandler(tPtr, XA_PRIMARY, CurrentTime, requestHandler,
298 lostHandler, NULL);
299 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
302 tPtr->flags.cursorOn = 1;
304 return tPtr;
308 void
309 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
311 tPtr->delegate = delegate;
315 void
316 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
318 int len;
320 CHECK_CLASS(tPtr, WC_TextField);
322 if (!text)
323 return;
325 len = strlen(text);
327 /* check if buffer will hold the text */
328 if (len + tPtr->textLen >= tPtr->bufferSize) {
329 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
330 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
333 if (position < 0 || position >= tPtr->textLen) {
334 /* append the text at the end */
335 strcat(tPtr->text, text);
337 incrToFit(tPtr);
339 tPtr->textLen += len;
340 tPtr->cursorPosition += len;
341 } else {
342 /* insert text at position */
343 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
344 tPtr->textLen-position+1);
346 memcpy(&(tPtr->text[position]), text, len);
348 tPtr->textLen += len;
349 if (position >= tPtr->cursorPosition) {
350 tPtr->cursorPosition += len;
351 incrToFit2(tPtr);
352 } else {
353 incrToFit(tPtr);
357 paintTextField(tPtr);
361 void
362 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
364 CHECK_CLASS(tPtr, WC_TextField);
366 if (range.position >= tPtr->textLen)
367 return;
369 if (range.count < 1) {
370 if (range.position < 0)
371 range.position = 0;
372 tPtr->text[range.position] = 0;
373 tPtr->textLen = range.position;
375 tPtr->cursorPosition = 0;
376 tPtr->viewPosition = 0;
377 } else {
378 if (range.position + range.count > tPtr->textLen)
379 range.count = tPtr->textLen - range.position;
380 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
381 tPtr->textLen - (range.position+range.count) + 1);
382 tPtr->textLen -= range.count;
384 if (tPtr->cursorPosition > range.position)
385 tPtr->cursorPosition -= range.count;
387 decrToFit(tPtr);
390 paintTextField(tPtr);
395 char*
396 WMGetTextFieldText(WMTextField *tPtr)
398 CHECK_CLASS(tPtr, WC_TextField);
400 return wstrdup(tPtr->text);
404 void
405 WMSetTextFieldText(WMTextField *tPtr, char *text)
407 if ((text && strcmp(tPtr->text, text) == 0) ||
408 (!text && tPtr->textLen == 0))
409 return;
411 if (text==NULL) {
412 tPtr->text[0] = 0;
413 tPtr->textLen = 0;
414 } else {
415 tPtr->textLen = strlen(text);
417 if (tPtr->textLen >= tPtr->bufferSize) {
418 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
419 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
421 strcpy(tPtr->text, text);
424 if (tPtr->textLen < tPtr->cursorPosition)
425 tPtr->cursorPosition = tPtr->textLen;
427 tPtr->cursorPosition = tPtr->textLen;
428 tPtr->viewPosition = 0;
429 tPtr->selection.count = 0;
431 if (tPtr->view->flags.realized)
432 paintTextField(tPtr);
436 void
437 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
439 /* TODO: update font change after field is mapped */
440 WMReleaseFont(tPtr->font);
441 tPtr->font = WMRetainFont(font);
445 void
446 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
448 tPtr->flags.alignment = alignment;
449 if (alignment!=WALeft) {
450 wwarning("only left alignment is supported in textfields");
451 return;
454 if (tPtr->view->flags.realized) {
455 paintTextField(tPtr);
460 void
461 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
463 tPtr->flags.bordered = bordered;
465 if (tPtr->view->flags.realized) {
466 paintTextField(tPtr);
471 void
472 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
474 tPtr->flags.beveled = flag;
476 if (tPtr->view->flags.realized) {
477 paintTextField(tPtr);
483 void
484 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
486 tPtr->flags.secure = flag;
488 if (tPtr->view->flags.realized) {
489 paintTextField(tPtr);
494 Bool
495 WMGetTextFieldEditable(WMTextField *tPtr)
497 return tPtr->flags.enabled;
501 void
502 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
504 tPtr->flags.enabled = flag;
506 if (tPtr->view->flags.realized) {
507 paintTextField(tPtr);
512 void
513 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
515 if (tPtr->flags.enabled) {
516 if (range.position < 0) {
517 range.count += range.position;
518 range.count = (range.count < 0) ? 0 : range.count;
519 range.position = 0;
520 } else if (range.position > tPtr->textLen) {
521 range.position = tPtr->textLen;
522 range.count = 0;
525 if (range.position + range.count > tPtr->textLen)
526 range.count = tPtr->textLen - range.position;
528 tPtr->prevselection = tPtr->selection; /* check if this is needed */
530 tPtr->selection = range;
532 if (tPtr->view->flags.realized) {
533 paintTextField(tPtr);
539 void
540 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
542 if (tPtr->flags.enabled) {
543 if (position > tPtr->textLen)
544 position = tPtr->textLen;
546 tPtr->cursorPosition = position;
547 if (tPtr->view->flags.realized) {
548 paintTextField(tPtr);
554 void
555 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
557 CHECK_CLASS(tPtr, WC_TextField);
558 if (next == NULL) {
559 if (tPtr->view->nextFocusChain)
560 tPtr->view->nextFocusChain->prevFocusChain = NULL;
561 tPtr->view->nextFocusChain = NULL;
562 return;
565 CHECK_CLASS(next, WC_TextField);
567 if (tPtr->view->nextFocusChain)
568 tPtr->view->nextFocusChain->prevFocusChain = NULL;
569 if (next->view->prevFocusChain)
570 next->view->prevFocusChain->nextFocusChain = NULL;
572 tPtr->view->nextFocusChain = next->view;
573 next->view->prevFocusChain = tPtr->view;
577 void
578 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
580 CHECK_CLASS(tPtr, WC_TextField);
581 if (prev == NULL) {
582 if (tPtr->view->prevFocusChain)
583 tPtr->view->prevFocusChain->nextFocusChain = NULL;
584 tPtr->view->prevFocusChain = NULL;
585 return;
588 CHECK_CLASS(prev, WC_TextField);
590 if (tPtr->view->prevFocusChain)
591 tPtr->view->prevFocusChain->nextFocusChain = NULL;
592 if (prev->view->nextFocusChain)
593 prev->view->nextFocusChain->prevFocusChain = NULL;
595 tPtr->view->prevFocusChain = prev->view;
596 prev->view->nextFocusChain = tPtr->view;
600 static void
601 didResizeTextField(W_ViewDelegate *self, WMView *view)
603 WMTextField *tPtr = (WMTextField*)view->self;
605 tPtr->offsetWidth =
606 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
608 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
612 static char*
613 makeHiddenString(int length)
615 char *data = wmalloc(length+1);
617 memset(data, '*', length);
618 data[length] = '\0';
620 return data;
624 static void
625 paintCursor(TextField *tPtr)
627 int cx;
628 WMScreen *screen = tPtr->view->screen;
629 int textWidth;
630 char *text;
632 if (tPtr->flags.secure)
633 text = makeHiddenString(strlen(tPtr->text));
634 else
635 text = tPtr->text;
637 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
638 tPtr->cursorPosition-tPtr->viewPosition);
640 switch (tPtr->flags.alignment) {
641 case WARight:
642 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
643 if (textWidth < tPtr->usableWidth)
644 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
645 else
646 cx += tPtr->offsetWidth + 1;
647 break;
648 case WALeft:
649 cx += tPtr->offsetWidth + 1;
650 break;
651 case WAJustified:
652 /* not supported */
653 case WACenter:
654 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
655 if (textWidth < tPtr->usableWidth)
656 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
657 else
658 cx += tPtr->offsetWidth;
659 break;
662 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
663 cx, tPtr->offsetWidth, 1,
664 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
665 printf("%d %d\n",cx,tPtr->cursorPosition);
667 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
668 cx, tPtr->offsetWidth, cx,
669 tPtr->view->size.height - tPtr->offsetWidth - 1);
671 if (tPtr->flags.secure)
672 free(text);
677 static void
678 drawRelief(WMView *view, Bool beveled)
680 WMScreen *scr = view->screen;
681 Display *dpy = scr->display;
682 GC wgc;
683 GC lgc;
684 GC dgc;
685 int width = view->size.width;
686 int height = view->size.height;
688 dgc = WMColorGC(scr->darkGray);
690 if (!beveled) {
691 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
693 return;
695 wgc = WMColorGC(scr->white);
696 lgc = WMColorGC(scr->gray);
698 /* top left */
699 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
700 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
702 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
703 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
705 /* bottom right */
706 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
707 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
709 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
710 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
714 static void
715 paintTextField(TextField *tPtr)
717 W_Screen *screen = tPtr->view->screen;
718 W_View *view = tPtr->view;
719 W_View viewbuffer;
720 int tx, ty, tw, th;
721 int rx;
722 int bd;
723 int totalWidth;
724 char *text;
725 Pixmap drawbuffer;
728 if (!view->flags.realized || !view->flags.mapped)
729 return;
731 if (!tPtr->flags.bordered) {
732 bd = 0;
733 } else {
734 bd = 2;
737 if (tPtr->flags.secure) {
738 text = makeHiddenString(strlen(tPtr->text));
739 } else {
740 text = tPtr->text;
743 totalWidth = tPtr->view->size.width - 2*bd;
745 drawbuffer = XCreatePixmap(screen->display, view->window,
746 view->size.width, view->size.height, screen->depth);
747 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
748 0,0, view->size.width,view->size.height);
749 /* this is quite dirty */
750 viewbuffer.screen = view->screen;
751 viewbuffer.size = view->size;
752 viewbuffer.window = drawbuffer;
755 if (tPtr->textLen > 0) {
756 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
757 tPtr->textLen - tPtr->viewPosition);
759 th = WMFontHeight(tPtr->font);
761 ty = tPtr->offsetWidth;
762 switch (tPtr->flags.alignment) {
763 case WALeft:
764 tx = tPtr->offsetWidth + 1;
765 if (tw < tPtr->usableWidth)
766 XFillRectangle(screen->display, drawbuffer,
767 WMColorGC(screen->white),
768 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
769 break;
771 case WACenter:
772 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
773 if (tw < tPtr->usableWidth)
774 XClearArea(screen->display, view->window, bd, bd,
775 totalWidth, view->size.height-2*bd, False);
776 break;
778 default:
779 case WARight:
780 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
781 if (tw < tPtr->usableWidth)
782 XClearArea(screen->display, view->window, bd, bd,
783 totalWidth-tw, view->size.height-2*bd, False);
784 break;
787 if (!tPtr->flags.enabled)
788 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
790 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
791 tPtr->font, tx, ty,
792 &(text[tPtr->viewPosition]),
793 tPtr->textLen - tPtr->viewPosition);
795 if (tPtr->selection.count) {
796 int count,count2;
798 count = tPtr->selection.count < 0
799 ? tPtr->selection.position + tPtr->selection.count
800 : tPtr->selection.position;
801 count2 = abs(tPtr->selection.count);
802 if (count < tPtr->viewPosition) {
803 count2 = abs(count2 - abs(tPtr->viewPosition - count));
804 count = tPtr->viewPosition;
808 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
809 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
811 XSetBackground(screen->display, screen->textFieldGC,
812 screen->gray->color.pixel);
814 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
815 tPtr->font, rx, ty, &(text[count]),
816 count2);
818 XSetBackground(screen->display, screen->textFieldGC,
819 screen->white->color.pixel);
822 if (!tPtr->flags.enabled)
823 WMSetColorInGC(screen->black, screen->textFieldGC);
824 } else {
825 XFillRectangle(screen->display, drawbuffer,
826 WMColorGC(screen->white),
827 bd,bd, totalWidth,view->size.height-2*bd);
830 /* draw relief */
831 if (tPtr->flags.bordered) {
832 drawRelief(&viewbuffer, tPtr->flags.beveled);
835 if (tPtr->flags.secure)
836 free(text);
837 XCopyArea(screen->display, drawbuffer, view->window,
838 screen->copyGC, 0,0, view->size.width,
839 view->size.height,0,0);
840 XFreePixmap(screen->display, drawbuffer);
842 /* draw cursor */
843 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
844 paintCursor(tPtr);
849 #if 0
850 static void
851 blinkCursor(void *data)
853 TextField *tPtr = (TextField*)data;
855 if (tPtr->flags.cursorOn) {
856 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
857 data);
858 } else {
859 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
860 data);
862 paintCursor(tPtr);
863 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
865 #endif
868 static void
869 handleEvents(XEvent *event, void *data)
871 TextField *tPtr = (TextField*)data;
873 CHECK_CLASS(data, WC_TextField);
876 switch (event->type) {
877 case FocusIn:
878 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
879 return;
880 tPtr->flags.focused = 1;
881 #if 0
882 if (!tPtr->timerID) {
883 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
884 blinkCursor, tPtr);
886 #endif
887 paintTextField(tPtr);
889 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
891 tPtr->flags.notIllegalMovement = 0;
892 break;
894 case FocusOut:
895 tPtr->flags.focused = 0;
896 #if 0
897 if (tPtr->timerID)
898 WMDeleteTimerHandler(tPtr->timerID);
899 tPtr->timerID = NULL;
900 #endif
902 paintTextField(tPtr);
903 if (!tPtr->flags.notIllegalMovement) {
904 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
905 (void*)WMIllegalTextMovement);
907 break;
909 case Expose:
910 if (event->xexpose.count!=0)
911 break;
912 paintTextField(tPtr);
913 break;
915 case DestroyNotify:
916 destroyTextField(tPtr);
917 break;
922 static void
923 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
925 char buffer[64];
926 KeySym ksym;
927 int count, refresh = 0;
928 int control_pressed = 0;
930 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK) {
931 control_pressed = 1;
934 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
935 buffer[count] = '\0';
937 if (!(event->xkey.state & ShiftMask)) {
938 if (tPtr->selection.count)
939 refresh = 1;
940 tPtr->prevselection = tPtr->selection;
941 tPtr->selection.position = tPtr->cursorPosition;
942 tPtr->selection.count = 0;
945 /* Be careful in any case in this switch statement, never to call
946 * to more than a function that can generate text change notifications.
947 * Only one text change notification should be sent in any case.
948 * Else hazardous things can happen.
949 * Maybe we need a better solution than the function wrapper to inform
950 * functions that change text in text fields, if they need to send a
951 * change notification or not. -Dan
953 switch (ksym) {
954 case XK_Tab:
955 #ifdef XK_ISO_Left_Tab
956 case XK_ISO_Left_Tab:
957 #endif
958 if (event->xkey.state & ShiftMask) {
959 if (tPtr->view->prevFocusChain) {
960 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
961 tPtr->view->prevFocusChain);
962 tPtr->flags.notIllegalMovement = 1;
964 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
965 (void*)WMBacktabTextMovement);
966 } else {
967 if (tPtr->view->nextFocusChain) {
968 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
969 tPtr->view->nextFocusChain);
970 tPtr->flags.notIllegalMovement = 1;
972 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
973 (void*)WMTabTextMovement);
975 break;
977 case XK_Return:
978 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
979 (void*)WMReturnTextMovement);
980 break;
982 case WM_EMACSKEY_LEFT:
983 if (!control_pressed) {
984 goto normal_key;
986 case XK_KP_Left:
987 case XK_Left:
988 if (tPtr->cursorPosition > 0) {
989 paintCursor(tPtr);
990 if (event->xkey.state & ControlMask) {
991 int i;
992 for (i = tPtr->cursorPosition - 1; i >= 0; i--)
993 if (tPtr->text[i] == ' ' || i == 0) {
994 tPtr->cursorPosition = i;
995 break;
997 } else {
998 tPtr->cursorPosition--;
1000 if (tPtr->cursorPosition < tPtr->viewPosition) {
1001 tPtr->viewPosition = tPtr->cursorPosition;
1002 refresh = 1;
1003 } else {
1004 paintCursor(tPtr);
1007 break;
1009 case WM_EMACSKEY_RIGHT:
1010 if (!control_pressed) {
1011 goto normal_key;
1013 case XK_KP_Right:
1014 case XK_Right:
1015 if (tPtr->cursorPosition < tPtr->textLen) {
1016 paintCursor(tPtr);
1017 if (event->xkey.state & ControlMask) {
1018 int i;
1019 for (i = tPtr->cursorPosition + 1; i <= tPtr->textLen; i++)
1020 if (tPtr->text[i] == ' ' || i == tPtr->textLen) {
1021 tPtr->cursorPosition = i;
1022 break;
1024 } else {
1025 tPtr->cursorPosition++;
1027 while (WMWidthOfString(tPtr->font,
1028 &(tPtr->text[tPtr->viewPosition]),
1029 tPtr->cursorPosition-tPtr->viewPosition)
1030 > tPtr->usableWidth) {
1031 tPtr->viewPosition++;
1032 refresh = 1;
1034 if (!refresh)
1035 paintCursor(tPtr);
1037 break;
1039 case WM_EMACSKEY_HOME:
1040 if (!control_pressed) {
1041 goto normal_key;
1043 case XK_KP_Home:
1044 case XK_Home:
1045 if (tPtr->cursorPosition > 0) {
1046 paintCursor(tPtr);
1047 tPtr->cursorPosition = 0;
1048 if (tPtr->viewPosition > 0) {
1049 tPtr->viewPosition = 0;
1050 refresh = 1;
1051 } else {
1052 paintCursor(tPtr);
1055 break;
1057 case WM_EMACSKEY_END:
1058 if (!control_pressed) {
1059 goto normal_key;
1061 case XK_KP_End:
1062 case XK_End:
1063 if (tPtr->cursorPosition < tPtr->textLen) {
1064 paintCursor(tPtr);
1065 tPtr->cursorPosition = tPtr->textLen;
1066 tPtr->viewPosition = 0;
1067 while (WMWidthOfString(tPtr->font,
1068 &(tPtr->text[tPtr->viewPosition]),
1069 tPtr->textLen-tPtr->viewPosition)
1070 > tPtr->usableWidth) {
1071 tPtr->viewPosition++;
1072 refresh = 1;
1074 if (!refresh)
1075 paintCursor(tPtr);
1077 break;
1079 case WM_EMACSKEY_BS:
1080 if (!control_pressed) {
1081 goto normal_key;
1083 case XK_BackSpace:
1084 if (tPtr->cursorPosition > 0) {
1085 WMRange range;
1087 if (tPtr->prevselection.count) {
1088 range.position = tPtr->prevselection.count < 0
1089 ? tPtr->prevselection.position + tPtr->prevselection.count
1090 : tPtr->prevselection.position;
1092 range.count = abs(tPtr->prevselection.count);
1093 } else {
1094 range.position = tPtr->cursorPosition - 1;
1095 range.count = 1;
1097 WMDeleteTextFieldRange(tPtr, range);
1098 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1099 (void*)WMDeleteTextEvent);
1101 break;
1103 case WM_EMACSKEY_DEL:
1104 if (!control_pressed) {
1105 goto normal_key;
1107 case XK_KP_Delete:
1108 case XK_Delete:
1109 if (tPtr->cursorPosition < tPtr->textLen || tPtr->prevselection.count) {
1110 WMRange range;
1112 if (tPtr->prevselection.count) {
1113 range.position = tPtr->prevselection.count < 0
1114 ? tPtr->prevselection.position + tPtr->prevselection.count
1115 : tPtr->prevselection.position;
1117 range.count = abs(tPtr->prevselection.count);
1118 } else {
1119 range.position = tPtr->cursorPosition;
1120 range.count = 1;
1122 WMDeleteTextFieldRange(tPtr, range);
1123 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1124 (void*)WMDeleteTextEvent);
1126 break;
1128 normal_key:
1129 default:
1130 if (count > 0 && !iscntrl(buffer[0])) {
1131 WMRange range;
1133 if (tPtr->prevselection.count) {
1134 range.position = tPtr->prevselection.count < 0
1135 ? tPtr->prevselection.position + tPtr->prevselection.count
1136 : tPtr->prevselection.position;
1138 range.count = abs(tPtr->prevselection.count);
1139 } else {
1140 range.position = tPtr->cursorPosition;
1141 range.count = 1;
1143 if (tPtr->prevselection.count)
1144 WMDeleteTextFieldRange(tPtr, range);
1145 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1146 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1147 (void*)WMInsertTextEvent);
1148 } else {
1149 return;
1151 break;
1153 if (event->xkey.state & ShiftMask) {
1154 if (tPtr->selection.count == 0)
1155 tPtr->selection.position = tPtr->cursorPosition;
1156 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1157 refresh = 1;
1159 tPtr->prevselection.count = 0;
1160 if (refresh) {
1161 paintTextField(tPtr);
1166 static int
1167 pointToCursorPosition(TextField *tPtr, int x)
1169 int a, b, mid;
1170 int tw;
1172 if (tPtr->flags.bordered)
1173 x -= 2;
1175 a = tPtr->viewPosition;
1176 b = tPtr->viewPosition + tPtr->textLen;
1177 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1178 tPtr->textLen - tPtr->viewPosition) < x)
1179 return tPtr->textLen;
1181 while (a < b && b-a>1) {
1182 mid = (a+b)/2;
1183 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1184 mid - tPtr->viewPosition);
1185 if (tw > x)
1186 b = mid;
1187 else if (tw < x)
1188 a = mid;
1189 else
1190 return mid;
1192 return (a+b)/2;
1196 static void
1197 handleTextFieldActionEvents(XEvent *event, void *data)
1199 TextField *tPtr = (TextField*)data;
1200 static int move;
1202 CHECK_CLASS(data, WC_TextField);
1204 switch (event->type) {
1205 case KeyPress:
1206 if (tPtr->flags.enabled && tPtr->flags.focused) {
1207 handleTextFieldKeyPress(tPtr, event);
1208 XGrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen),
1209 W_VIEW(tPtr)->window, False,
1210 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1211 GrabModeAsync, GrabModeAsync, None,
1212 W_VIEW(tPtr)->screen->invisibleCursor,
1213 CurrentTime);
1214 tPtr->flags.pointerGrabbed = 1;
1216 break;
1218 case MotionNotify:
1220 if (tPtr->flags.pointerGrabbed) {
1221 tPtr->flags.pointerGrabbed = 0;
1222 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1225 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1227 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1228 tPtr->usableWidth) {
1229 if (WMWidthOfString(tPtr->font,
1230 &(tPtr->text[tPtr->viewPosition]),
1231 tPtr->cursorPosition-tPtr->viewPosition)
1232 > tPtr->usableWidth) {
1233 tPtr->viewPosition++;
1235 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1236 paintCursor(tPtr);
1237 tPtr->viewPosition--;
1240 if (!tPtr->selection.count) {
1241 tPtr->selection.position = tPtr->cursorPosition;
1244 tPtr->cursorPosition =
1245 pointToCursorPosition(tPtr, event->xmotion.x);
1247 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1250 printf("notify %d %d\n",event->xmotion.x,tPtr->usableWidth);
1253 paintCursor(tPtr);
1254 paintTextField(tPtr);
1257 if (move) {
1258 XSetSelectionOwner(tPtr->view->screen->display,
1259 XA_PRIMARY, tPtr->view->window, CurrentTime);
1261 WMNotification *notif = WMCreateNotification("_lostOwnership",
1262 NULL,tPtr);
1263 puts("notify it");
1264 WMPostNotification(notif);
1265 WMReleaseNotification(notif);
1268 break;
1270 case ButtonPress:
1271 if (tPtr->flags.pointerGrabbed) {
1272 tPtr->flags.pointerGrabbed = 0;
1273 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1274 break;
1277 move = 1;
1278 switch (tPtr->flags.alignment) {
1279 int textWidth;
1280 case WARight:
1281 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1282 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1283 WMSetFocusToWidget(tPtr);
1284 } else if (tPtr->flags.focused) {
1285 tPtr->selection.count = 0;
1287 if(textWidth < tPtr->usableWidth){
1288 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1289 event->xbutton.x - tPtr->usableWidth
1290 + textWidth);
1292 else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1293 event->xbutton.x);
1295 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1296 event->xbutton.x);
1297 tPtr->cursorPosition += tPtr->usableWidth - textWidth;
1300 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1301 event->xbutton.x);
1303 paintTextField(tPtr);
1304 break;
1306 case WALeft:
1307 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1308 WMSetFocusToWidget(tPtr);
1309 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1310 event->xbutton.x);
1311 paintTextField(tPtr);
1312 } else if (tPtr->flags.focused) {
1313 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1314 event->xbutton.x);
1315 tPtr->selection.count = 0;
1316 paintTextField(tPtr);
1318 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1319 char *text;
1321 text = W_GetTextSelection(tPtr->view->screen, XA_PRIMARY);
1323 if (!text) {
1324 text = W_GetTextSelection(tPtr->view->screen,
1325 tPtr->view->screen->clipboardAtom);
1327 if (!text) {
1328 text = W_GetTextSelection(tPtr->view->screen,
1329 XA_CUT_BUFFER0);
1331 if (text) {
1332 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1333 XFree(text);
1334 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1335 (void*)WMInsertTextEvent);
1338 break;
1340 break;
1342 case ButtonRelease:
1343 if (tPtr->flags.pointerGrabbed) {
1344 tPtr->flags.pointerGrabbed = 0;
1345 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1348 move = 0;
1349 break;
1354 static void
1355 destroyTextField(TextField *tPtr)
1357 #if 0
1358 if (tPtr->timerID)
1359 WMDeleteTimerHandler(tPtr->timerID);
1360 #endif
1362 WMReleaseFont(tPtr->font);
1363 WMDeleteSelectionHandler(tPtr, XA_PRIMARY);
1364 WMRemoveNotificationObserver(tPtr);
1366 if (tPtr->text)
1367 free(tPtr->text);
1369 free(tPtr);