Fix misc bugs.
[wmaker-crm.git] / WINGs / wtextfield.c
blob8b7791d99c910d53398abbb43fcd7b1711330cc1
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) {
181 TextField *tPtr = w;
182 int count,count2;
183 Display *dpy = tPtr->view->screen->display;
184 Atom _TARGETS;
185 char *text;
186 text = XGetAtomName(tPtr->view->screen->display,target);
187 XFree(text);
188 text = XGetAtomName(tPtr->view->screen->display,selection);
189 XFree(text);
191 *format = 32;
192 *length = 0;
193 *value = NULL;
194 count = tPtr->selection.count < 0
195 ? tPtr->selection.position + tPtr->selection.count
196 : tPtr->selection.position;
198 if (target == XA_STRING ||
199 target == XInternAtom(dpy, "TEXT", False) ||
200 target == XInternAtom(dpy, "COMPOUND_TEXT", False)) {
201 *value = wstrdup(&(tPtr->text[count]));
202 *length = abs(tPtr->selection.count);
203 *format = 8;
204 *type = target;
205 return True;
208 _TARGETS = XInternAtom(dpy, "TARGETS", False);
209 if (target == _TARGETS) {
210 int *ptr;
212 *length = 4;
213 ptr = *value = (char *) wmalloc(4 * sizeof(Atom));
214 ptr[0] = _TARGETS;
215 ptr[1] = XA_STRING;
216 ptr[2] = XInternAtom(dpy, "TEXT", False);
217 ptr[3] = XInternAtom(dpy, "COMPOUND_TEXT", False);
218 *type = target;
219 return True;
222 *target = XA_PRIMARY;
224 return False;
229 static void
230 lostHandler(WMWidget *w, Atom selection)
232 TextField *tPtr;
233 tPtr->selection.count = 0;
234 paintTextField(tPtr);
238 WMTextField*
239 WMCreateTextField(WMWidget *parent)
241 TextField *tPtr;
243 tPtr = wmalloc(sizeof(TextField));
244 memset(tPtr, 0, sizeof(TextField));
246 tPtr->widgetClass = WC_TextField;
248 tPtr->view = W_CreateView(W_VIEW(parent));
249 if (!tPtr->view) {
250 free(tPtr);
251 return NULL;
253 tPtr->view->self = tPtr;
255 tPtr->view->attribFlags |= CWCursor;
256 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
258 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
260 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
261 tPtr->text[0] = 0;
262 tPtr->textLen = 0;
263 tPtr->bufferSize = MIN_TEXT_BUFFER;
265 tPtr->flags.enabled = 1;
267 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
268 |FocusChangeMask, handleEvents, tPtr);
270 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
272 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
274 tPtr->flags.bordered = DEFAULT_BORDERED;
275 tPtr->flags.beveled = True;
276 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
277 tPtr->offsetWidth =
278 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
280 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
281 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
282 handleTextFieldActionEvents, tPtr);
284 WMCreateSelectionHandler(tPtr, XA_PRIMARY, CurrentTime, requestHandler,
285 lostHandler, NULL);
288 tPtr->flags.cursorOn = 1;
290 return tPtr;
294 void
295 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
297 tPtr->delegate = delegate;
301 void
302 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
304 int len;
306 CHECK_CLASS(tPtr, WC_TextField);
308 if (!text)
309 return;
311 len = strlen(text);
313 /* check if buffer will hold the text */
314 if (len + tPtr->textLen >= tPtr->bufferSize) {
315 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
316 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
319 if (position < 0 || position >= tPtr->textLen) {
320 /* append the text at the end */
321 strcat(tPtr->text, text);
323 incrToFit(tPtr);
325 tPtr->textLen += len;
326 tPtr->cursorPosition += len;
327 } else {
328 /* insert text at position */
329 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
330 tPtr->textLen-position+1);
332 memcpy(&(tPtr->text[position]), text, len);
334 tPtr->textLen += len;
335 if (position >= tPtr->cursorPosition) {
336 tPtr->cursorPosition += len;
337 incrToFit2(tPtr);
338 } else {
339 incrToFit(tPtr);
343 paintTextField(tPtr);
347 void
348 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
350 CHECK_CLASS(tPtr, WC_TextField);
352 if (range.position >= tPtr->textLen)
353 return;
355 if (range.count < 1) {
356 if (range.position < 0)
357 range.position = 0;
358 tPtr->text[range.position] = 0;
359 tPtr->textLen = range.position;
361 tPtr->cursorPosition = 0;
362 tPtr->viewPosition = 0;
363 } else {
364 if (range.position + range.count > tPtr->textLen)
365 range.count = tPtr->textLen - range.position;
366 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
367 tPtr->textLen - (range.position+range.count) + 1);
368 tPtr->textLen -= range.count;
370 if (tPtr->cursorPosition > range.position)
371 tPtr->cursorPosition -= range.count;
373 decrToFit(tPtr);
376 paintTextField(tPtr);
381 char*
382 WMGetTextFieldText(WMTextField *tPtr)
384 CHECK_CLASS(tPtr, WC_TextField);
386 return wstrdup(tPtr->text);
390 void
391 WMSetTextFieldText(WMTextField *tPtr, char *text)
393 if ((text && strcmp(tPtr->text, text) == 0) ||
394 (!text && tPtr->textLen == 0))
395 return;
397 if (text==NULL) {
398 tPtr->text[0] = 0;
399 tPtr->textLen = 0;
400 } else {
401 tPtr->textLen = strlen(text);
403 if (tPtr->textLen >= tPtr->bufferSize) {
404 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
405 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
407 strcpy(tPtr->text, text);
410 if (tPtr->textLen < tPtr->cursorPosition)
411 tPtr->cursorPosition = tPtr->textLen;
413 tPtr->cursorPosition = tPtr->textLen;
414 tPtr->viewPosition = 0;
415 tPtr->selection.count = 0;
417 if (tPtr->view->flags.realized)
418 paintTextField(tPtr);
422 void
423 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
425 /* TODO: update font change after field is mapped */
426 WMReleaseFont(tPtr->font);
427 tPtr->font = WMRetainFont(font);
431 void
432 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
434 tPtr->flags.alignment = alignment;
435 if (alignment!=WALeft) {
436 wwarning("only left alignment is supported in textfields");
437 return;
440 if (tPtr->view->flags.realized) {
441 paintTextField(tPtr);
446 void
447 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
449 tPtr->flags.bordered = bordered;
451 if (tPtr->view->flags.realized) {
452 paintTextField(tPtr);
457 void
458 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
460 tPtr->flags.beveled = flag;
462 if (tPtr->view->flags.realized) {
463 paintTextField(tPtr);
469 void
470 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
472 tPtr->flags.secure = flag;
474 if (tPtr->view->flags.realized) {
475 paintTextField(tPtr);
480 Bool
481 WMGetTextFieldEditable(WMTextField *tPtr)
483 return tPtr->flags.enabled;
487 void
488 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
490 tPtr->flags.enabled = flag;
492 if (tPtr->view->flags.realized) {
493 paintTextField(tPtr);
498 void
499 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
501 if (tPtr->flags.enabled) {
502 if (range.position < 0) {
503 range.count += range.position;
504 range.count = (range.count < 0) ? 0 : range.count;
505 range.position = 0;
506 } else if (range.position > tPtr->textLen) {
507 range.position = tPtr->textLen;
508 range.count = 0;
511 if (range.position + range.count > tPtr->textLen)
512 range.count = tPtr->textLen - range.position;
514 tPtr->prevselection = tPtr->selection; /* check if this is needed */
516 tPtr->selection = range;
518 if (tPtr->view->flags.realized) {
519 paintTextField(tPtr);
525 void
526 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
528 if (tPtr->flags.enabled) {
529 if (position > tPtr->textLen)
530 position = tPtr->textLen;
532 tPtr->cursorPosition = position;
533 if (tPtr->view->flags.realized) {
534 paintTextField(tPtr);
540 void
541 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
543 CHECK_CLASS(tPtr, WC_TextField);
544 if (next == NULL) {
545 if (tPtr->view->nextFocusChain)
546 tPtr->view->nextFocusChain->prevFocusChain = NULL;
547 tPtr->view->nextFocusChain = NULL;
548 return;
551 CHECK_CLASS(next, WC_TextField);
553 if (tPtr->view->nextFocusChain)
554 tPtr->view->nextFocusChain->prevFocusChain = NULL;
555 if (next->view->prevFocusChain)
556 next->view->prevFocusChain->nextFocusChain = NULL;
558 tPtr->view->nextFocusChain = next->view;
559 next->view->prevFocusChain = tPtr->view;
563 void
564 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
566 CHECK_CLASS(tPtr, WC_TextField);
567 if (prev == NULL) {
568 if (tPtr->view->prevFocusChain)
569 tPtr->view->prevFocusChain->nextFocusChain = NULL;
570 tPtr->view->prevFocusChain = NULL;
571 return;
574 CHECK_CLASS(prev, WC_TextField);
576 if (tPtr->view->prevFocusChain)
577 tPtr->view->prevFocusChain->nextFocusChain = NULL;
578 if (prev->view->nextFocusChain)
579 prev->view->nextFocusChain->prevFocusChain = NULL;
581 tPtr->view->prevFocusChain = prev->view;
582 prev->view->nextFocusChain = tPtr->view;
586 static void
587 resizeTextField(WMTextField *tPtr, unsigned int width, unsigned int height)
589 W_ResizeView(tPtr->view, width, height);
591 tPtr->offsetWidth =
592 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
594 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
598 static char*
599 makeHiddenString(int length)
601 char *data = wmalloc(length+1);
603 memset(data, '*', length);
604 data[length] = '\0';
605 return data;
609 static void
610 paintCursor(TextField *tPtr)
612 int cx;
613 WMScreen *screen = tPtr->view->screen;
614 int textWidth;
615 char *text;
617 if (tPtr->flags.secure)
618 text = makeHiddenString(strlen(tPtr->text));
619 else
620 text = tPtr->text;
622 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
623 tPtr->cursorPosition-tPtr->viewPosition);
625 switch (tPtr->flags.alignment) {
626 case WARight:
627 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
628 if (textWidth < tPtr->usableWidth)
629 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
630 else
631 cx += tPtr->offsetWidth + 1;
632 break;
633 case WALeft:
634 cx += tPtr->offsetWidth + 1;
635 break;
636 case WAJustified:
637 /* not supported */
638 case WACenter:
639 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
640 if (textWidth < tPtr->usableWidth)
641 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
642 else
643 cx += tPtr->offsetWidth;
644 break;
647 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
648 cx, tPtr->offsetWidth, 1,
649 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
650 printf("%d %d\n",cx,tPtr->cursorPosition);
652 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
653 cx, tPtr->offsetWidth, cx,
654 tPtr->view->size.height - tPtr->offsetWidth - 1);
656 if (tPtr->flags.secure)
657 free(text);
662 static void
663 drawRelief(WMView *view, Bool beveled)
665 WMScreen *scr = view->screen;
666 Display *dpy = scr->display;
667 GC wgc;
668 GC lgc;
669 GC dgc;
670 int width = view->size.width;
671 int height = view->size.height;
673 dgc = WMColorGC(scr->darkGray);
675 if (!beveled) {
676 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
678 return;
680 wgc = WMColorGC(scr->white);
681 lgc = WMColorGC(scr->gray);
683 /* top left */
684 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
685 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
687 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
688 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
690 /* bottom right */
691 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
692 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
694 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
695 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
699 static void
700 paintTextField(TextField *tPtr)
702 W_Screen *screen = tPtr->view->screen;
703 W_View *view = tPtr->view;
704 W_View viewbuffer;
705 int tx, ty, tw, th;
706 int rx;
707 int bd;
708 int totalWidth;
709 char *text;
710 Pixmap drawbuffer;
713 if (!view->flags.realized || !view->flags.mapped)
714 return;
716 if (!tPtr->flags.bordered) {
717 bd = 0;
718 } else {
719 bd = 2;
722 if (tPtr->flags.secure) {
723 text = makeHiddenString(strlen(tPtr->text));
724 } else {
725 text = tPtr->text;
728 totalWidth = tPtr->view->size.width - 2*bd;
730 drawbuffer = XCreatePixmap(screen->display, view->window,
731 view->size.width, view->size.height, screen->depth);
732 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
733 0,0, view->size.width,view->size.height);
734 /* this is quite dirty */
735 viewbuffer.screen = view->screen;
736 viewbuffer.size = view->size;
737 viewbuffer.window = drawbuffer;
740 if (tPtr->textLen > 0) {
741 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
742 tPtr->textLen - tPtr->viewPosition);
744 th = WMFontHeight(tPtr->font);
746 ty = tPtr->offsetWidth;
747 switch (tPtr->flags.alignment) {
748 case WALeft:
749 tx = tPtr->offsetWidth + 1;
750 if (tw < tPtr->usableWidth)
751 XFillRectangle(screen->display, drawbuffer,
752 WMColorGC(screen->white),
753 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
754 break;
756 case WACenter:
757 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
758 if (tw < tPtr->usableWidth)
759 XClearArea(screen->display, view->window, bd, bd,
760 totalWidth, view->size.height-2*bd, False);
761 break;
763 default:
764 case WARight:
765 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
766 if (tw < tPtr->usableWidth)
767 XClearArea(screen->display, view->window, bd, bd,
768 totalWidth-tw, view->size.height-2*bd, False);
769 break;
772 if (!tPtr->flags.enabled)
773 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
775 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
776 tPtr->font, tx, ty,
777 &(text[tPtr->viewPosition]),
778 tPtr->textLen - tPtr->viewPosition);
780 if (tPtr->selection.count) {
781 int count,count2;
783 count = tPtr->selection.count < 0
784 ? tPtr->selection.position + tPtr->selection.count
785 : tPtr->selection.position;
786 count2 = abs(tPtr->selection.count);
787 if (count < tPtr->viewPosition) {
788 count2 = abs(count2 - abs(tPtr->viewPosition - count));
789 count = tPtr->viewPosition;
793 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
794 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
796 XSetBackground(screen->display, screen->textFieldGC,
797 screen->gray->color.pixel);
799 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
800 tPtr->font, rx, ty, &(text[count]),
801 count2);
803 XSetBackground(screen->display, screen->textFieldGC,
804 screen->white->color.pixel);
807 if (!tPtr->flags.enabled)
808 WMSetColorInGC(screen->black, screen->textFieldGC);
809 } else {
810 XFillRectangle(screen->display, drawbuffer,
811 WMColorGC(screen->white),
812 bd,bd, totalWidth,view->size.height-2*bd);
815 /* draw relief */
816 if (tPtr->flags.bordered) {
817 drawRelief(&viewbuffer, tPtr->flags.beveled);
820 if (tPtr->flags.secure)
821 free(text);
822 XCopyArea(screen->display, drawbuffer, view->window,
823 screen->copyGC, 0,0, view->size.width,
824 view->size.height,0,0);
825 XFreePixmap(screen->display, drawbuffer);
827 /* draw cursor */
828 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
829 paintCursor(tPtr);
834 #if 0
835 static void
836 blinkCursor(void *data)
838 TextField *tPtr = (TextField*)data;
840 if (tPtr->flags.cursorOn) {
841 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
842 data);
843 } else {
844 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
845 data);
847 paintCursor(tPtr);
848 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
850 #endif
853 static void
854 handleEvents(XEvent *event, void *data)
856 TextField *tPtr = (TextField*)data;
858 CHECK_CLASS(data, WC_TextField);
861 switch (event->type) {
862 case FocusIn:
863 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
864 return;
865 tPtr->flags.focused = 1;
866 #if 0
867 if (!tPtr->timerID) {
868 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
869 blinkCursor, tPtr);
871 #endif
872 paintTextField(tPtr);
874 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
876 tPtr->flags.notIllegalMovement = 0;
877 break;
879 case FocusOut:
880 tPtr->flags.focused = 0;
881 #if 0
882 if (tPtr->timerID)
883 WMDeleteTimerHandler(tPtr->timerID);
884 tPtr->timerID = NULL;
885 #endif
887 paintTextField(tPtr);
888 if (!tPtr->flags.notIllegalMovement) {
889 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
890 (void*)WMIllegalTextMovement);
892 break;
894 case Expose:
895 if (event->xexpose.count!=0)
896 break;
897 paintTextField(tPtr);
898 break;
900 case DestroyNotify:
901 destroyTextField(tPtr);
902 break;
907 static void
908 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
910 char buffer[64];
911 KeySym ksym;
912 int count, refresh = 0;
913 int control_pressed = 0;
915 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK) {
916 control_pressed = 1;
919 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
920 buffer[count] = '\0';
922 if (!(event->xkey.state & ShiftMask)) {
923 if (tPtr->selection.count)
924 refresh = 1;
925 tPtr->prevselection = tPtr->selection;
926 tPtr->selection.position = tPtr->cursorPosition;
927 tPtr->selection.count = 0;
930 /* Be careful in any case in this switch statement, never to call
931 * to more than a function that can generate text change notifications.
932 * Only one text change notification should be sent in any case.
933 * Else hazardous things can happen.
934 * Maybe we need a better solution than the function wrapper to inform
935 * functions that change text in text fields, if they need to send a
936 * change notification or not. -Dan
938 switch (ksym) {
939 case XK_Tab:
940 case XK_ISO_Left_Tab:
941 if (event->xkey.state & ShiftMask) {
942 if (tPtr->view->prevFocusChain) {
943 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
944 tPtr->view->prevFocusChain);
945 tPtr->flags.notIllegalMovement = 1;
947 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
948 (void*)WMBacktabTextMovement);
949 } else {
950 if (tPtr->view->nextFocusChain) {
951 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
952 tPtr->view->nextFocusChain);
953 tPtr->flags.notIllegalMovement = 1;
955 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
956 (void*)WMTabTextMovement);
958 break;
960 case XK_Return:
961 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
962 (void*)WMReturnTextMovement);
963 break;
965 case WM_EMACSKEY_LEFT:
966 if (!control_pressed) {
967 goto normal_key;
969 case XK_KP_Left:
970 case XK_Left:
971 if (tPtr->cursorPosition > 0) {
972 paintCursor(tPtr);
973 if (event->xkey.state & ControlMask) {
974 int i;
975 for (i = tPtr->cursorPosition - 1; i >= 0; i--)
976 if (tPtr->text[i] == ' ' || i == 0) {
977 tPtr->cursorPosition = i;
978 break;
980 } else {
981 tPtr->cursorPosition--;
983 if (tPtr->cursorPosition < tPtr->viewPosition) {
984 tPtr->viewPosition = tPtr->cursorPosition;
985 refresh = 1;
986 } else {
987 paintCursor(tPtr);
990 break;
992 case WM_EMACSKEY_RIGHT:
993 if (!control_pressed) {
994 goto normal_key;
996 case XK_KP_Right:
997 case XK_Right:
998 if (tPtr->cursorPosition < tPtr->textLen) {
999 paintCursor(tPtr);
1000 if (event->xkey.state & ControlMask) {
1001 int i;
1002 for (i = tPtr->cursorPosition + 1; i <= tPtr->textLen; i++)
1003 if (tPtr->text[i] == ' ' || i == tPtr->textLen) {
1004 tPtr->cursorPosition = i;
1005 break;
1007 } else {
1008 tPtr->cursorPosition++;
1010 while (WMWidthOfString(tPtr->font,
1011 &(tPtr->text[tPtr->viewPosition]),
1012 tPtr->cursorPosition-tPtr->viewPosition)
1013 > tPtr->usableWidth) {
1014 tPtr->viewPosition++;
1015 refresh = 1;
1017 if (!refresh)
1018 paintCursor(tPtr);
1020 break;
1022 case WM_EMACSKEY_HOME:
1023 if (!control_pressed) {
1024 goto normal_key;
1026 case XK_KP_Home:
1027 case XK_Home:
1028 if (tPtr->cursorPosition > 0) {
1029 paintCursor(tPtr);
1030 tPtr->cursorPosition = 0;
1031 if (tPtr->viewPosition > 0) {
1032 tPtr->viewPosition = 0;
1033 refresh = 1;
1034 } else {
1035 paintCursor(tPtr);
1038 break;
1040 case WM_EMACSKEY_END:
1041 if (!control_pressed) {
1042 goto normal_key;
1044 case XK_KP_End:
1045 case XK_End:
1046 if (tPtr->cursorPosition < tPtr->textLen) {
1047 paintCursor(tPtr);
1048 tPtr->cursorPosition = tPtr->textLen;
1049 tPtr->viewPosition = 0;
1050 while (WMWidthOfString(tPtr->font,
1051 &(tPtr->text[tPtr->viewPosition]),
1052 tPtr->textLen-tPtr->viewPosition)
1053 > tPtr->usableWidth) {
1054 tPtr->viewPosition++;
1055 refresh = 1;
1057 if (!refresh)
1058 paintCursor(tPtr);
1060 break;
1062 case WM_EMACSKEY_BS:
1063 if (!control_pressed) {
1064 goto normal_key;
1066 case XK_BackSpace:
1067 if (tPtr->cursorPosition > 0) {
1068 WMRange range;
1070 if (tPtr->prevselection.count) {
1071 range.position = tPtr->prevselection.count < 0
1072 ? tPtr->prevselection.position + tPtr->prevselection.count
1073 : tPtr->prevselection.position;
1075 range.count = abs(tPtr->prevselection.count);
1076 } else {
1077 range.position = tPtr->cursorPosition - 1;
1078 range.count = 1;
1080 WMDeleteTextFieldRange(tPtr, range);
1081 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1082 (void*)WMDeleteTextEvent);
1084 break;
1086 case WM_EMACSKEY_DEL:
1087 if (!control_pressed) {
1088 goto normal_key;
1090 case XK_KP_Delete:
1091 case XK_Delete:
1092 if (tPtr->cursorPosition < tPtr->textLen || tPtr->prevselection.count) {
1093 WMRange range;
1095 if (tPtr->prevselection.count) {
1096 range.position = tPtr->prevselection.count < 0
1097 ? tPtr->prevselection.position + tPtr->prevselection.count
1098 : tPtr->prevselection.position;
1100 range.count = abs(tPtr->prevselection.count);
1101 } else {
1102 range.position = tPtr->cursorPosition;
1103 range.count = 1;
1105 WMDeleteTextFieldRange(tPtr, range);
1106 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1107 (void*)WMDeleteTextEvent);
1109 break;
1111 normal_key:
1112 default:
1113 if (count > 0 && !iscntrl(buffer[0])) {
1114 WMRange range;
1116 if (tPtr->prevselection.count) {
1117 range.position = tPtr->prevselection.count < 0
1118 ? tPtr->prevselection.position + tPtr->prevselection.count
1119 : tPtr->prevselection.position;
1121 range.count = abs(tPtr->prevselection.count);
1122 } else {
1123 range.position = tPtr->cursorPosition;
1124 range.count = 1;
1126 if (tPtr->prevselection.count)
1127 WMDeleteTextFieldRange(tPtr, range);
1128 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1129 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1130 (void*)WMInsertTextEvent);
1131 } else {
1132 return;
1134 break;
1136 if (event->xkey.state & ShiftMask) {
1137 if (tPtr->selection.count == 0)
1138 tPtr->selection.position = tPtr->cursorPosition;
1139 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1140 refresh = 1;
1142 tPtr->prevselection.count = 0;
1143 if (refresh) {
1144 paintTextField(tPtr);
1149 static int
1150 pointToCursorPosition(TextField *tPtr, int x)
1152 int a, b, mid;
1153 int tw;
1155 if (tPtr->flags.bordered)
1156 x -= 2;
1158 a = tPtr->viewPosition;
1159 b = tPtr->viewPosition + tPtr->textLen;
1160 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1161 tPtr->textLen - tPtr->viewPosition) < x)
1162 return tPtr->textLen;
1164 while (a < b && b-a>1) {
1165 mid = (a+b)/2;
1166 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1167 mid - tPtr->viewPosition);
1168 if (tw > x)
1169 b = mid;
1170 else if (tw < x)
1171 a = mid;
1172 else
1173 return mid;
1175 return (a+b)/2;
1179 static void
1180 handleTextFieldActionEvents(XEvent *event, void *data)
1182 TextField *tPtr = (TextField*)data;
1183 static int move;
1185 CHECK_CLASS(data, WC_TextField);
1187 switch (event->type) {
1188 case KeyPress:
1189 if (tPtr->flags.enabled && tPtr->flags.focused) {
1190 handleTextFieldKeyPress(tPtr, event);
1191 XGrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen),
1192 W_VIEW(tPtr)->window, False,
1193 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1194 GrabModeAsync, GrabModeAsync, None,
1195 W_VIEW(tPtr)->screen->invisibleCursor,
1196 CurrentTime);
1197 tPtr->flags.pointerGrabbed = 1;
1199 break;
1201 case MotionNotify:
1203 if (tPtr->flags.pointerGrabbed) {
1204 tPtr->flags.pointerGrabbed = 0;
1205 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1208 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1210 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1211 tPtr->usableWidth) {
1212 if (WMWidthOfString(tPtr->font,
1213 &(tPtr->text[tPtr->viewPosition]),
1214 tPtr->cursorPosition-tPtr->viewPosition)
1215 > tPtr->usableWidth) {
1216 tPtr->viewPosition++;
1218 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1219 paintCursor(tPtr);
1220 tPtr->viewPosition--;
1223 if (!tPtr->selection.count) {
1224 tPtr->selection.position = tPtr->cursorPosition;
1227 tPtr->cursorPosition =
1228 pointToCursorPosition(tPtr, event->xmotion.x);
1230 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1233 printf("notify %d %d\n",event->xmotion.x,tPtr->usableWidth);
1236 paintCursor(tPtr);
1237 paintTextField(tPtr);
1240 if (move) {
1241 XSetSelectionOwner(tPtr->view->screen->display,
1242 XA_PRIMARY, tPtr->view->window, CurrentTime);
1244 break;
1246 case ButtonPress:
1247 if (tPtr->flags.pointerGrabbed) {
1248 tPtr->flags.pointerGrabbed = 0;
1249 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1250 break;
1253 move = 1;
1254 switch (tPtr->flags.alignment) {
1255 int textWidth;
1256 case WARight:
1257 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1258 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1259 WMSetFocusToWidget(tPtr);
1260 } else if (tPtr->flags.focused) {
1261 tPtr->selection.count = 0;
1263 if(textWidth < tPtr->usableWidth){
1264 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1265 event->xbutton.x - tPtr->usableWidth
1266 + textWidth);
1268 else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1269 event->xbutton.x);
1271 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1272 event->xbutton.x);
1273 tPtr->cursorPosition += tPtr->usableWidth - textWidth;
1276 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1277 event->xbutton.x);
1279 paintTextField(tPtr);
1280 break;
1282 case WALeft:
1283 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1284 WMSetFocusToWidget(tPtr);
1285 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1286 event->xbutton.x);
1287 paintTextField(tPtr);
1288 } else if (tPtr->flags.focused) {
1289 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1290 event->xbutton.x);
1291 tPtr->selection.count = 0;
1292 paintTextField(tPtr);
1294 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1295 char *text;
1297 text = W_GetTextSelection(tPtr->view->screen, XA_PRIMARY);
1299 if (!text) {
1300 text = W_GetTextSelection(tPtr->view->screen,
1301 tPtr->view->screen->clipboardAtom);
1303 if (!text) {
1304 text = W_GetTextSelection(tPtr->view->screen,
1305 XA_CUT_BUFFER0);
1307 if (text) {
1308 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1309 XFree(text);
1310 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1311 (void*)WMInsertTextEvent);
1314 break;
1316 break;
1318 case ButtonRelease:
1319 if (tPtr->flags.pointerGrabbed) {
1320 tPtr->flags.pointerGrabbed = 0;
1321 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1324 move = 0;
1325 break;
1330 static void
1331 destroyTextField(TextField *tPtr)
1333 #if 0
1334 if (tPtr->timerID)
1335 WMDeleteTimerHandler(tPtr->timerID);
1336 #endif
1338 WMReleaseFont(tPtr->font);
1340 if (tPtr->text)
1341 free(tPtr->text);
1343 free(tPtr);