Add internal notification observer for deselection.
[wmaker-crm.git] / WINGs / wtextfield.c
blob88a025554c48c62d7e52ba8b9a520f0d16be947d
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);
239 static void
240 _notification(void *observerData, WMNotification *notification)
242 WMTextField *to = (WMTextField*)observerData;
243 WMTextField *tw = (WMTextField*)WMGetNotificationClientData(notification);
244 if (to != tw) lostHandler(to, 0);
247 WMTextField*
248 WMCreateTextField(WMWidget *parent)
250 TextField *tPtr;
252 tPtr = wmalloc(sizeof(TextField));
253 memset(tPtr, 0, sizeof(TextField));
255 tPtr->widgetClass = WC_TextField;
257 tPtr->view = W_CreateView(W_VIEW(parent));
258 if (!tPtr->view) {
259 free(tPtr);
260 return NULL;
262 tPtr->view->self = tPtr;
264 tPtr->view->attribFlags |= CWCursor;
265 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
267 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
269 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
270 tPtr->text[0] = 0;
271 tPtr->textLen = 0;
272 tPtr->bufferSize = MIN_TEXT_BUFFER;
274 tPtr->flags.enabled = 1;
276 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
277 |FocusChangeMask, handleEvents, tPtr);
279 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
281 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
283 tPtr->flags.bordered = DEFAULT_BORDERED;
284 tPtr->flags.beveled = True;
285 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
286 tPtr->offsetWidth =
287 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
289 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
290 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
291 handleTextFieldActionEvents, tPtr);
293 WMCreateSelectionHandler(tPtr, XA_PRIMARY, CurrentTime, requestHandler,
294 lostHandler, NULL);
295 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
298 tPtr->flags.cursorOn = 1;
300 return tPtr;
304 void
305 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
307 tPtr->delegate = delegate;
311 void
312 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
314 int len;
316 CHECK_CLASS(tPtr, WC_TextField);
318 if (!text)
319 return;
321 len = strlen(text);
323 /* check if buffer will hold the text */
324 if (len + tPtr->textLen >= tPtr->bufferSize) {
325 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
326 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
329 if (position < 0 || position >= tPtr->textLen) {
330 /* append the text at the end */
331 strcat(tPtr->text, text);
333 incrToFit(tPtr);
335 tPtr->textLen += len;
336 tPtr->cursorPosition += len;
337 } else {
338 /* insert text at position */
339 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
340 tPtr->textLen-position+1);
342 memcpy(&(tPtr->text[position]), text, len);
344 tPtr->textLen += len;
345 if (position >= tPtr->cursorPosition) {
346 tPtr->cursorPosition += len;
347 incrToFit2(tPtr);
348 } else {
349 incrToFit(tPtr);
353 paintTextField(tPtr);
357 void
358 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
360 CHECK_CLASS(tPtr, WC_TextField);
362 if (range.position >= tPtr->textLen)
363 return;
365 if (range.count < 1) {
366 if (range.position < 0)
367 range.position = 0;
368 tPtr->text[range.position] = 0;
369 tPtr->textLen = range.position;
371 tPtr->cursorPosition = 0;
372 tPtr->viewPosition = 0;
373 } else {
374 if (range.position + range.count > tPtr->textLen)
375 range.count = tPtr->textLen - range.position;
376 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
377 tPtr->textLen - (range.position+range.count) + 1);
378 tPtr->textLen -= range.count;
380 if (tPtr->cursorPosition > range.position)
381 tPtr->cursorPosition -= range.count;
383 decrToFit(tPtr);
386 paintTextField(tPtr);
391 char*
392 WMGetTextFieldText(WMTextField *tPtr)
394 CHECK_CLASS(tPtr, WC_TextField);
396 return wstrdup(tPtr->text);
400 void
401 WMSetTextFieldText(WMTextField *tPtr, char *text)
403 if ((text && strcmp(tPtr->text, text) == 0) ||
404 (!text && tPtr->textLen == 0))
405 return;
407 if (text==NULL) {
408 tPtr->text[0] = 0;
409 tPtr->textLen = 0;
410 } else {
411 tPtr->textLen = strlen(text);
413 if (tPtr->textLen >= tPtr->bufferSize) {
414 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
415 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
417 strcpy(tPtr->text, text);
420 if (tPtr->textLen < tPtr->cursorPosition)
421 tPtr->cursorPosition = tPtr->textLen;
423 tPtr->cursorPosition = tPtr->textLen;
424 tPtr->viewPosition = 0;
425 tPtr->selection.count = 0;
427 if (tPtr->view->flags.realized)
428 paintTextField(tPtr);
432 void
433 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
435 /* TODO: update font change after field is mapped */
436 WMReleaseFont(tPtr->font);
437 tPtr->font = WMRetainFont(font);
441 void
442 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
444 tPtr->flags.alignment = alignment;
445 if (alignment!=WALeft) {
446 wwarning("only left alignment is supported in textfields");
447 return;
450 if (tPtr->view->flags.realized) {
451 paintTextField(tPtr);
456 void
457 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
459 tPtr->flags.bordered = bordered;
461 if (tPtr->view->flags.realized) {
462 paintTextField(tPtr);
467 void
468 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
470 tPtr->flags.beveled = flag;
472 if (tPtr->view->flags.realized) {
473 paintTextField(tPtr);
479 void
480 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
482 tPtr->flags.secure = flag;
484 if (tPtr->view->flags.realized) {
485 paintTextField(tPtr);
490 Bool
491 WMGetTextFieldEditable(WMTextField *tPtr)
493 return tPtr->flags.enabled;
497 void
498 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
500 tPtr->flags.enabled = flag;
502 if (tPtr->view->flags.realized) {
503 paintTextField(tPtr);
508 void
509 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
511 if (tPtr->flags.enabled) {
512 if (range.position < 0) {
513 range.count += range.position;
514 range.count = (range.count < 0) ? 0 : range.count;
515 range.position = 0;
516 } else if (range.position > tPtr->textLen) {
517 range.position = tPtr->textLen;
518 range.count = 0;
521 if (range.position + range.count > tPtr->textLen)
522 range.count = tPtr->textLen - range.position;
524 tPtr->prevselection = tPtr->selection; /* check if this is needed */
526 tPtr->selection = range;
528 if (tPtr->view->flags.realized) {
529 paintTextField(tPtr);
535 void
536 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
538 if (tPtr->flags.enabled) {
539 if (position > tPtr->textLen)
540 position = tPtr->textLen;
542 tPtr->cursorPosition = position;
543 if (tPtr->view->flags.realized) {
544 paintTextField(tPtr);
550 void
551 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
553 CHECK_CLASS(tPtr, WC_TextField);
554 if (next == NULL) {
555 if (tPtr->view->nextFocusChain)
556 tPtr->view->nextFocusChain->prevFocusChain = NULL;
557 tPtr->view->nextFocusChain = NULL;
558 return;
561 CHECK_CLASS(next, WC_TextField);
563 if (tPtr->view->nextFocusChain)
564 tPtr->view->nextFocusChain->prevFocusChain = NULL;
565 if (next->view->prevFocusChain)
566 next->view->prevFocusChain->nextFocusChain = NULL;
568 tPtr->view->nextFocusChain = next->view;
569 next->view->prevFocusChain = tPtr->view;
573 void
574 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
576 CHECK_CLASS(tPtr, WC_TextField);
577 if (prev == NULL) {
578 if (tPtr->view->prevFocusChain)
579 tPtr->view->prevFocusChain->nextFocusChain = NULL;
580 tPtr->view->prevFocusChain = NULL;
581 return;
584 CHECK_CLASS(prev, WC_TextField);
586 if (tPtr->view->prevFocusChain)
587 tPtr->view->prevFocusChain->nextFocusChain = NULL;
588 if (prev->view->nextFocusChain)
589 prev->view->nextFocusChain->prevFocusChain = NULL;
591 tPtr->view->prevFocusChain = prev->view;
592 prev->view->nextFocusChain = tPtr->view;
596 static void
597 resizeTextField(WMTextField *tPtr, unsigned int width, unsigned int height)
599 W_ResizeView(tPtr->view, width, height);
601 tPtr->offsetWidth =
602 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
604 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
608 static char*
609 makeHiddenString(int length)
611 char *data = wmalloc(length+1);
613 memset(data, '*', length);
614 data[length] = '\0';
615 return data;
619 static void
620 paintCursor(TextField *tPtr)
622 int cx;
623 WMScreen *screen = tPtr->view->screen;
624 int textWidth;
625 char *text;
627 if (tPtr->flags.secure)
628 text = makeHiddenString(strlen(tPtr->text));
629 else
630 text = tPtr->text;
632 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
633 tPtr->cursorPosition-tPtr->viewPosition);
635 switch (tPtr->flags.alignment) {
636 case WARight:
637 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
638 if (textWidth < tPtr->usableWidth)
639 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
640 else
641 cx += tPtr->offsetWidth + 1;
642 break;
643 case WALeft:
644 cx += tPtr->offsetWidth + 1;
645 break;
646 case WAJustified:
647 /* not supported */
648 case WACenter:
649 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
650 if (textWidth < tPtr->usableWidth)
651 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
652 else
653 cx += tPtr->offsetWidth;
654 break;
657 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
658 cx, tPtr->offsetWidth, 1,
659 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
660 printf("%d %d\n",cx,tPtr->cursorPosition);
662 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
663 cx, tPtr->offsetWidth, cx,
664 tPtr->view->size.height - tPtr->offsetWidth - 1);
666 if (tPtr->flags.secure)
667 free(text);
672 static void
673 drawRelief(WMView *view, Bool beveled)
675 WMScreen *scr = view->screen;
676 Display *dpy = scr->display;
677 GC wgc;
678 GC lgc;
679 GC dgc;
680 int width = view->size.width;
681 int height = view->size.height;
683 dgc = WMColorGC(scr->darkGray);
685 if (!beveled) {
686 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
688 return;
690 wgc = WMColorGC(scr->white);
691 lgc = WMColorGC(scr->gray);
693 /* top left */
694 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
695 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
697 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
698 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
700 /* bottom right */
701 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
702 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
704 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
705 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
709 static void
710 paintTextField(TextField *tPtr)
712 W_Screen *screen = tPtr->view->screen;
713 W_View *view = tPtr->view;
714 W_View viewbuffer;
715 int tx, ty, tw, th;
716 int rx;
717 int bd;
718 int totalWidth;
719 char *text;
720 Pixmap drawbuffer;
723 if (!view->flags.realized || !view->flags.mapped)
724 return;
726 if (!tPtr->flags.bordered) {
727 bd = 0;
728 } else {
729 bd = 2;
732 if (tPtr->flags.secure) {
733 text = makeHiddenString(strlen(tPtr->text));
734 } else {
735 text = tPtr->text;
738 totalWidth = tPtr->view->size.width - 2*bd;
740 drawbuffer = XCreatePixmap(screen->display, view->window,
741 view->size.width, view->size.height, screen->depth);
742 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
743 0,0, view->size.width,view->size.height);
744 /* this is quite dirty */
745 viewbuffer.screen = view->screen;
746 viewbuffer.size = view->size;
747 viewbuffer.window = drawbuffer;
750 if (tPtr->textLen > 0) {
751 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
752 tPtr->textLen - tPtr->viewPosition);
754 th = WMFontHeight(tPtr->font);
756 ty = tPtr->offsetWidth;
757 switch (tPtr->flags.alignment) {
758 case WALeft:
759 tx = tPtr->offsetWidth + 1;
760 if (tw < tPtr->usableWidth)
761 XFillRectangle(screen->display, drawbuffer,
762 WMColorGC(screen->white),
763 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
764 break;
766 case WACenter:
767 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
768 if (tw < tPtr->usableWidth)
769 XClearArea(screen->display, view->window, bd, bd,
770 totalWidth, view->size.height-2*bd, False);
771 break;
773 default:
774 case WARight:
775 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
776 if (tw < tPtr->usableWidth)
777 XClearArea(screen->display, view->window, bd, bd,
778 totalWidth-tw, view->size.height-2*bd, False);
779 break;
782 if (!tPtr->flags.enabled)
783 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
785 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
786 tPtr->font, tx, ty,
787 &(text[tPtr->viewPosition]),
788 tPtr->textLen - tPtr->viewPosition);
790 if (tPtr->selection.count) {
791 int count,count2;
793 count = tPtr->selection.count < 0
794 ? tPtr->selection.position + tPtr->selection.count
795 : tPtr->selection.position;
796 count2 = abs(tPtr->selection.count);
797 if (count < tPtr->viewPosition) {
798 count2 = abs(count2 - abs(tPtr->viewPosition - count));
799 count = tPtr->viewPosition;
803 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
804 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
806 XSetBackground(screen->display, screen->textFieldGC,
807 screen->gray->color.pixel);
809 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
810 tPtr->font, rx, ty, &(text[count]),
811 count2);
813 XSetBackground(screen->display, screen->textFieldGC,
814 screen->white->color.pixel);
817 if (!tPtr->flags.enabled)
818 WMSetColorInGC(screen->black, screen->textFieldGC);
819 } else {
820 XFillRectangle(screen->display, drawbuffer,
821 WMColorGC(screen->white),
822 bd,bd, totalWidth,view->size.height-2*bd);
825 /* draw relief */
826 if (tPtr->flags.bordered) {
827 drawRelief(&viewbuffer, tPtr->flags.beveled);
830 if (tPtr->flags.secure)
831 free(text);
832 XCopyArea(screen->display, drawbuffer, view->window,
833 screen->copyGC, 0,0, view->size.width,
834 view->size.height,0,0);
835 XFreePixmap(screen->display, drawbuffer);
837 /* draw cursor */
838 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
839 paintCursor(tPtr);
844 #if 0
845 static void
846 blinkCursor(void *data)
848 TextField *tPtr = (TextField*)data;
850 if (tPtr->flags.cursorOn) {
851 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
852 data);
853 } else {
854 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
855 data);
857 paintCursor(tPtr);
858 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
860 #endif
863 static void
864 handleEvents(XEvent *event, void *data)
866 TextField *tPtr = (TextField*)data;
868 CHECK_CLASS(data, WC_TextField);
871 switch (event->type) {
872 case FocusIn:
873 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
874 return;
875 tPtr->flags.focused = 1;
876 #if 0
877 if (!tPtr->timerID) {
878 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
879 blinkCursor, tPtr);
881 #endif
882 paintTextField(tPtr);
884 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
886 tPtr->flags.notIllegalMovement = 0;
887 break;
889 case FocusOut:
890 tPtr->flags.focused = 0;
891 #if 0
892 if (tPtr->timerID)
893 WMDeleteTimerHandler(tPtr->timerID);
894 tPtr->timerID = NULL;
895 #endif
897 paintTextField(tPtr);
898 if (!tPtr->flags.notIllegalMovement) {
899 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
900 (void*)WMIllegalTextMovement);
902 break;
904 case Expose:
905 if (event->xexpose.count!=0)
906 break;
907 paintTextField(tPtr);
908 break;
910 case DestroyNotify:
911 destroyTextField(tPtr);
912 break;
917 static void
918 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
920 char buffer[64];
921 KeySym ksym;
922 int count, refresh = 0;
923 int control_pressed = 0;
925 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK) {
926 control_pressed = 1;
929 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
930 buffer[count] = '\0';
932 if (!(event->xkey.state & ShiftMask)) {
933 if (tPtr->selection.count)
934 refresh = 1;
935 tPtr->prevselection = tPtr->selection;
936 tPtr->selection.position = tPtr->cursorPosition;
937 tPtr->selection.count = 0;
940 /* Be careful in any case in this switch statement, never to call
941 * to more than a function that can generate text change notifications.
942 * Only one text change notification should be sent in any case.
943 * Else hazardous things can happen.
944 * Maybe we need a better solution than the function wrapper to inform
945 * functions that change text in text fields, if they need to send a
946 * change notification or not. -Dan
948 switch (ksym) {
949 case XK_Tab:
950 case XK_ISO_Left_Tab:
951 if (event->xkey.state & ShiftMask) {
952 if (tPtr->view->prevFocusChain) {
953 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
954 tPtr->view->prevFocusChain);
955 tPtr->flags.notIllegalMovement = 1;
957 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
958 (void*)WMBacktabTextMovement);
959 } else {
960 if (tPtr->view->nextFocusChain) {
961 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
962 tPtr->view->nextFocusChain);
963 tPtr->flags.notIllegalMovement = 1;
965 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
966 (void*)WMTabTextMovement);
968 break;
970 case XK_Return:
971 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
972 (void*)WMReturnTextMovement);
973 break;
975 case WM_EMACSKEY_LEFT:
976 if (!control_pressed) {
977 goto normal_key;
979 case XK_KP_Left:
980 case XK_Left:
981 if (tPtr->cursorPosition > 0) {
982 paintCursor(tPtr);
983 if (event->xkey.state & ControlMask) {
984 int i;
985 for (i = tPtr->cursorPosition - 1; i >= 0; i--)
986 if (tPtr->text[i] == ' ' || i == 0) {
987 tPtr->cursorPosition = i;
988 break;
990 } else {
991 tPtr->cursorPosition--;
993 if (tPtr->cursorPosition < tPtr->viewPosition) {
994 tPtr->viewPosition = tPtr->cursorPosition;
995 refresh = 1;
996 } else {
997 paintCursor(tPtr);
1000 break;
1002 case WM_EMACSKEY_RIGHT:
1003 if (!control_pressed) {
1004 goto normal_key;
1006 case XK_KP_Right:
1007 case XK_Right:
1008 if (tPtr->cursorPosition < tPtr->textLen) {
1009 paintCursor(tPtr);
1010 if (event->xkey.state & ControlMask) {
1011 int i;
1012 for (i = tPtr->cursorPosition + 1; i <= tPtr->textLen; i++)
1013 if (tPtr->text[i] == ' ' || i == tPtr->textLen) {
1014 tPtr->cursorPosition = i;
1015 break;
1017 } else {
1018 tPtr->cursorPosition++;
1020 while (WMWidthOfString(tPtr->font,
1021 &(tPtr->text[tPtr->viewPosition]),
1022 tPtr->cursorPosition-tPtr->viewPosition)
1023 > tPtr->usableWidth) {
1024 tPtr->viewPosition++;
1025 refresh = 1;
1027 if (!refresh)
1028 paintCursor(tPtr);
1030 break;
1032 case WM_EMACSKEY_HOME:
1033 if (!control_pressed) {
1034 goto normal_key;
1036 case XK_KP_Home:
1037 case XK_Home:
1038 if (tPtr->cursorPosition > 0) {
1039 paintCursor(tPtr);
1040 tPtr->cursorPosition = 0;
1041 if (tPtr->viewPosition > 0) {
1042 tPtr->viewPosition = 0;
1043 refresh = 1;
1044 } else {
1045 paintCursor(tPtr);
1048 break;
1050 case WM_EMACSKEY_END:
1051 if (!control_pressed) {
1052 goto normal_key;
1054 case XK_KP_End:
1055 case XK_End:
1056 if (tPtr->cursorPosition < tPtr->textLen) {
1057 paintCursor(tPtr);
1058 tPtr->cursorPosition = tPtr->textLen;
1059 tPtr->viewPosition = 0;
1060 while (WMWidthOfString(tPtr->font,
1061 &(tPtr->text[tPtr->viewPosition]),
1062 tPtr->textLen-tPtr->viewPosition)
1063 > tPtr->usableWidth) {
1064 tPtr->viewPosition++;
1065 refresh = 1;
1067 if (!refresh)
1068 paintCursor(tPtr);
1070 break;
1072 case WM_EMACSKEY_BS:
1073 if (!control_pressed) {
1074 goto normal_key;
1076 case XK_BackSpace:
1077 if (tPtr->cursorPosition > 0) {
1078 WMRange range;
1080 if (tPtr->prevselection.count) {
1081 range.position = tPtr->prevselection.count < 0
1082 ? tPtr->prevselection.position + tPtr->prevselection.count
1083 : tPtr->prevselection.position;
1085 range.count = abs(tPtr->prevselection.count);
1086 } else {
1087 range.position = tPtr->cursorPosition - 1;
1088 range.count = 1;
1090 WMDeleteTextFieldRange(tPtr, range);
1091 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1092 (void*)WMDeleteTextEvent);
1094 break;
1096 case WM_EMACSKEY_DEL:
1097 if (!control_pressed) {
1098 goto normal_key;
1100 case XK_KP_Delete:
1101 case XK_Delete:
1102 if (tPtr->cursorPosition < tPtr->textLen || tPtr->prevselection.count) {
1103 WMRange range;
1105 if (tPtr->prevselection.count) {
1106 range.position = tPtr->prevselection.count < 0
1107 ? tPtr->prevselection.position + tPtr->prevselection.count
1108 : tPtr->prevselection.position;
1110 range.count = abs(tPtr->prevselection.count);
1111 } else {
1112 range.position = tPtr->cursorPosition;
1113 range.count = 1;
1115 WMDeleteTextFieldRange(tPtr, range);
1116 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1117 (void*)WMDeleteTextEvent);
1119 break;
1121 normal_key:
1122 default:
1123 if (count > 0 && !iscntrl(buffer[0])) {
1124 WMRange range;
1126 if (tPtr->prevselection.count) {
1127 range.position = tPtr->prevselection.count < 0
1128 ? tPtr->prevselection.position + tPtr->prevselection.count
1129 : tPtr->prevselection.position;
1131 range.count = abs(tPtr->prevselection.count);
1132 } else {
1133 range.position = tPtr->cursorPosition;
1134 range.count = 1;
1136 if (tPtr->prevselection.count)
1137 WMDeleteTextFieldRange(tPtr, range);
1138 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1139 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1140 (void*)WMInsertTextEvent);
1141 } else {
1142 return;
1144 break;
1146 if (event->xkey.state & ShiftMask) {
1147 if (tPtr->selection.count == 0)
1148 tPtr->selection.position = tPtr->cursorPosition;
1149 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1150 refresh = 1;
1152 tPtr->prevselection.count = 0;
1153 if (refresh) {
1154 paintTextField(tPtr);
1159 static int
1160 pointToCursorPosition(TextField *tPtr, int x)
1162 int a, b, mid;
1163 int tw;
1165 if (tPtr->flags.bordered)
1166 x -= 2;
1168 a = tPtr->viewPosition;
1169 b = tPtr->viewPosition + tPtr->textLen;
1170 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1171 tPtr->textLen - tPtr->viewPosition) < x)
1172 return tPtr->textLen;
1174 while (a < b && b-a>1) {
1175 mid = (a+b)/2;
1176 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1177 mid - tPtr->viewPosition);
1178 if (tw > x)
1179 b = mid;
1180 else if (tw < x)
1181 a = mid;
1182 else
1183 return mid;
1185 return (a+b)/2;
1189 static void
1190 handleTextFieldActionEvents(XEvent *event, void *data)
1192 TextField *tPtr = (TextField*)data;
1193 static int move;
1195 CHECK_CLASS(data, WC_TextField);
1197 switch (event->type) {
1198 case KeyPress:
1199 if (tPtr->flags.enabled && tPtr->flags.focused) {
1200 handleTextFieldKeyPress(tPtr, event);
1201 XGrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen),
1202 W_VIEW(tPtr)->window, False,
1203 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1204 GrabModeAsync, GrabModeAsync, None,
1205 W_VIEW(tPtr)->screen->invisibleCursor,
1206 CurrentTime);
1207 tPtr->flags.pointerGrabbed = 1;
1209 break;
1211 case MotionNotify:
1213 if (tPtr->flags.pointerGrabbed) {
1214 tPtr->flags.pointerGrabbed = 0;
1215 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1218 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1220 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1221 tPtr->usableWidth) {
1222 if (WMWidthOfString(tPtr->font,
1223 &(tPtr->text[tPtr->viewPosition]),
1224 tPtr->cursorPosition-tPtr->viewPosition)
1225 > tPtr->usableWidth) {
1226 tPtr->viewPosition++;
1228 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1229 paintCursor(tPtr);
1230 tPtr->viewPosition--;
1233 if (!tPtr->selection.count) {
1234 tPtr->selection.position = tPtr->cursorPosition;
1237 tPtr->cursorPosition =
1238 pointToCursorPosition(tPtr, event->xmotion.x);
1240 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1243 printf("notify %d %d\n",event->xmotion.x,tPtr->usableWidth);
1246 paintCursor(tPtr);
1247 paintTextField(tPtr);
1250 if (move) {
1251 XSetSelectionOwner(tPtr->view->screen->display,
1252 XA_PRIMARY, tPtr->view->window, CurrentTime);
1254 WMNotification *notif = WMCreateNotification("_lostOwnership",
1255 NULL,tPtr);
1256 puts("notify it");
1257 WMPostNotification(notif);
1258 WMReleaseNotification(notif);
1261 break;
1263 case ButtonPress:
1264 if (tPtr->flags.pointerGrabbed) {
1265 tPtr->flags.pointerGrabbed = 0;
1266 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1267 break;
1270 move = 1;
1271 switch (tPtr->flags.alignment) {
1272 int textWidth;
1273 case WARight:
1274 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1275 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1276 WMSetFocusToWidget(tPtr);
1277 } else if (tPtr->flags.focused) {
1278 tPtr->selection.count = 0;
1280 if(textWidth < tPtr->usableWidth){
1281 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1282 event->xbutton.x - tPtr->usableWidth
1283 + textWidth);
1285 else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1286 event->xbutton.x);
1288 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1289 event->xbutton.x);
1290 tPtr->cursorPosition += tPtr->usableWidth - textWidth;
1293 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1294 event->xbutton.x);
1296 paintTextField(tPtr);
1297 break;
1299 case WALeft:
1300 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1301 WMSetFocusToWidget(tPtr);
1302 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1303 event->xbutton.x);
1304 paintTextField(tPtr);
1305 } else if (tPtr->flags.focused) {
1306 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1307 event->xbutton.x);
1308 tPtr->selection.count = 0;
1309 paintTextField(tPtr);
1311 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1312 char *text;
1314 text = W_GetTextSelection(tPtr->view->screen, XA_PRIMARY);
1316 if (!text) {
1317 text = W_GetTextSelection(tPtr->view->screen,
1318 tPtr->view->screen->clipboardAtom);
1320 if (!text) {
1321 text = W_GetTextSelection(tPtr->view->screen,
1322 XA_CUT_BUFFER0);
1324 if (text) {
1325 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1326 XFree(text);
1327 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1328 (void*)WMInsertTextEvent);
1331 break;
1333 break;
1335 case ButtonRelease:
1336 if (tPtr->flags.pointerGrabbed) {
1337 tPtr->flags.pointerGrabbed = 0;
1338 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1341 move = 0;
1342 break;
1347 static void
1348 destroyTextField(TextField *tPtr)
1350 #if 0
1351 if (tPtr->timerID)
1352 WMDeleteTimerHandler(tPtr->timerID);
1353 #endif
1355 WMReleaseFont(tPtr->font);
1356 WMDeleteSelectionHandler(tPtr, XA_PRIMARY);
1357 WMRemoveNotificationObserver(tPtr);
1359 if (tPtr->text)
1360 free(tPtr->text);
1362 free(tPtr);