added color editing and title justification to appearance section
[wmaker-crm.git] / WINGs / wtextfield.c
blob027dedfe3111f94dfd39f0988ceca80ce8771392
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;
234 tPtr->selection.count = 0;
235 paintTextField(tPtr);
239 WMTextField*
240 WMCreateTextField(WMWidget *parent)
242 TextField *tPtr;
244 tPtr = wmalloc(sizeof(TextField));
245 memset(tPtr, 0, sizeof(TextField));
247 tPtr->widgetClass = WC_TextField;
249 tPtr->view = W_CreateView(W_VIEW(parent));
250 if (!tPtr->view) {
251 free(tPtr);
252 return NULL;
254 tPtr->view->self = tPtr;
256 tPtr->view->attribFlags |= CWCursor;
257 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
259 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
261 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
262 tPtr->text[0] = 0;
263 tPtr->textLen = 0;
264 tPtr->bufferSize = MIN_TEXT_BUFFER;
266 tPtr->flags.enabled = 1;
268 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
269 |FocusChangeMask, handleEvents, tPtr);
271 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
273 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
275 tPtr->flags.bordered = DEFAULT_BORDERED;
276 tPtr->flags.beveled = True;
277 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
278 tPtr->offsetWidth =
279 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
281 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
282 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
283 handleTextFieldActionEvents, tPtr);
285 WMCreateSelectionHandler(tPtr, XA_PRIMARY, CurrentTime, requestHandler,
286 lostHandler, NULL);
289 tPtr->flags.cursorOn = 1;
291 return tPtr;
295 void
296 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
298 tPtr->delegate = delegate;
302 void
303 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
305 int len;
307 CHECK_CLASS(tPtr, WC_TextField);
309 if (!text)
310 return;
312 len = strlen(text);
314 /* check if buffer will hold the text */
315 if (len + tPtr->textLen >= tPtr->bufferSize) {
316 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
317 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
320 if (position < 0 || position >= tPtr->textLen) {
321 /* append the text at the end */
322 strcat(tPtr->text, text);
324 incrToFit(tPtr);
326 tPtr->textLen += len;
327 tPtr->cursorPosition += len;
328 } else {
329 /* insert text at position */
330 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
331 tPtr->textLen-position+1);
333 memcpy(&(tPtr->text[position]), text, len);
335 tPtr->textLen += len;
336 if (position >= tPtr->cursorPosition) {
337 tPtr->cursorPosition += len;
338 incrToFit2(tPtr);
339 } else {
340 incrToFit(tPtr);
344 paintTextField(tPtr);
348 void
349 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
351 CHECK_CLASS(tPtr, WC_TextField);
353 if (range.position >= tPtr->textLen)
354 return;
356 if (range.count < 1) {
357 if (range.position < 0)
358 range.position = 0;
359 tPtr->text[range.position] = 0;
360 tPtr->textLen = range.position;
362 tPtr->cursorPosition = 0;
363 tPtr->viewPosition = 0;
364 } else {
365 if (range.position + range.count > tPtr->textLen)
366 range.count = tPtr->textLen - range.position;
367 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
368 tPtr->textLen - (range.position+range.count) + 1);
369 tPtr->textLen -= range.count;
371 if (tPtr->cursorPosition > range.position)
372 tPtr->cursorPosition -= range.count;
374 decrToFit(tPtr);
377 paintTextField(tPtr);
382 char*
383 WMGetTextFieldText(WMTextField *tPtr)
385 CHECK_CLASS(tPtr, WC_TextField);
387 return wstrdup(tPtr->text);
391 void
392 WMSetTextFieldText(WMTextField *tPtr, char *text)
394 if ((text && strcmp(tPtr->text, text) == 0) ||
395 (!text && tPtr->textLen == 0))
396 return;
398 if (text==NULL) {
399 tPtr->text[0] = 0;
400 tPtr->textLen = 0;
401 } else {
402 tPtr->textLen = strlen(text);
404 if (tPtr->textLen >= tPtr->bufferSize) {
405 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
406 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
408 strcpy(tPtr->text, text);
411 if (tPtr->textLen < tPtr->cursorPosition)
412 tPtr->cursorPosition = tPtr->textLen;
414 tPtr->cursorPosition = tPtr->textLen;
415 tPtr->viewPosition = 0;
416 tPtr->selection.count = 0;
418 if (tPtr->view->flags.realized)
419 paintTextField(tPtr);
423 void
424 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
426 /* TODO: update font change after field is mapped */
427 WMReleaseFont(tPtr->font);
428 tPtr->font = WMRetainFont(font);
432 void
433 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
435 tPtr->flags.alignment = alignment;
436 if (alignment!=WALeft) {
437 wwarning("only left alignment is supported in textfields");
438 return;
441 if (tPtr->view->flags.realized) {
442 paintTextField(tPtr);
447 void
448 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
450 tPtr->flags.bordered = bordered;
452 if (tPtr->view->flags.realized) {
453 paintTextField(tPtr);
458 void
459 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
461 tPtr->flags.beveled = flag;
463 if (tPtr->view->flags.realized) {
464 paintTextField(tPtr);
470 void
471 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
473 tPtr->flags.secure = flag;
475 if (tPtr->view->flags.realized) {
476 paintTextField(tPtr);
481 Bool
482 WMGetTextFieldEditable(WMTextField *tPtr)
484 return tPtr->flags.enabled;
488 void
489 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
491 tPtr->flags.enabled = flag;
493 if (tPtr->view->flags.realized) {
494 paintTextField(tPtr);
499 void
500 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
502 if (tPtr->flags.enabled) {
503 if (range.position < 0) {
504 range.count += range.position;
505 range.count = (range.count < 0) ? 0 : range.count;
506 range.position = 0;
507 } else if (range.position > tPtr->textLen) {
508 range.position = tPtr->textLen;
509 range.count = 0;
512 if (range.position + range.count > tPtr->textLen)
513 range.count = tPtr->textLen - range.position;
515 tPtr->prevselection = tPtr->selection; /* check if this is needed */
517 tPtr->selection = range;
519 if (tPtr->view->flags.realized) {
520 paintTextField(tPtr);
526 void
527 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
529 if (tPtr->flags.enabled) {
530 if (position > tPtr->textLen)
531 position = tPtr->textLen;
533 tPtr->cursorPosition = position;
534 if (tPtr->view->flags.realized) {
535 paintTextField(tPtr);
541 void
542 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
544 CHECK_CLASS(tPtr, WC_TextField);
545 if (next == NULL) {
546 if (tPtr->view->nextFocusChain)
547 tPtr->view->nextFocusChain->prevFocusChain = NULL;
548 tPtr->view->nextFocusChain = NULL;
549 return;
552 CHECK_CLASS(next, WC_TextField);
554 if (tPtr->view->nextFocusChain)
555 tPtr->view->nextFocusChain->prevFocusChain = NULL;
556 if (next->view->prevFocusChain)
557 next->view->prevFocusChain->nextFocusChain = NULL;
559 tPtr->view->nextFocusChain = next->view;
560 next->view->prevFocusChain = tPtr->view;
564 void
565 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
567 CHECK_CLASS(tPtr, WC_TextField);
568 if (prev == NULL) {
569 if (tPtr->view->prevFocusChain)
570 tPtr->view->prevFocusChain->nextFocusChain = NULL;
571 tPtr->view->prevFocusChain = NULL;
572 return;
575 CHECK_CLASS(prev, WC_TextField);
577 if (tPtr->view->prevFocusChain)
578 tPtr->view->prevFocusChain->nextFocusChain = NULL;
579 if (prev->view->nextFocusChain)
580 prev->view->nextFocusChain->prevFocusChain = NULL;
582 tPtr->view->prevFocusChain = prev->view;
583 prev->view->nextFocusChain = tPtr->view;
587 static void
588 resizeTextField(WMTextField *tPtr, unsigned int width, unsigned int height)
590 W_ResizeView(tPtr->view, width, height);
592 tPtr->offsetWidth =
593 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
595 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
599 static char*
600 makeHiddenString(int length)
602 char *data = wmalloc(length+1);
604 memset(data, '*', length);
605 data[length] = '\0';
606 return data;
610 static void
611 paintCursor(TextField *tPtr)
613 int cx;
614 WMScreen *screen = tPtr->view->screen;
615 int textWidth;
616 char *text;
618 if (tPtr->flags.secure)
619 text = makeHiddenString(strlen(tPtr->text));
620 else
621 text = tPtr->text;
623 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
624 tPtr->cursorPosition-tPtr->viewPosition);
626 switch (tPtr->flags.alignment) {
627 case WARight:
628 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
629 if (textWidth < tPtr->usableWidth)
630 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
631 else
632 cx += tPtr->offsetWidth + 1;
633 break;
634 case WALeft:
635 cx += tPtr->offsetWidth + 1;
636 break;
637 case WAJustified:
638 /* not supported */
639 case WACenter:
640 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
641 if (textWidth < tPtr->usableWidth)
642 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
643 else
644 cx += tPtr->offsetWidth;
645 break;
648 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
649 cx, tPtr->offsetWidth, 1,
650 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
651 printf("%d %d\n",cx,tPtr->cursorPosition);
653 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
654 cx, tPtr->offsetWidth, cx,
655 tPtr->view->size.height - tPtr->offsetWidth - 1);
657 if (tPtr->flags.secure)
658 free(text);
663 static void
664 drawRelief(WMView *view, Bool beveled)
666 WMScreen *scr = view->screen;
667 Display *dpy = scr->display;
668 GC wgc;
669 GC lgc;
670 GC dgc;
671 int width = view->size.width;
672 int height = view->size.height;
674 dgc = WMColorGC(scr->darkGray);
676 if (!beveled) {
677 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
679 return;
681 wgc = WMColorGC(scr->white);
682 lgc = WMColorGC(scr->gray);
684 /* top left */
685 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
686 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
688 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
689 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
691 /* bottom right */
692 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
693 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
695 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
696 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
700 static void
701 paintTextField(TextField *tPtr)
703 W_Screen *screen = tPtr->view->screen;
704 W_View *view = tPtr->view;
705 W_View viewbuffer;
706 int tx, ty, tw, th;
707 int rx;
708 int bd;
709 int totalWidth;
710 char *text;
711 Pixmap drawbuffer;
714 if (!view->flags.realized || !view->flags.mapped)
715 return;
717 if (!tPtr->flags.bordered) {
718 bd = 0;
719 } else {
720 bd = 2;
723 if (tPtr->flags.secure) {
724 text = makeHiddenString(strlen(tPtr->text));
725 } else {
726 text = tPtr->text;
729 totalWidth = tPtr->view->size.width - 2*bd;
731 drawbuffer = XCreatePixmap(screen->display, view->window,
732 view->size.width, view->size.height, screen->depth);
733 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
734 0,0, view->size.width,view->size.height);
735 /* this is quite dirty */
736 viewbuffer.screen = view->screen;
737 viewbuffer.size = view->size;
738 viewbuffer.window = drawbuffer;
741 if (tPtr->textLen > 0) {
742 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
743 tPtr->textLen - tPtr->viewPosition);
745 th = WMFontHeight(tPtr->font);
747 ty = tPtr->offsetWidth;
748 switch (tPtr->flags.alignment) {
749 case WALeft:
750 tx = tPtr->offsetWidth + 1;
751 if (tw < tPtr->usableWidth)
752 XFillRectangle(screen->display, drawbuffer,
753 WMColorGC(screen->white),
754 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
755 break;
757 case WACenter:
758 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
759 if (tw < tPtr->usableWidth)
760 XClearArea(screen->display, view->window, bd, bd,
761 totalWidth, view->size.height-2*bd, False);
762 break;
764 default:
765 case WARight:
766 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
767 if (tw < tPtr->usableWidth)
768 XClearArea(screen->display, view->window, bd, bd,
769 totalWidth-tw, view->size.height-2*bd, False);
770 break;
773 if (!tPtr->flags.enabled)
774 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
776 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
777 tPtr->font, tx, ty,
778 &(text[tPtr->viewPosition]),
779 tPtr->textLen - tPtr->viewPosition);
781 if (tPtr->selection.count) {
782 int count,count2;
784 count = tPtr->selection.count < 0
785 ? tPtr->selection.position + tPtr->selection.count
786 : tPtr->selection.position;
787 count2 = abs(tPtr->selection.count);
788 if (count < tPtr->viewPosition) {
789 count2 = abs(count2 - abs(tPtr->viewPosition - count));
790 count = tPtr->viewPosition;
794 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
795 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
797 XSetBackground(screen->display, screen->textFieldGC,
798 screen->gray->color.pixel);
800 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
801 tPtr->font, rx, ty, &(text[count]),
802 count2);
804 XSetBackground(screen->display, screen->textFieldGC,
805 screen->white->color.pixel);
808 if (!tPtr->flags.enabled)
809 WMSetColorInGC(screen->black, screen->textFieldGC);
810 } else {
811 XFillRectangle(screen->display, drawbuffer,
812 WMColorGC(screen->white),
813 bd,bd, totalWidth,view->size.height-2*bd);
816 /* draw relief */
817 if (tPtr->flags.bordered) {
818 drawRelief(&viewbuffer, tPtr->flags.beveled);
821 if (tPtr->flags.secure)
822 free(text);
823 XCopyArea(screen->display, drawbuffer, view->window,
824 screen->copyGC, 0,0, view->size.width,
825 view->size.height,0,0);
826 XFreePixmap(screen->display, drawbuffer);
828 /* draw cursor */
829 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
830 paintCursor(tPtr);
835 #if 0
836 static void
837 blinkCursor(void *data)
839 TextField *tPtr = (TextField*)data;
841 if (tPtr->flags.cursorOn) {
842 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
843 data);
844 } else {
845 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
846 data);
848 paintCursor(tPtr);
849 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
851 #endif
854 static void
855 handleEvents(XEvent *event, void *data)
857 TextField *tPtr = (TextField*)data;
859 CHECK_CLASS(data, WC_TextField);
862 switch (event->type) {
863 case FocusIn:
864 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
865 return;
866 tPtr->flags.focused = 1;
867 #if 0
868 if (!tPtr->timerID) {
869 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
870 blinkCursor, tPtr);
872 #endif
873 paintTextField(tPtr);
875 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
877 tPtr->flags.notIllegalMovement = 0;
878 break;
880 case FocusOut:
881 tPtr->flags.focused = 0;
882 #if 0
883 if (tPtr->timerID)
884 WMDeleteTimerHandler(tPtr->timerID);
885 tPtr->timerID = NULL;
886 #endif
888 paintTextField(tPtr);
889 if (!tPtr->flags.notIllegalMovement) {
890 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
891 (void*)WMIllegalTextMovement);
893 break;
895 case Expose:
896 if (event->xexpose.count!=0)
897 break;
898 paintTextField(tPtr);
899 break;
901 case DestroyNotify:
902 destroyTextField(tPtr);
903 break;
908 static void
909 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
911 char buffer[64];
912 KeySym ksym;
913 int count, refresh = 0;
914 int control_pressed = 0;
916 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK) {
917 control_pressed = 1;
920 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
921 buffer[count] = '\0';
923 if (!(event->xkey.state & ShiftMask)) {
924 if (tPtr->selection.count)
925 refresh = 1;
926 tPtr->prevselection = tPtr->selection;
927 tPtr->selection.position = tPtr->cursorPosition;
928 tPtr->selection.count = 0;
931 /* Be careful in any case in this switch statement, never to call
932 * to more than a function that can generate text change notifications.
933 * Only one text change notification should be sent in any case.
934 * Else hazardous things can happen.
935 * Maybe we need a better solution than the function wrapper to inform
936 * functions that change text in text fields, if they need to send a
937 * change notification or not. -Dan
939 switch (ksym) {
940 case XK_Tab:
941 case XK_ISO_Left_Tab:
942 if (event->xkey.state & ShiftMask) {
943 if (tPtr->view->prevFocusChain) {
944 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
945 tPtr->view->prevFocusChain);
946 tPtr->flags.notIllegalMovement = 1;
948 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
949 (void*)WMBacktabTextMovement);
950 } else {
951 if (tPtr->view->nextFocusChain) {
952 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
953 tPtr->view->nextFocusChain);
954 tPtr->flags.notIllegalMovement = 1;
956 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
957 (void*)WMTabTextMovement);
959 break;
961 case XK_Return:
962 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
963 (void*)WMReturnTextMovement);
964 break;
966 case WM_EMACSKEY_LEFT:
967 if (!control_pressed) {
968 goto normal_key;
970 case XK_KP_Left:
971 case XK_Left:
972 if (tPtr->cursorPosition > 0) {
973 paintCursor(tPtr);
974 if (event->xkey.state & ControlMask) {
975 int i;
976 for (i = tPtr->cursorPosition - 1; i >= 0; i--)
977 if (tPtr->text[i] == ' ' || i == 0) {
978 tPtr->cursorPosition = i;
979 break;
981 } else {
982 tPtr->cursorPosition--;
984 if (tPtr->cursorPosition < tPtr->viewPosition) {
985 tPtr->viewPosition = tPtr->cursorPosition;
986 refresh = 1;
987 } else {
988 paintCursor(tPtr);
991 break;
993 case WM_EMACSKEY_RIGHT:
994 if (!control_pressed) {
995 goto normal_key;
997 case XK_KP_Right:
998 case XK_Right:
999 if (tPtr->cursorPosition < tPtr->textLen) {
1000 paintCursor(tPtr);
1001 if (event->xkey.state & ControlMask) {
1002 int i;
1003 for (i = tPtr->cursorPosition + 1; i <= tPtr->textLen; i++)
1004 if (tPtr->text[i] == ' ' || i == tPtr->textLen) {
1005 tPtr->cursorPosition = i;
1006 break;
1008 } else {
1009 tPtr->cursorPosition++;
1011 while (WMWidthOfString(tPtr->font,
1012 &(tPtr->text[tPtr->viewPosition]),
1013 tPtr->cursorPosition-tPtr->viewPosition)
1014 > tPtr->usableWidth) {
1015 tPtr->viewPosition++;
1016 refresh = 1;
1018 if (!refresh)
1019 paintCursor(tPtr);
1021 break;
1023 case WM_EMACSKEY_HOME:
1024 if (!control_pressed) {
1025 goto normal_key;
1027 case XK_KP_Home:
1028 case XK_Home:
1029 if (tPtr->cursorPosition > 0) {
1030 paintCursor(tPtr);
1031 tPtr->cursorPosition = 0;
1032 if (tPtr->viewPosition > 0) {
1033 tPtr->viewPosition = 0;
1034 refresh = 1;
1035 } else {
1036 paintCursor(tPtr);
1039 break;
1041 case WM_EMACSKEY_END:
1042 if (!control_pressed) {
1043 goto normal_key;
1045 case XK_KP_End:
1046 case XK_End:
1047 if (tPtr->cursorPosition < tPtr->textLen) {
1048 paintCursor(tPtr);
1049 tPtr->cursorPosition = tPtr->textLen;
1050 tPtr->viewPosition = 0;
1051 while (WMWidthOfString(tPtr->font,
1052 &(tPtr->text[tPtr->viewPosition]),
1053 tPtr->textLen-tPtr->viewPosition)
1054 > tPtr->usableWidth) {
1055 tPtr->viewPosition++;
1056 refresh = 1;
1058 if (!refresh)
1059 paintCursor(tPtr);
1061 break;
1063 case WM_EMACSKEY_BS:
1064 if (!control_pressed) {
1065 goto normal_key;
1067 case XK_BackSpace:
1068 if (tPtr->cursorPosition > 0) {
1069 WMRange range;
1071 if (tPtr->prevselection.count) {
1072 range.position = tPtr->prevselection.count < 0
1073 ? tPtr->prevselection.position + tPtr->prevselection.count
1074 : tPtr->prevselection.position;
1076 range.count = abs(tPtr->prevselection.count);
1077 } else {
1078 range.position = tPtr->cursorPosition - 1;
1079 range.count = 1;
1081 WMDeleteTextFieldRange(tPtr, range);
1082 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1083 (void*)WMDeleteTextEvent);
1085 break;
1087 case WM_EMACSKEY_DEL:
1088 if (!control_pressed) {
1089 goto normal_key;
1091 case XK_KP_Delete:
1092 case XK_Delete:
1093 if (tPtr->cursorPosition < tPtr->textLen || tPtr->prevselection.count) {
1094 WMRange range;
1096 if (tPtr->prevselection.count) {
1097 range.position = tPtr->prevselection.count < 0
1098 ? tPtr->prevselection.position + tPtr->prevselection.count
1099 : tPtr->prevselection.position;
1101 range.count = abs(tPtr->prevselection.count);
1102 } else {
1103 range.position = tPtr->cursorPosition;
1104 range.count = 1;
1106 WMDeleteTextFieldRange(tPtr, range);
1107 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1108 (void*)WMDeleteTextEvent);
1110 break;
1112 normal_key:
1113 default:
1114 if (count > 0 && !iscntrl(buffer[0])) {
1115 WMRange range;
1117 if (tPtr->prevselection.count) {
1118 range.position = tPtr->prevselection.count < 0
1119 ? tPtr->prevselection.position + tPtr->prevselection.count
1120 : tPtr->prevselection.position;
1122 range.count = abs(tPtr->prevselection.count);
1123 } else {
1124 range.position = tPtr->cursorPosition;
1125 range.count = 1;
1127 if (tPtr->prevselection.count)
1128 WMDeleteTextFieldRange(tPtr, range);
1129 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1130 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1131 (void*)WMInsertTextEvent);
1132 } else {
1133 return;
1135 break;
1137 if (event->xkey.state & ShiftMask) {
1138 if (tPtr->selection.count == 0)
1139 tPtr->selection.position = tPtr->cursorPosition;
1140 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1141 refresh = 1;
1143 tPtr->prevselection.count = 0;
1144 if (refresh) {
1145 paintTextField(tPtr);
1150 static int
1151 pointToCursorPosition(TextField *tPtr, int x)
1153 int a, b, mid;
1154 int tw;
1156 if (tPtr->flags.bordered)
1157 x -= 2;
1159 a = tPtr->viewPosition;
1160 b = tPtr->viewPosition + tPtr->textLen;
1161 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1162 tPtr->textLen - tPtr->viewPosition) < x)
1163 return tPtr->textLen;
1165 while (a < b && b-a>1) {
1166 mid = (a+b)/2;
1167 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1168 mid - tPtr->viewPosition);
1169 if (tw > x)
1170 b = mid;
1171 else if (tw < x)
1172 a = mid;
1173 else
1174 return mid;
1176 return (a+b)/2;
1180 static void
1181 handleTextFieldActionEvents(XEvent *event, void *data)
1183 TextField *tPtr = (TextField*)data;
1184 static int move;
1186 CHECK_CLASS(data, WC_TextField);
1188 switch (event->type) {
1189 case KeyPress:
1190 if (tPtr->flags.enabled && tPtr->flags.focused) {
1191 handleTextFieldKeyPress(tPtr, event);
1192 XGrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen),
1193 W_VIEW(tPtr)->window, False,
1194 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1195 GrabModeAsync, GrabModeAsync, None,
1196 W_VIEW(tPtr)->screen->invisibleCursor,
1197 CurrentTime);
1198 tPtr->flags.pointerGrabbed = 1;
1200 break;
1202 case MotionNotify:
1204 if (tPtr->flags.pointerGrabbed) {
1205 tPtr->flags.pointerGrabbed = 0;
1206 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1209 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1211 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1212 tPtr->usableWidth) {
1213 if (WMWidthOfString(tPtr->font,
1214 &(tPtr->text[tPtr->viewPosition]),
1215 tPtr->cursorPosition-tPtr->viewPosition)
1216 > tPtr->usableWidth) {
1217 tPtr->viewPosition++;
1219 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1220 paintCursor(tPtr);
1221 tPtr->viewPosition--;
1224 if (!tPtr->selection.count) {
1225 tPtr->selection.position = tPtr->cursorPosition;
1228 tPtr->cursorPosition =
1229 pointToCursorPosition(tPtr, event->xmotion.x);
1231 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1234 printf("notify %d %d\n",event->xmotion.x,tPtr->usableWidth);
1237 paintCursor(tPtr);
1238 paintTextField(tPtr);
1241 if (move) {
1242 XSetSelectionOwner(tPtr->view->screen->display,
1243 XA_PRIMARY, tPtr->view->window, CurrentTime);
1245 break;
1247 case ButtonPress:
1248 if (tPtr->flags.pointerGrabbed) {
1249 tPtr->flags.pointerGrabbed = 0;
1250 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1251 break;
1254 move = 1;
1255 switch (tPtr->flags.alignment) {
1256 int textWidth;
1257 case WARight:
1258 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1259 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1260 WMSetFocusToWidget(tPtr);
1261 } else if (tPtr->flags.focused) {
1262 tPtr->selection.count = 0;
1264 if(textWidth < tPtr->usableWidth){
1265 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1266 event->xbutton.x - tPtr->usableWidth
1267 + textWidth);
1269 else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1270 event->xbutton.x);
1272 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1273 event->xbutton.x);
1274 tPtr->cursorPosition += tPtr->usableWidth - textWidth;
1277 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1278 event->xbutton.x);
1280 paintTextField(tPtr);
1281 break;
1283 case WALeft:
1284 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1285 WMSetFocusToWidget(tPtr);
1286 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1287 event->xbutton.x);
1288 paintTextField(tPtr);
1289 } else if (tPtr->flags.focused) {
1290 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1291 event->xbutton.x);
1292 tPtr->selection.count = 0;
1293 paintTextField(tPtr);
1295 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1296 char *text;
1298 text = W_GetTextSelection(tPtr->view->screen, XA_PRIMARY);
1300 if (!text) {
1301 text = W_GetTextSelection(tPtr->view->screen,
1302 tPtr->view->screen->clipboardAtom);
1304 if (!text) {
1305 text = W_GetTextSelection(tPtr->view->screen,
1306 XA_CUT_BUFFER0);
1308 if (text) {
1309 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1310 XFree(text);
1311 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1312 (void*)WMInsertTextEvent);
1315 break;
1317 break;
1319 case ButtonRelease:
1320 if (tPtr->flags.pointerGrabbed) {
1321 tPtr->flags.pointerGrabbed = 0;
1322 XUngrabPointer(WMScreenDisplay(W_VIEW(tPtr)->screen), CurrentTime);
1325 move = 0;
1326 break;
1331 static void
1332 destroyTextField(TextField *tPtr)
1334 #if 0
1335 if (tPtr->timerID)
1336 WMDeleteTimerHandler(tPtr->timerID);
1337 #endif
1339 WMReleaseFont(tPtr->font);
1341 if (tPtr->text)
1342 free(tPtr->text);
1344 free(tPtr);