Fixed some focus related problems when switching workspaces, including the
[wmaker-crm.git] / WINGs / wtextfield.c
blobb071c080742f4502bca0fae6ebea8bd4f1eddaac
5 #include "WINGsP.h"
6 #include "wconfig.h"
8 #include <X11/keysym.h>
9 #include <X11/Xatom.h>
11 #include <ctype.h>
13 #define CURSOR_BLINK_ON_DELAY 600
14 #define CURSOR_BLINK_OFF_DELAY 300
18 char *WMTextDidChangeNotification = "WMTextDidChangeNotification";
19 char *WMTextDidBeginEditingNotification = "WMTextDidBeginEditingNotification";
20 char *WMTextDidEndEditingNotification = "WMTextDidEndEditingNotification";
23 typedef struct W_TextField {
24 W_Class widgetClass;
25 W_View *view;
27 #if 0
28 struct W_TextField *nextField; /* next textfield in the chain */
29 struct W_TextField *prevField;
30 #endif
32 char *text;
33 int textLen; /* size of text */
34 int bufferSize; /* memory allocated for text */
36 int viewPosition; /* position of text being shown */
38 int cursorPosition; /* position of the insertion cursor */
40 short usableWidth;
41 short offsetWidth; /* offset of text from border */
43 WMRange selection;
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 unsigned int ownsSelection:1;
71 unsigned int waitingSelection:1; /* requested selection, but
72 * didnt get yet */
74 /**/
75 unsigned int notIllegalMovement:1;
76 } flags;
77 } TextField;
80 #define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\
81 if ((T)->delegate && (T)->delegate->C)\
82 (*(T)->delegate->C)((T)->delegate,notif);\
83 WMPostNotification(notif);\
84 WMReleaseNotification(notif);}
87 #define MIN_TEXT_BUFFER 2
88 #define TEXT_BUFFER_INCR 8
91 #define WM_EMACSKEYMASK ControlMask
93 #define WM_EMACSKEY_LEFT XK_b
94 #define WM_EMACSKEY_RIGHT XK_f
95 #define WM_EMACSKEY_HOME XK_a
96 #define WM_EMACSKEY_END XK_e
97 #define WM_EMACSKEY_BS XK_h
98 #define WM_EMACSKEY_DEL XK_d
102 #define DEFAULT_WIDTH 60
103 #define DEFAULT_HEIGHT 20
104 #define DEFAULT_BORDERED True
105 #define DEFAULT_ALIGNMENT WALeft
109 static void destroyTextField(TextField *tPtr);
110 static void paintTextField(TextField *tPtr);
112 static void handleEvents(XEvent *event, void *data);
113 static void handleTextFieldActionEvents(XEvent *event, void *data);
114 static void didResizeTextField();
116 struct W_ViewDelegate _TextFieldViewDelegate = {
117 NULL,
118 NULL,
119 didResizeTextField,
120 NULL,
121 NULL
126 static void lostHandler(WMView *view, Atom selection, void *cdata);
128 static WMData *requestHandler(WMView *view, Atom selection, Atom target,
129 void *cdata, Atom *type);
132 static WMSelectionProcs selectionHandler = {
133 requestHandler,
134 lostHandler,
135 NULL
139 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
140 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
142 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
143 &((tPtr)->text[(start)]), (end) - (start) + 1))
146 static void
147 normalizeRange(TextField *tPtr, WMRange *range)
149 if (range->position < 0 && range->count < 0)
150 range->count = 0;
152 if (range->count == 0) {
153 /*range->position = 0; why is this?*/
154 return;
157 /* (1,-2) ~> (0,1) ; (1,-1) ~> (0,1) ; (2,-1) ~> (1,1) */
158 if (range->count < 0) { /* && range->position >= 0 */
159 if (range->position + range->count < 0) {
160 range->count = range->position;
161 range->position = 0;
162 } else {
163 range->count = -range->count;
164 range->position -= range->count;
166 /* (-2,1) ~> (0,0) ; (-1,1) ~> (0,0) ; (-1,2) ~> (0,1) */
167 } else if (range->position < 0) { /* && range->count > 0 */
168 if (range->position + range->count < 0) {
169 range->position = range->count = 0;
170 } else {
171 range->count += range->position;
172 range->position = 0;
176 if (range->position + range->count > tPtr->textLen)
177 range->count = tPtr->textLen - range->position;
180 static void
181 memmv(char *dest, char *src, int size)
183 int i;
185 if (dest > src) {
186 for (i=size-1; i>=0; i--) {
187 dest[i] = src[i];
189 } else if (dest < src) {
190 for (i=0; i<size; i++) {
191 dest[i] = src[i];
197 static int
198 incrToFit(TextField *tPtr)
200 int vp = tPtr->viewPosition;
202 while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
203 tPtr->viewPosition++;
205 return vp!=tPtr->viewPosition;
209 static int
210 incrToFit2(TextField *tPtr)
212 int vp = tPtr->viewPosition;
213 while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
214 >= tPtr->usableWidth)
215 tPtr->viewPosition++;
218 return vp!=tPtr->viewPosition;
222 static void
223 decrToFit(TextField *tPtr)
225 while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->usableWidth
226 && tPtr->viewPosition>0)
227 tPtr->viewPosition--;
230 #undef TEXT_WIDTH
231 #undef TEXT_WIDTH2
235 static WMData*
236 requestHandler(WMView *view, Atom selection, Atom target, void *cdata,
237 Atom *type)
239 TextField *tPtr = view->self;
240 int count;
241 Display *dpy = tPtr->view->screen->display;
242 Atom _TARGETS;
243 Atom TEXT = XInternAtom(dpy, "TEXT", False);
244 Atom COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", False);
245 WMData *data;
247 count = tPtr->selection.count < 0
248 ? tPtr->selection.position + tPtr->selection.count
249 : tPtr->selection.position;
251 if (target == XA_STRING || target == TEXT || target == COMPOUND_TEXT) {
253 data = WMCreateDataWithBytes(&(tPtr->text[count]),
254 abs(tPtr->selection.count));
255 WMSetDataFormat(data, 8);
256 *type = target;
258 return data;
261 _TARGETS = XInternAtom(dpy, "TARGETS", False);
262 if (target == _TARGETS) {
263 Atom *ptr;
265 ptr = wmalloc(4 * sizeof(Atom));
266 ptr[0] = _TARGETS;
267 ptr[1] = XA_STRING;
268 ptr[2] = TEXT;
269 ptr[3] = COMPOUND_TEXT;
271 data = WMCreateDataWithBytes(ptr, 4*4);
272 WMSetDataFormat(data, 32);
274 *type = target;
275 return data;
278 return NULL;
283 static void
284 lostHandler(WMView *view, Atom selection, void *cdata)
286 TextField *tPtr = (WMTextField*)view->self;
288 //WMDeleteSelectionHandler(view, XA_PRIMARY, CurrentTime);
289 tPtr->flags.ownsSelection = 0;
290 tPtr->selection.count = 0;
291 paintTextField(tPtr);
295 static void
296 _notification(void *observerData, WMNotification *notification)
298 WMTextField *to = (WMTextField*)observerData;
299 WMTextField *tw = (WMTextField*)WMGetNotificationClientData(notification);
300 if (to != tw) lostHandler(to->view, XA_PRIMARY, NULL);
304 WMTextField*
305 WMCreateTextField(WMWidget *parent)
307 TextField *tPtr;
309 tPtr = wmalloc(sizeof(TextField));
310 memset(tPtr, 0, sizeof(TextField));
312 tPtr->widgetClass = WC_TextField;
314 tPtr->view = W_CreateView(W_VIEW(parent));
315 if (!tPtr->view) {
316 wfree(tPtr);
317 return NULL;
319 tPtr->view->self = tPtr;
321 tPtr->view->delegate = &_TextFieldViewDelegate;
323 tPtr->view->attribFlags |= CWCursor;
324 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
326 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
328 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
329 tPtr->text[0] = 0;
330 tPtr->textLen = 0;
331 tPtr->bufferSize = MIN_TEXT_BUFFER;
333 tPtr->flags.enabled = 1;
335 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
336 |FocusChangeMask, handleEvents, tPtr);
338 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
340 tPtr->flags.bordered = DEFAULT_BORDERED;
341 tPtr->flags.beveled = True;
342 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
343 tPtr->offsetWidth =
344 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
346 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
348 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
349 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
350 handleTextFieldActionEvents, tPtr);
352 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
355 tPtr->flags.cursorOn = 1;
357 return tPtr;
361 void
362 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
364 CHECK_CLASS(tPtr, WC_TextField);
366 tPtr->delegate = delegate;
370 void
371 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
373 int len;
375 CHECK_CLASS(tPtr, WC_TextField);
377 if (!text)
378 return;
380 len = strlen(text);
382 /* check if buffer will hold the text */
383 if (len + tPtr->textLen >= tPtr->bufferSize) {
384 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
385 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
388 if (position < 0 || position >= tPtr->textLen) {
389 /* append the text at the end */
390 strcat(tPtr->text, text);
392 incrToFit(tPtr);
394 tPtr->textLen += len;
395 tPtr->cursorPosition += len;
396 } else {
397 /* insert text at position */
398 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
399 tPtr->textLen-position+1);
401 memcpy(&(tPtr->text[position]), text, len);
403 tPtr->textLen += len;
404 if (position >= tPtr->cursorPosition) {
405 tPtr->cursorPosition += len;
406 incrToFit2(tPtr);
407 } else {
408 incrToFit(tPtr);
412 paintTextField(tPtr);
415 void
416 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
418 CHECK_CLASS(tPtr, WC_TextField);
420 normalizeRange(tPtr, &range);
422 if (!range.count)
423 return;
425 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
426 tPtr->textLen - (range.position+range.count) + 1);
428 tPtr->textLen -= range.count;
430 /* try to keep cursorPosition at the same place */
431 tPtr->viewPosition -= range.count;
432 if (tPtr->viewPosition < 0)
433 tPtr->viewPosition = 0;
434 tPtr->cursorPosition = range.position;
436 decrToFit(tPtr);
438 paintTextField(tPtr);
443 char*
444 WMGetTextFieldText(WMTextField *tPtr)
446 CHECK_CLASS(tPtr, WC_TextField);
448 return wstrdup(tPtr->text);
452 void
453 WMSetTextFieldText(WMTextField *tPtr, char *text)
455 CHECK_CLASS(tPtr, WC_TextField);
457 if ((text && strcmp(tPtr->text, text) == 0) ||
458 (!text && tPtr->textLen == 0))
459 return;
461 if (text==NULL) {
462 tPtr->text[0] = 0;
463 tPtr->textLen = 0;
464 } else {
465 tPtr->textLen = strlen(text);
467 if (tPtr->textLen >= tPtr->bufferSize) {
468 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
469 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
471 strcpy(tPtr->text, text);
474 tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
475 tPtr->viewPosition = 0;
476 tPtr->selection.count = 0;
478 if (tPtr->view->flags.realized)
479 paintTextField(tPtr);
483 void
484 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
486 CHECK_CLASS(tPtr, WC_TextField);
488 tPtr->flags.alignment = alignment;
490 if (alignment!=WALeft) {
491 wwarning("only left alignment is supported in textfields");
492 return;
495 if (tPtr->view->flags.realized) {
496 paintTextField(tPtr);
501 void
502 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
504 CHECK_CLASS(tPtr, WC_TextField);
506 tPtr->flags.bordered = bordered;
508 if (tPtr->view->flags.realized) {
509 paintTextField(tPtr);
514 void
515 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
517 CHECK_CLASS(tPtr, WC_TextField);
519 tPtr->flags.beveled = flag;
521 if (tPtr->view->flags.realized) {
522 paintTextField(tPtr);
528 void
529 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
531 CHECK_CLASS(tPtr, WC_TextField);
533 tPtr->flags.secure = flag;
535 if (tPtr->view->flags.realized) {
536 paintTextField(tPtr);
541 Bool
542 WMGetTextFieldEditable(WMTextField *tPtr)
544 CHECK_CLASS(tPtr, WC_TextField);
546 return tPtr->flags.enabled;
550 void
551 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
553 CHECK_CLASS(tPtr, WC_TextField);
555 tPtr->flags.enabled = flag;
557 if (tPtr->view->flags.realized) {
558 paintTextField(tPtr);
563 void
564 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
566 CHECK_CLASS(tPtr, WC_TextField);
568 if (tPtr->flags.enabled) {
569 normalizeRange(tPtr, &range);
571 tPtr->selection = range;
573 tPtr->cursorPosition = range.position + range.count;
575 if (tPtr->view->flags.realized) {
576 paintTextField(tPtr);
582 void
583 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
585 CHECK_CLASS(tPtr, WC_TextField);
587 if (tPtr->flags.enabled) {
588 if (position > tPtr->textLen)
589 position = tPtr->textLen;
591 tPtr->cursorPosition = position;
592 if (tPtr->view->flags.realized) {
593 paintTextField(tPtr);
599 void
600 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
602 CHECK_CLASS(tPtr, WC_TextField);
603 if (next == NULL) {
604 if (tPtr->view->nextFocusChain)
605 tPtr->view->nextFocusChain->prevFocusChain = NULL;
606 tPtr->view->nextFocusChain = NULL;
607 return;
610 CHECK_CLASS(next, WC_TextField);
612 if (tPtr->view->nextFocusChain)
613 tPtr->view->nextFocusChain->prevFocusChain = NULL;
614 if (next->view->prevFocusChain)
615 next->view->prevFocusChain->nextFocusChain = NULL;
617 tPtr->view->nextFocusChain = next->view;
618 next->view->prevFocusChain = tPtr->view;
622 void
623 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
625 CHECK_CLASS(tPtr, WC_TextField);
626 if (prev == NULL) {
627 if (tPtr->view->prevFocusChain)
628 tPtr->view->prevFocusChain->nextFocusChain = NULL;
629 tPtr->view->prevFocusChain = NULL;
630 return;
633 CHECK_CLASS(prev, WC_TextField);
635 if (tPtr->view->prevFocusChain)
636 tPtr->view->prevFocusChain->nextFocusChain = NULL;
637 if (prev->view->nextFocusChain)
638 prev->view->nextFocusChain->prevFocusChain = NULL;
640 tPtr->view->prevFocusChain = prev->view;
641 prev->view->nextFocusChain = tPtr->view;
645 void
646 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
648 CHECK_CLASS(tPtr, WC_TextField);
650 if (tPtr->font)
651 WMReleaseFont(tPtr->font);
652 tPtr->font = WMRetainFont(font);
654 tPtr->offsetWidth =
655 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
657 if (tPtr->view->flags.realized) {
658 paintTextField(tPtr);
664 WMFont*
665 WMGetTextFieldFont(WMTextField *tPtr)
667 return tPtr->font;
671 static void
672 didResizeTextField(W_ViewDelegate *self, WMView *view)
674 WMTextField *tPtr = (WMTextField*)view->self;
676 tPtr->offsetWidth =
677 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
679 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth /*+ 2*/;
683 static char*
684 makeHiddenString(int length)
686 char *data = wmalloc(length+1);
688 memset(data, '*', length);
689 data[length] = '\0';
691 return data;
695 static void
696 paintCursor(TextField *tPtr)
698 int cx;
699 WMScreen *screen = tPtr->view->screen;
700 int textWidth;
701 char *text;
703 if (tPtr->flags.secure)
704 text = makeHiddenString(strlen(tPtr->text));
705 else
706 text = tPtr->text;
708 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
709 tPtr->cursorPosition-tPtr->viewPosition);
711 switch (tPtr->flags.alignment) {
712 case WARight:
713 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
714 if (textWidth < tPtr->usableWidth)
715 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
716 else
717 cx += tPtr->offsetWidth + 1;
718 break;
719 case WALeft:
720 cx += tPtr->offsetWidth + 1;
721 break;
722 case WAJustified:
723 /* not supported */
724 case WACenter:
725 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
726 if (textWidth < tPtr->usableWidth)
727 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
728 else
729 cx += tPtr->offsetWidth;
730 break;
733 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
734 cx, tPtr->offsetWidth, 1,
735 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
736 printf("%d %d\n",cx,tPtr->cursorPosition);
738 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
739 cx, tPtr->offsetWidth, cx,
740 tPtr->view->size.height - tPtr->offsetWidth - 1);
742 if (tPtr->flags.secure)
743 wfree(text);
748 static void
749 drawRelief(WMView *view, Bool beveled)
751 WMScreen *scr = view->screen;
752 Display *dpy = scr->display;
753 GC wgc;
754 GC lgc;
755 GC dgc;
756 int width = view->size.width;
757 int height = view->size.height;
759 dgc = WMColorGC(scr->darkGray);
761 if (!beveled) {
762 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
764 return;
766 wgc = WMColorGC(scr->white);
767 lgc = WMColorGC(scr->gray);
769 /* top left */
770 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
771 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
773 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
774 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
776 /* bottom right */
777 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
778 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
780 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
781 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
785 static void
786 paintTextField(TextField *tPtr)
788 W_Screen *screen = tPtr->view->screen;
789 W_View *view = tPtr->view;
790 W_View viewbuffer;
791 int tx, ty, tw, th;
792 int rx;
793 int bd;
794 int totalWidth;
795 char *text;
796 Pixmap drawbuffer;
799 if (!view->flags.realized || !view->flags.mapped)
800 return;
802 if (!tPtr->flags.bordered) {
803 bd = 0;
804 } else {
805 bd = 2;
808 if (tPtr->flags.secure) {
809 text = makeHiddenString(strlen(tPtr->text));
810 } else {
811 text = tPtr->text;
814 totalWidth = tPtr->view->size.width - 2*bd;
816 drawbuffer = XCreatePixmap(screen->display, view->window,
817 view->size.width, view->size.height, screen->depth);
818 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
819 0,0, view->size.width,view->size.height);
820 /* this is quite dirty */
821 viewbuffer.screen = view->screen;
822 viewbuffer.size = view->size;
823 viewbuffer.window = drawbuffer;
826 if (tPtr->textLen > 0) {
827 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
828 tPtr->textLen - tPtr->viewPosition);
830 th = WMFontHeight(tPtr->font);
832 ty = tPtr->offsetWidth;
833 switch (tPtr->flags.alignment) {
834 case WALeft:
835 tx = tPtr->offsetWidth + 1;
836 if (tw < tPtr->usableWidth)
837 XFillRectangle(screen->display, drawbuffer,
838 WMColorGC(screen->white),
839 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
840 break;
842 case WACenter:
843 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
844 if (tw < tPtr->usableWidth)
845 XClearArea(screen->display, view->window, bd, bd,
846 totalWidth, view->size.height-2*bd, False);
847 break;
849 default:
850 case WARight:
851 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
852 if (tw < tPtr->usableWidth)
853 XClearArea(screen->display, view->window, bd, bd,
854 totalWidth-tw, view->size.height-2*bd, False);
855 break;
858 if (!tPtr->flags.enabled)
859 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
861 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
862 tPtr->font, tx, ty,
863 &(text[tPtr->viewPosition]),
864 tPtr->textLen - tPtr->viewPosition);
866 if (tPtr->selection.count) {
867 int count,count2;
869 count = tPtr->selection.count < 0
870 ? tPtr->selection.position + tPtr->selection.count
871 : tPtr->selection.position;
872 count2 = abs(tPtr->selection.count);
873 if (count < tPtr->viewPosition) {
874 count2 = abs(count2 - abs(tPtr->viewPosition - count));
875 count = tPtr->viewPosition;
879 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
880 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
882 XSetBackground(screen->display, screen->textFieldGC,
883 screen->gray->color.pixel);
885 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
886 tPtr->font, rx, ty, &(text[count]),
887 count2);
889 XSetBackground(screen->display, screen->textFieldGC,
890 screen->white->color.pixel);
893 if (!tPtr->flags.enabled)
894 WMSetColorInGC(screen->black, screen->textFieldGC);
895 } else {
896 XFillRectangle(screen->display, drawbuffer,
897 WMColorGC(screen->white),
898 bd,bd, totalWidth,view->size.height-2*bd);
901 /* draw relief */
902 if (tPtr->flags.bordered) {
903 drawRelief(&viewbuffer, tPtr->flags.beveled);
906 if (tPtr->flags.secure)
907 wfree(text);
908 XCopyArea(screen->display, drawbuffer, view->window,
909 screen->copyGC, 0,0, view->size.width,
910 view->size.height,0,0);
911 XFreePixmap(screen->display, drawbuffer);
913 /* draw cursor */
914 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
915 paintCursor(tPtr);
920 #if 0
921 static void
922 blinkCursor(void *data)
924 TextField *tPtr = (TextField*)data;
926 if (tPtr->flags.cursorOn) {
927 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
928 data);
929 } else {
930 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
931 data);
933 paintCursor(tPtr);
934 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
936 #endif
939 static void
940 handleEvents(XEvent *event, void *data)
942 TextField *tPtr = (TextField*)data;
944 CHECK_CLASS(data, WC_TextField);
947 switch (event->type) {
948 case FocusIn:
949 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
950 return;
951 tPtr->flags.focused = 1;
952 #if 0
953 if (!tPtr->timerID) {
954 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
955 blinkCursor, tPtr);
957 #endif
958 paintTextField(tPtr);
960 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
962 tPtr->flags.notIllegalMovement = 0;
963 break;
965 case FocusOut:
966 tPtr->flags.focused = 0;
967 #if 0
968 if (tPtr->timerID)
969 WMDeleteTimerHandler(tPtr->timerID);
970 tPtr->timerID = NULL;
971 #endif
973 paintTextField(tPtr);
974 if (!tPtr->flags.notIllegalMovement) {
975 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
976 (void*)WMIllegalTextMovement);
978 break;
980 case Expose:
981 if (event->xexpose.count!=0)
982 break;
983 paintTextField(tPtr);
984 break;
986 case DestroyNotify:
987 destroyTextField(tPtr);
988 break;
993 static void
994 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
996 char buffer[64];
997 KeySym ksym;
998 char *textEvent = NULL;
999 void *data = NULL;
1000 int count, refresh = 0;
1001 int control_pressed = 0;
1002 int cancelSelection = 1;
1003 Bool shifted, controled, modified;
1004 Bool relay = True;
1006 /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
1007 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
1008 control_pressed = 1;
1010 shifted = event->xkey.state & ShiftMask;
1011 controled = event->xkey.state & ControlMask;
1012 if ((event->xkey.state & (ShiftMask|ControlMask)) != 0) {
1013 modified = True;
1014 } else {
1015 modified = False;
1018 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
1019 buffer[count] = '\0';
1021 switch (ksym) {
1022 case XK_Tab:
1023 #ifdef XK_ISO_Left_Tab
1024 case XK_ISO_Left_Tab:
1025 #endif
1026 if (!controled && !modified) {
1027 if (shifted) {
1028 if (tPtr->view->prevFocusChain) {
1029 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1030 tPtr->view->prevFocusChain);
1031 tPtr->flags.notIllegalMovement = 1;
1033 data = (void*)WMBacktabTextMovement;
1034 } else {
1035 if (tPtr->view->nextFocusChain) {
1036 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1037 tPtr->view->nextFocusChain);
1038 tPtr->flags.notIllegalMovement = 1;
1040 data = (void*)WMTabTextMovement;
1042 textEvent = WMTextDidEndEditingNotification;
1044 relay = False;
1046 break;
1048 case XK_Escape:
1049 if (!shifted && !controled && !modified) {
1050 data = (void*)WMEscapeTextMovement;
1051 textEvent = WMTextDidEndEditingNotification;
1053 relay = False;
1055 break;
1057 case XK_Return:
1058 if (!shifted && !controled && !modified) {
1059 data = (void*)WMReturnTextMovement;
1060 textEvent = WMTextDidEndEditingNotification;
1062 relay = False;
1064 break;
1066 case WM_EMACSKEY_LEFT:
1067 if (!control_pressed)
1068 goto normal_key;
1069 else
1070 modified = False;
1071 #ifdef XK_KP_Left
1072 case XK_KP_Left:
1073 #endif
1074 case XK_Left:
1075 if (!modified) {
1076 if (tPtr->cursorPosition > 0) {
1077 paintCursor(tPtr);
1078 if (event->xkey.state & ControlMask) {
1079 int i = tPtr->cursorPosition - 1;
1081 while (i > 0 && tPtr->text[i] != ' ') i--;
1082 while (i > 0 && tPtr->text[i] == ' ') i--;
1084 tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
1085 } else
1086 tPtr->cursorPosition--;
1088 if (tPtr->cursorPosition < tPtr->viewPosition) {
1089 tPtr->viewPosition = tPtr->cursorPosition;
1090 refresh = 1;
1091 } else
1092 paintCursor(tPtr);
1094 if (event->xkey.state & ShiftMask)
1095 cancelSelection = 0;
1097 relay = False;
1099 break;
1101 case WM_EMACSKEY_RIGHT:
1102 if (!control_pressed)
1103 goto normal_key;
1104 else
1105 modified = False;
1107 #ifdef XK_KP_Right
1108 case XK_KP_Right:
1109 #endif
1110 case XK_Right:
1111 if (!modified) {
1112 if (tPtr->cursorPosition < tPtr->textLen) {
1113 paintCursor(tPtr);
1114 if (event->xkey.state & ControlMask) {
1115 int i = tPtr->cursorPosition;
1117 while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
1118 while (tPtr->text[i] == ' ') i++;
1120 tPtr->cursorPosition = i;
1121 } else {
1122 tPtr->cursorPosition++;
1124 while (WMWidthOfString(tPtr->font,
1125 &(tPtr->text[tPtr->viewPosition]),
1126 tPtr->cursorPosition-tPtr->viewPosition)
1127 > tPtr->usableWidth) {
1128 tPtr->viewPosition++;
1129 refresh = 1;
1131 if (!refresh)
1132 paintCursor(tPtr);
1134 if (event->xkey.state & ShiftMask)
1135 cancelSelection = 0;
1137 relay = False;
1139 break;
1141 case WM_EMACSKEY_HOME:
1142 if (!control_pressed)
1143 goto normal_key;
1144 else {
1145 modified = False;
1146 controled = False;
1148 #ifdef XK_KP_Home
1149 case XK_KP_Home:
1150 #endif
1151 case XK_Home:
1152 if (!modified && !controled) {
1153 if (tPtr->cursorPosition > 0) {
1154 paintCursor(tPtr);
1155 tPtr->cursorPosition = 0;
1156 if (tPtr->viewPosition > 0) {
1157 tPtr->viewPosition = 0;
1158 refresh = 1;
1159 } else
1160 paintCursor(tPtr);
1162 if (event->xkey.state & ShiftMask)
1163 cancelSelection = 0;
1165 relay = False;
1167 break;
1169 case WM_EMACSKEY_END:
1170 if (!control_pressed)
1171 goto normal_key;
1172 else {
1173 modified = False;
1174 controled = False;
1176 #ifdef XK_KP_End
1177 case XK_KP_End:
1178 #endif
1179 case XK_End:
1180 if (!modified && !controled) {
1181 if (tPtr->cursorPosition < tPtr->textLen) {
1182 paintCursor(tPtr);
1183 tPtr->cursorPosition = tPtr->textLen;
1184 tPtr->viewPosition = 0;
1185 while (WMWidthOfString(tPtr->font,
1186 &(tPtr->text[tPtr->viewPosition]),
1187 tPtr->textLen-tPtr->viewPosition)
1188 > tPtr->usableWidth) {
1189 tPtr->viewPosition++;
1190 refresh = 1;
1192 if (!refresh)
1193 paintCursor(tPtr);
1195 if (event->xkey.state & ShiftMask)
1196 cancelSelection = 0;
1198 relay = False;
1200 break;
1202 case WM_EMACSKEY_BS:
1203 if (!control_pressed)
1204 goto normal_key;
1205 else {
1206 modified = False;
1207 controled = False;
1208 shifted = False;
1210 case XK_BackSpace:
1211 if (!modified && !shifted && !controled) {
1212 if (tPtr->selection.count) {
1213 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1214 data = (void*)WMDeleteTextEvent;
1215 textEvent = WMTextDidChangeNotification;
1216 } else if (tPtr->cursorPosition > 0) {
1217 WMRange range;
1218 range.position = tPtr->cursorPosition - 1;
1219 range.count = 1;
1220 WMDeleteTextFieldRange(tPtr, range);
1221 data = (void*)WMDeleteTextEvent;
1222 textEvent = WMTextDidChangeNotification;
1225 relay = False;
1227 break;
1229 case WM_EMACSKEY_DEL:
1230 if (!control_pressed)
1231 goto normal_key;
1232 else {
1233 modified = False;
1234 controled = False;
1235 shifted = False;
1237 #ifdef XK_KP_Delete
1238 case XK_KP_Delete:
1239 #endif
1240 case XK_Delete:
1241 if (!modified && !controled && !shifted) {
1242 if (tPtr->selection.count) {
1243 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1244 data = (void*)WMDeleteTextEvent;
1245 textEvent = WMTextDidChangeNotification;
1246 } else if (tPtr->cursorPosition < tPtr->textLen) {
1247 WMRange range;
1248 range.position = tPtr->cursorPosition;
1249 range.count = 1;
1250 WMDeleteTextFieldRange(tPtr, range);
1251 data = (void*)WMDeleteTextEvent;
1252 textEvent = WMTextDidChangeNotification;
1255 relay = False;
1257 break;
1259 normal_key:
1260 default:
1261 if (!controled) {
1262 if (count > 0 && !iscntrl(buffer[0])) {
1263 if (tPtr->selection.count)
1264 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1265 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1266 data = (void*)WMInsertTextEvent;
1267 textEvent = WMTextDidChangeNotification;
1269 relay = False;
1272 break;
1275 if (relay) {
1276 WMRelayToNextResponder(W_VIEW(tPtr), event);
1277 return;
1280 /* Do not allow text selection in secure text fields */
1281 if (cancelSelection || tPtr->flags.secure) {
1282 lostHandler(tPtr->view, XA_PRIMARY, NULL);
1284 if (tPtr->selection.count) {
1285 tPtr->selection.count = 0;
1286 refresh = 1;
1288 tPtr->selection.position = tPtr->cursorPosition;
1289 } else {
1290 if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
1292 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1294 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1296 refresh = 1;
1300 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1302 if (textEvent) {
1303 WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);
1305 if (tPtr->delegate) {
1306 if (textEvent==WMTextDidBeginEditingNotification &&
1307 tPtr->delegate->didBeginEditing)
1308 (*tPtr->delegate->didBeginEditing)(tPtr->delegate, notif);
1310 else if (textEvent==WMTextDidEndEditingNotification &&
1311 tPtr->delegate->didEndEditing)
1312 (*tPtr->delegate->didEndEditing)(tPtr->delegate, notif);
1314 else if (textEvent==WMTextDidChangeNotification &&
1315 tPtr->delegate->didChange)
1316 (*tPtr->delegate->didChange)(tPtr->delegate, notif);
1319 WMPostNotification(notif);
1320 WMReleaseNotification(notif);
1323 if (refresh)
1324 paintTextField(tPtr);
1326 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1330 static int
1331 pointToCursorPosition(TextField *tPtr, int x)
1333 int a, b, mid;
1334 int tw;
1336 if (tPtr->flags.bordered)
1337 x -= 2;
1339 a = tPtr->viewPosition;
1340 b = tPtr->viewPosition + tPtr->textLen;
1341 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1342 tPtr->textLen - tPtr->viewPosition) < x)
1343 return tPtr->textLen;
1345 while (a < b && b-a>1) {
1346 mid = (a+b)/2;
1347 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1348 mid - tPtr->viewPosition);
1349 if (tw > x)
1350 b = mid;
1351 else if (tw < x)
1352 a = mid;
1353 else
1354 return mid;
1356 return (a+b)/2;
1361 static void
1362 pasteText(WMView *view, Atom selection, Atom target, Time timestamp,
1363 void *cdata, WMData *data)
1365 TextField *tPtr = (TextField*)view->self;
1366 char *str;
1368 tPtr->flags.waitingSelection = 0;
1370 if (data != NULL) {
1371 str = (char*)WMDataBytes(data);
1373 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1374 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1375 (void*)WMInsertTextEvent);
1376 } else {
1377 int n;
1379 str = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1381 if (str != NULL) {
1382 str[n] = 0;
1383 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1384 XFree(str);
1385 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1386 (void*)WMInsertTextEvent);
1392 static void
1393 handleTextFieldActionEvents(XEvent *event, void *data)
1395 TextField *tPtr = (TextField*)data;
1396 static int move = 0;
1397 static Time lastButtonReleasedEvent = 0;
1398 Display *dpy = event->xany.display;
1400 CHECK_CLASS(data, WC_TextField);
1402 switch (event->type) {
1403 case KeyPress:
1404 if (tPtr->flags.waitingSelection) {
1405 return;
1407 if (tPtr->flags.enabled && tPtr->flags.focused) {
1408 handleTextFieldKeyPress(tPtr, event);
1409 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1410 W_VIEW(tPtr)->screen->invisibleCursor);
1411 tPtr->flags.pointerGrabbed = 1;
1413 break;
1415 case MotionNotify:
1417 if (tPtr->flags.pointerGrabbed) {
1418 tPtr->flags.pointerGrabbed = 0;
1419 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1420 W_VIEW(tPtr)->screen->textCursor);
1422 if (tPtr->flags.waitingSelection) {
1423 return;
1426 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1428 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1429 tPtr->usableWidth) {
1430 if (WMWidthOfString(tPtr->font,
1431 &(tPtr->text[tPtr->viewPosition]),
1432 tPtr->cursorPosition-tPtr->viewPosition)
1433 > tPtr->usableWidth) {
1434 tPtr->viewPosition++;
1436 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1437 paintCursor(tPtr);
1438 tPtr->viewPosition--;
1441 tPtr->cursorPosition =
1442 pointToCursorPosition(tPtr, event->xmotion.x);
1444 /* Do not allow text selection in secure textfields */
1445 if (tPtr->flags.secure) {
1446 tPtr->selection.position = tPtr->cursorPosition;
1449 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1451 paintCursor(tPtr);
1452 paintTextField(tPtr);
1455 break;
1457 case ButtonPress:
1458 if (tPtr->flags.pointerGrabbed) {
1459 tPtr->flags.pointerGrabbed = 0;
1460 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1461 W_VIEW(tPtr)->screen->textCursor);
1462 break;
1465 if (tPtr->flags.waitingSelection) {
1466 break;
1469 move = 1;
1470 switch (tPtr->flags.alignment) {
1471 int textWidth;
1472 case WARight:
1473 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1474 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1475 WMSetFocusToWidget(tPtr);
1477 if (tPtr->flags.focused) {
1478 tPtr->selection.position = tPtr->cursorPosition;
1479 tPtr->selection.count = 0;
1481 if (textWidth < tPtr->usableWidth) {
1482 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1483 event->xbutton.x - tPtr->usableWidth
1484 + textWidth);
1485 } else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1486 event->xbutton.x);
1488 paintTextField(tPtr);
1489 break;
1491 case WALeft:
1492 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1493 WMSetFocusToWidget(tPtr);
1495 if (tPtr->flags.focused && event->xbutton.button == Button1) {
1496 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1497 event->xbutton.x);
1498 tPtr->selection.position = tPtr->cursorPosition;
1499 tPtr->selection.count = 0;
1500 paintTextField(tPtr);
1502 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1503 char *text;
1504 int n;
1506 if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
1507 event->xbutton.time,
1508 pasteText, NULL)) {
1509 text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1511 if (text) {
1512 text[n] = 0;
1513 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1514 XFree(text);
1515 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1516 (void*)WMInsertTextEvent);
1518 } else {
1519 tPtr->flags.waitingSelection = 1;
1522 break;
1523 default:
1524 break;
1526 break;
1528 case ButtonRelease:
1529 if (tPtr->flags.pointerGrabbed) {
1530 tPtr->flags.pointerGrabbed = 0;
1531 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1532 W_VIEW(tPtr)->screen->textCursor);
1534 if (tPtr->flags.waitingSelection) {
1535 break;
1538 if (tPtr->selection.count != 0) {
1539 int start, count;
1540 XRotateBuffers(dpy, 1);
1542 count = abs(tPtr->selection.count);
1543 if (tPtr->selection.count < 0)
1544 start = tPtr->selection.position - count;
1545 else
1546 start = tPtr->selection.position;
1548 XStoreBuffer(dpy, &tPtr->text[start], count, 0);
1551 move = 0;
1553 if (!tPtr->flags.secure &&
1554 event->xbutton.time - lastButtonReleasedEvent
1555 <= WINGsConfiguration.doubleClickDelay) {
1556 tPtr->selection.position = 0;
1557 tPtr->selection.count = tPtr->textLen;
1558 paintTextField(tPtr);
1560 if (!tPtr->flags.ownsSelection) {
1561 WMCreateSelectionHandler(tPtr->view,
1562 XA_PRIMARY,
1563 event->xbutton.time,
1564 &selectionHandler, NULL);
1565 tPtr->flags.ownsSelection = 1;
1566 //puts("lost ownership");
1567 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1569 } else if (!tPtr->flags.secure && tPtr->selection.count!=0 &&
1570 !tPtr->flags.ownsSelection) {
1571 WMCreateSelectionHandler(tPtr->view,
1572 XA_PRIMARY,
1573 event->xbutton.time,
1574 &selectionHandler, NULL);
1575 tPtr->flags.ownsSelection = 1;
1576 //puts("lost ownership");
1577 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1580 lastButtonReleasedEvent = event->xbutton.time;
1582 break;
1587 static void
1588 destroyTextField(TextField *tPtr)
1590 #if 0
1591 if (tPtr->timerID)
1592 WMDeleteTimerHandler(tPtr->timerID);
1593 #endif
1595 WMReleaseFont(tPtr->font);
1596 WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
1597 WMRemoveNotificationObserver(tPtr);
1599 if (tPtr->text)
1600 wfree(tPtr->text);
1602 wfree(tPtr);