Fixed secure textfields not to allow text selection, to avoid
[wmaker-crm.git] / WINGs / wtextfield.c
blob97f6c5ae80028db312aada9a790c3673a4981533
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;
44 WMFont *font;
46 WMTextFieldDelegate *delegate;
48 #if 0
49 WMHandlerID timerID; /* for cursor blinking */
50 #endif
51 struct {
52 WMAlignment alignment:2;
54 unsigned int bordered:1;
56 unsigned int beveled:1;
58 unsigned int enabled:1;
60 unsigned int focused:1;
62 unsigned int cursorOn:1;
64 unsigned int secure:1; /* password entry style */
66 unsigned int pointerGrabbed:1;
68 unsigned int ownsSelection:1;
70 unsigned int waitingSelection:1; /* requested selection, but
71 * didnt get yet */
73 /**/
74 unsigned int notIllegalMovement:1;
75 } flags;
76 } TextField;
79 #define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\
80 if ((T)->delegate && (T)->delegate->C)\
81 (*(T)->delegate->C)((T)->delegate,notif);\
82 WMPostNotification(notif);\
83 WMReleaseNotification(notif);}
86 #define MIN_TEXT_BUFFER 2
87 #define TEXT_BUFFER_INCR 8
90 #define WM_EMACSKEYMASK ControlMask
92 #define WM_EMACSKEY_LEFT XK_b
93 #define WM_EMACSKEY_RIGHT XK_f
94 #define WM_EMACSKEY_HOME XK_a
95 #define WM_EMACSKEY_END XK_e
96 #define WM_EMACSKEY_BS XK_h
97 #define WM_EMACSKEY_DEL XK_d
101 #define DEFAULT_WIDTH 60
102 #define DEFAULT_HEIGHT 20
103 #define DEFAULT_BORDERED True
104 #define DEFAULT_ALIGNMENT WALeft
108 static void destroyTextField(TextField *tPtr);
109 static void paintTextField(TextField *tPtr);
111 static void handleEvents(XEvent *event, void *data);
112 static void handleTextFieldActionEvents(XEvent *event, void *data);
113 static void didResizeTextField();
115 struct W_ViewDelegate _TextFieldViewDelegate = {
116 NULL,
117 NULL,
118 didResizeTextField,
119 NULL,
120 NULL
125 static void lostHandler(WMView *view, Atom selection, void *cdata);
127 static WMData *requestHandler(WMView *view, Atom selection, Atom target,
128 void *cdata, Atom *type);
131 static WMSelectionProcs selectionHandler = {
132 requestHandler,
133 lostHandler,
134 NULL
138 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
139 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
141 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
142 &((tPtr)->text[(start)]), (end) - (start) + 1))
145 static void
146 normalizeRange(TextField *tPtr, WMRange *range)
148 if (range->position < 0 && range->count < 0)
149 range->count = 0;
151 if (range->count == 0) {
152 /*range->position = 0; why is this?*/
153 return;
156 /* (1,-2) ~> (0,1) ; (1,-1) ~> (0,1) ; (2,-1) ~> (1,1) */
157 if (range->count < 0) { /* && range->position >= 0 */
158 if (range->position + range->count < 0) {
159 range->count = range->position;
160 range->position = 0;
161 } else {
162 range->count = -range->count;
163 range->position -= range->count;
165 /* (-2,1) ~> (0,0) ; (-1,1) ~> (0,0) ; (-1,2) ~> (0,1) */
166 } else if (range->position < 0) { /* && range->count > 0 */
167 if (range->position + range->count < 0) {
168 range->position = range->count = 0;
169 } else {
170 range->count += range->position;
171 range->position = 0;
175 if (range->position + range->count > tPtr->textLen)
176 range->count = tPtr->textLen - range->position;
179 static void
180 memmv(char *dest, char *src, int size)
182 int i;
184 if (dest > src) {
185 for (i=size-1; i>=0; i--) {
186 dest[i] = src[i];
188 } else if (dest < src) {
189 for (i=0; i<size; i++) {
190 dest[i] = src[i];
196 static int
197 incrToFit(TextField *tPtr)
199 int vp = tPtr->viewPosition;
201 while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
202 tPtr->viewPosition++;
204 return vp!=tPtr->viewPosition;
208 static int
209 incrToFit2(TextField *tPtr)
211 int vp = tPtr->viewPosition;
212 while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
213 >= tPtr->usableWidth)
214 tPtr->viewPosition++;
217 return vp!=tPtr->viewPosition;
221 static void
222 decrToFit(TextField *tPtr)
224 while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->usableWidth
225 && tPtr->viewPosition>0)
226 tPtr->viewPosition--;
229 #undef TEXT_WIDTH
230 #undef TEXT_WIDTH2
234 static WMData*
235 requestHandler(WMView *view, Atom selection, Atom target, void *cdata,
236 Atom *type)
238 TextField *tPtr = view->self;
239 int count;
240 Display *dpy = tPtr->view->screen->display;
241 Atom _TARGETS;
242 Atom TEXT = XInternAtom(dpy, "TEXT", False);
243 Atom COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", False);
244 WMData *data;
246 count = tPtr->selection.count < 0
247 ? tPtr->selection.position + tPtr->selection.count
248 : tPtr->selection.position;
250 if (target == XA_STRING || target == TEXT || target == COMPOUND_TEXT) {
252 data = WMCreateDataWithBytes(&(tPtr->text[count]),
253 abs(tPtr->selection.count));
254 WMSetDataFormat(data, 8);
255 *type = target;
257 return data;
260 _TARGETS = XInternAtom(dpy, "TARGETS", False);
261 if (target == _TARGETS) {
262 Atom *ptr;
264 ptr = wmalloc(4 * sizeof(Atom));
265 ptr[0] = _TARGETS;
266 ptr[1] = XA_STRING;
267 ptr[2] = TEXT;
268 ptr[3] = COMPOUND_TEXT;
270 data = WMCreateDataWithBytes(ptr, 4*4);
271 WMSetDataFormat(data, 32);
273 *type = target;
274 return data;
277 return NULL;
282 static void
283 lostHandler(WMView *view, Atom selection, void *cdata)
285 TextField *tPtr = (WMTextField*)view->self;
287 tPtr->flags.ownsSelection = 0;
288 tPtr->selection.count = 0;
289 paintTextField(tPtr);
293 static void
294 _notification(void *observerData, WMNotification *notification)
296 WMTextField *to = (WMTextField*)observerData;
297 WMTextField *tw = (WMTextField*)WMGetNotificationClientData(notification);
298 if (to != tw) lostHandler(to->view, XA_PRIMARY, NULL);
302 WMTextField*
303 WMCreateTextField(WMWidget *parent)
305 TextField *tPtr;
307 tPtr = wmalloc(sizeof(TextField));
308 memset(tPtr, 0, sizeof(TextField));
310 tPtr->widgetClass = WC_TextField;
312 tPtr->view = W_CreateView(W_VIEW(parent));
313 if (!tPtr->view) {
314 wfree(tPtr);
315 return NULL;
317 tPtr->view->self = tPtr;
319 tPtr->view->delegate = &_TextFieldViewDelegate;
321 tPtr->view->attribFlags |= CWCursor;
322 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
324 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
326 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
327 tPtr->text[0] = 0;
328 tPtr->textLen = 0;
329 tPtr->bufferSize = MIN_TEXT_BUFFER;
331 tPtr->flags.enabled = 1;
333 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
334 |FocusChangeMask, handleEvents, tPtr);
336 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
338 tPtr->flags.bordered = DEFAULT_BORDERED;
339 tPtr->flags.beveled = True;
340 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
341 tPtr->offsetWidth =
342 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
344 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
346 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
347 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
348 handleTextFieldActionEvents, tPtr);
350 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
353 tPtr->flags.cursorOn = 1;
355 return tPtr;
359 void
360 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
362 CHECK_CLASS(tPtr, WC_TextField);
364 tPtr->delegate = delegate;
368 void
369 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
371 int len;
373 CHECK_CLASS(tPtr, WC_TextField);
375 if (!text)
376 return;
378 len = strlen(text);
380 /* check if buffer will hold the text */
381 if (len + tPtr->textLen >= tPtr->bufferSize) {
382 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
383 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
386 if (position < 0 || position >= tPtr->textLen) {
387 /* append the text at the end */
388 strcat(tPtr->text, text);
390 incrToFit(tPtr);
392 tPtr->textLen += len;
393 tPtr->cursorPosition += len;
394 } else {
395 /* insert text at position */
396 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
397 tPtr->textLen-position+1);
399 memcpy(&(tPtr->text[position]), text, len);
401 tPtr->textLen += len;
402 if (position >= tPtr->cursorPosition) {
403 tPtr->cursorPosition += len;
404 incrToFit2(tPtr);
405 } else {
406 incrToFit(tPtr);
410 paintTextField(tPtr);
413 void
414 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
416 CHECK_CLASS(tPtr, WC_TextField);
418 normalizeRange(tPtr, &range);
420 if (!range.count)
421 return;
423 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
424 tPtr->textLen - (range.position+range.count) + 1);
426 tPtr->textLen -= range.count;
428 /* try to keep cursorPosition at the same place */
429 tPtr->viewPosition -= range.count;
430 if (tPtr->viewPosition < 0)
431 tPtr->viewPosition = 0;
432 tPtr->cursorPosition = range.position;
434 decrToFit(tPtr);
436 paintTextField(tPtr);
441 char*
442 WMGetTextFieldText(WMTextField *tPtr)
444 CHECK_CLASS(tPtr, WC_TextField);
446 return wstrdup(tPtr->text);
450 void
451 WMSetTextFieldText(WMTextField *tPtr, char *text)
453 CHECK_CLASS(tPtr, WC_TextField);
455 if ((text && strcmp(tPtr->text, text) == 0) ||
456 (!text && tPtr->textLen == 0))
457 return;
459 if (text==NULL) {
460 tPtr->text[0] = 0;
461 tPtr->textLen = 0;
462 } else {
463 tPtr->textLen = strlen(text);
465 if (tPtr->textLen >= tPtr->bufferSize) {
466 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
467 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
469 strcpy(tPtr->text, text);
472 tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
473 tPtr->viewPosition = 0;
474 tPtr->selection.count = 0;
476 if (tPtr->view->flags.realized)
477 paintTextField(tPtr);
481 void
482 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
484 CHECK_CLASS(tPtr, WC_TextField);
486 tPtr->flags.alignment = alignment;
488 if (alignment!=WALeft) {
489 wwarning("only left alignment is supported in textfields");
490 return;
493 if (tPtr->view->flags.realized) {
494 paintTextField(tPtr);
499 void
500 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
502 CHECK_CLASS(tPtr, WC_TextField);
504 tPtr->flags.bordered = bordered;
506 if (tPtr->view->flags.realized) {
507 paintTextField(tPtr);
512 void
513 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
515 CHECK_CLASS(tPtr, WC_TextField);
517 tPtr->flags.beveled = flag;
519 if (tPtr->view->flags.realized) {
520 paintTextField(tPtr);
526 void
527 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
529 CHECK_CLASS(tPtr, WC_TextField);
531 tPtr->flags.secure = flag;
533 if (tPtr->view->flags.realized) {
534 paintTextField(tPtr);
539 Bool
540 WMGetTextFieldEditable(WMTextField *tPtr)
542 CHECK_CLASS(tPtr, WC_TextField);
544 return tPtr->flags.enabled;
548 void
549 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
551 CHECK_CLASS(tPtr, WC_TextField);
553 tPtr->flags.enabled = flag;
555 if (tPtr->view->flags.realized) {
556 paintTextField(tPtr);
561 void
562 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
564 CHECK_CLASS(tPtr, WC_TextField);
566 if (tPtr->flags.enabled) {
567 normalizeRange(tPtr, &range);
569 tPtr->selection = range;
571 tPtr->cursorPosition = range.position + range.count;
573 if (tPtr->view->flags.realized) {
574 paintTextField(tPtr);
580 void
581 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
583 CHECK_CLASS(tPtr, WC_TextField);
585 if (tPtr->flags.enabled) {
586 if (position > tPtr->textLen)
587 position = tPtr->textLen;
589 tPtr->cursorPosition = position;
590 if (tPtr->view->flags.realized) {
591 paintTextField(tPtr);
597 void
598 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
600 CHECK_CLASS(tPtr, WC_TextField);
601 if (next == NULL) {
602 if (tPtr->view->nextFocusChain)
603 tPtr->view->nextFocusChain->prevFocusChain = NULL;
604 tPtr->view->nextFocusChain = NULL;
605 return;
608 CHECK_CLASS(next, WC_TextField);
610 if (tPtr->view->nextFocusChain)
611 tPtr->view->nextFocusChain->prevFocusChain = NULL;
612 if (next->view->prevFocusChain)
613 next->view->prevFocusChain->nextFocusChain = NULL;
615 tPtr->view->nextFocusChain = next->view;
616 next->view->prevFocusChain = tPtr->view;
620 void
621 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
623 CHECK_CLASS(tPtr, WC_TextField);
624 if (prev == NULL) {
625 if (tPtr->view->prevFocusChain)
626 tPtr->view->prevFocusChain->nextFocusChain = NULL;
627 tPtr->view->prevFocusChain = NULL;
628 return;
631 CHECK_CLASS(prev, WC_TextField);
633 if (tPtr->view->prevFocusChain)
634 tPtr->view->prevFocusChain->nextFocusChain = NULL;
635 if (prev->view->nextFocusChain)
636 prev->view->nextFocusChain->prevFocusChain = NULL;
638 tPtr->view->prevFocusChain = prev->view;
639 prev->view->nextFocusChain = tPtr->view;
643 void
644 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
646 CHECK_CLASS(tPtr, WC_TextField);
648 if (tPtr->font)
649 WMReleaseFont(tPtr->font);
650 tPtr->font = WMRetainFont(font);
652 tPtr->offsetWidth =
653 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
655 if (tPtr->view->flags.realized) {
656 paintTextField(tPtr);
662 WMFont*
663 WMGetTextFieldFont(WMTextField *tPtr)
665 return tPtr->font;
669 static void
670 didResizeTextField(W_ViewDelegate *self, WMView *view)
672 WMTextField *tPtr = (WMTextField*)view->self;
674 tPtr->offsetWidth =
675 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
677 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
681 static char*
682 makeHiddenString(int length)
684 char *data = wmalloc(length+1);
686 memset(data, '*', length);
687 data[length] = '\0';
689 return data;
693 static void
694 paintCursor(TextField *tPtr)
696 int cx;
697 WMScreen *screen = tPtr->view->screen;
698 int textWidth;
699 char *text;
701 if (tPtr->flags.secure)
702 text = makeHiddenString(strlen(tPtr->text));
703 else
704 text = tPtr->text;
706 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
707 tPtr->cursorPosition-tPtr->viewPosition);
709 switch (tPtr->flags.alignment) {
710 case WARight:
711 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
712 if (textWidth < tPtr->usableWidth)
713 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
714 else
715 cx += tPtr->offsetWidth + 1;
716 break;
717 case WALeft:
718 cx += tPtr->offsetWidth + 1;
719 break;
720 case WAJustified:
721 /* not supported */
722 case WACenter:
723 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
724 if (textWidth < tPtr->usableWidth)
725 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
726 else
727 cx += tPtr->offsetWidth;
728 break;
731 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
732 cx, tPtr->offsetWidth, 1,
733 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
734 printf("%d %d\n",cx,tPtr->cursorPosition);
736 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
737 cx, tPtr->offsetWidth, cx,
738 tPtr->view->size.height - tPtr->offsetWidth - 1);
740 if (tPtr->flags.secure)
741 wfree(text);
746 static void
747 drawRelief(WMView *view, Bool beveled)
749 WMScreen *scr = view->screen;
750 Display *dpy = scr->display;
751 GC wgc;
752 GC lgc;
753 GC dgc;
754 int width = view->size.width;
755 int height = view->size.height;
757 dgc = WMColorGC(scr->darkGray);
759 if (!beveled) {
760 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
762 return;
764 wgc = WMColorGC(scr->white);
765 lgc = WMColorGC(scr->gray);
767 /* top left */
768 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
769 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
771 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
772 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
774 /* bottom right */
775 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
776 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
778 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
779 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
783 static void
784 paintTextField(TextField *tPtr)
786 W_Screen *screen = tPtr->view->screen;
787 W_View *view = tPtr->view;
788 W_View viewbuffer;
789 int tx, ty, tw, th;
790 int rx;
791 int bd;
792 int totalWidth;
793 char *text;
794 Pixmap drawbuffer;
797 if (!view->flags.realized || !view->flags.mapped)
798 return;
800 if (!tPtr->flags.bordered) {
801 bd = 0;
802 } else {
803 bd = 2;
806 if (tPtr->flags.secure) {
807 text = makeHiddenString(strlen(tPtr->text));
808 } else {
809 text = tPtr->text;
812 totalWidth = tPtr->view->size.width - 2*bd;
814 drawbuffer = XCreatePixmap(screen->display, view->window,
815 view->size.width, view->size.height, screen->depth);
816 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
817 0,0, view->size.width,view->size.height);
818 /* this is quite dirty */
819 viewbuffer.screen = view->screen;
820 viewbuffer.size = view->size;
821 viewbuffer.window = drawbuffer;
824 if (tPtr->textLen > 0) {
825 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
826 tPtr->textLen - tPtr->viewPosition);
828 th = WMFontHeight(tPtr->font);
830 ty = tPtr->offsetWidth;
831 switch (tPtr->flags.alignment) {
832 case WALeft:
833 tx = tPtr->offsetWidth + 1;
834 if (tw < tPtr->usableWidth)
835 XFillRectangle(screen->display, drawbuffer,
836 WMColorGC(screen->white),
837 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
838 break;
840 case WACenter:
841 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
842 if (tw < tPtr->usableWidth)
843 XClearArea(screen->display, view->window, bd, bd,
844 totalWidth, view->size.height-2*bd, False);
845 break;
847 default:
848 case WARight:
849 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
850 if (tw < tPtr->usableWidth)
851 XClearArea(screen->display, view->window, bd, bd,
852 totalWidth-tw, view->size.height-2*bd, False);
853 break;
856 if (!tPtr->flags.enabled)
857 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
859 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
860 tPtr->font, tx, ty,
861 &(text[tPtr->viewPosition]),
862 tPtr->textLen - tPtr->viewPosition);
864 if (tPtr->selection.count) {
865 int count,count2;
867 count = tPtr->selection.count < 0
868 ? tPtr->selection.position + tPtr->selection.count
869 : tPtr->selection.position;
870 count2 = abs(tPtr->selection.count);
871 if (count < tPtr->viewPosition) {
872 count2 = abs(count2 - abs(tPtr->viewPosition - count));
873 count = tPtr->viewPosition;
877 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
878 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
880 XSetBackground(screen->display, screen->textFieldGC,
881 screen->gray->color.pixel);
883 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
884 tPtr->font, rx, ty, &(text[count]),
885 count2);
887 XSetBackground(screen->display, screen->textFieldGC,
888 screen->white->color.pixel);
891 if (!tPtr->flags.enabled)
892 WMSetColorInGC(screen->black, screen->textFieldGC);
893 } else {
894 XFillRectangle(screen->display, drawbuffer,
895 WMColorGC(screen->white),
896 bd,bd, totalWidth,view->size.height-2*bd);
899 /* draw relief */
900 if (tPtr->flags.bordered) {
901 drawRelief(&viewbuffer, tPtr->flags.beveled);
904 if (tPtr->flags.secure)
905 wfree(text);
906 XCopyArea(screen->display, drawbuffer, view->window,
907 screen->copyGC, 0,0, view->size.width,
908 view->size.height,0,0);
909 XFreePixmap(screen->display, drawbuffer);
911 /* draw cursor */
912 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
913 paintCursor(tPtr);
918 #if 0
919 static void
920 blinkCursor(void *data)
922 TextField *tPtr = (TextField*)data;
924 if (tPtr->flags.cursorOn) {
925 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
926 data);
927 } else {
928 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
929 data);
931 paintCursor(tPtr);
932 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
934 #endif
937 static void
938 handleEvents(XEvent *event, void *data)
940 TextField *tPtr = (TextField*)data;
942 CHECK_CLASS(data, WC_TextField);
945 switch (event->type) {
946 case FocusIn:
947 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
948 return;
949 tPtr->flags.focused = 1;
950 #if 0
951 if (!tPtr->timerID) {
952 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
953 blinkCursor, tPtr);
955 #endif
956 paintTextField(tPtr);
958 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
960 tPtr->flags.notIllegalMovement = 0;
961 break;
963 case FocusOut:
964 tPtr->flags.focused = 0;
965 #if 0
966 if (tPtr->timerID)
967 WMDeleteTimerHandler(tPtr->timerID);
968 tPtr->timerID = NULL;
969 #endif
971 paintTextField(tPtr);
972 if (!tPtr->flags.notIllegalMovement) {
973 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
974 (void*)WMIllegalTextMovement);
976 break;
978 case Expose:
979 if (event->xexpose.count!=0)
980 break;
981 paintTextField(tPtr);
982 break;
984 case DestroyNotify:
985 destroyTextField(tPtr);
986 break;
991 static void
992 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
994 char buffer[64];
995 KeySym ksym;
996 char *textEvent = NULL;
997 void *data = NULL;
998 int count, refresh = 0;
999 int control_pressed = 0;
1000 int cancelSelection = 1;
1001 Bool shifted, controled, modified;
1002 Bool relay = True;
1003 WMScreen *scr = tPtr->view->screen;
1005 event->xkey.state &= ~scr->ignoredModifierMask;
1007 /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
1008 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
1009 control_pressed = 1;
1011 shifted = event->xkey.state & ShiftMask;
1012 controled = event->xkey.state & ControlMask;
1013 if ((event->xkey.state & ~(ShiftMask|ControlMask)) != 0) {
1014 modified = True;
1015 } else {
1016 modified = False;
1019 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
1020 buffer[count] = '\0';
1022 switch (ksym) {
1023 case XK_Tab:
1024 #ifdef XK_ISO_Left_Tab
1025 case XK_ISO_Left_Tab:
1026 #endif
1027 if (!controled && !modified) {
1028 if (shifted) {
1029 if (tPtr->view->prevFocusChain) {
1030 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1031 tPtr->view->prevFocusChain);
1032 tPtr->flags.notIllegalMovement = 1;
1034 data = (void*)WMBacktabTextMovement;
1035 } else {
1036 if (tPtr->view->nextFocusChain) {
1037 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1038 tPtr->view->nextFocusChain);
1039 tPtr->flags.notIllegalMovement = 1;
1041 data = (void*)WMTabTextMovement;
1043 textEvent = WMTextDidEndEditingNotification;
1045 relay = False;
1047 break;
1049 case XK_Escape:
1050 if (!shifted && !controled && !modified) {
1051 data = (void*)WMEscapeTextMovement;
1052 textEvent = WMTextDidEndEditingNotification;
1054 relay = False;
1056 break;
1058 case XK_Return:
1059 if (!shifted && !controled && !modified) {
1060 data = (void*)WMReturnTextMovement;
1061 textEvent = WMTextDidEndEditingNotification;
1063 relay = False;
1065 break;
1067 case WM_EMACSKEY_LEFT:
1068 if (!control_pressed) {
1069 goto normal_key;
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;
1105 #ifdef XK_KP_Right
1106 case XK_KP_Right:
1107 #endif
1108 case XK_Right:
1109 if (!modified) {
1110 if (tPtr->cursorPosition < tPtr->textLen) {
1111 paintCursor(tPtr);
1112 if (event->xkey.state & ControlMask) {
1113 int i = tPtr->cursorPosition;
1115 while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
1116 while (tPtr->text[i] == ' ') i++;
1118 tPtr->cursorPosition = i;
1119 } else {
1120 tPtr->cursorPosition++;
1122 while (WMWidthOfString(tPtr->font,
1123 &(tPtr->text[tPtr->viewPosition]),
1124 tPtr->cursorPosition-tPtr->viewPosition)
1125 > tPtr->usableWidth) {
1126 tPtr->viewPosition++;
1127 refresh = 1;
1129 if (!refresh)
1130 paintCursor(tPtr);
1132 if (event->xkey.state & ShiftMask)
1133 cancelSelection = 0;
1135 relay = False;
1137 break;
1139 case WM_EMACSKEY_HOME:
1140 if (!control_pressed) {
1141 goto normal_key;
1143 #ifdef XK_KP_Home
1144 case XK_KP_Home:
1145 #endif
1146 case XK_Home:
1147 if (!modified && !controled) {
1148 if (tPtr->cursorPosition > 0) {
1149 paintCursor(tPtr);
1150 tPtr->cursorPosition = 0;
1151 if (tPtr->viewPosition > 0) {
1152 tPtr->viewPosition = 0;
1153 refresh = 1;
1154 } else
1155 paintCursor(tPtr);
1157 if (event->xkey.state & ShiftMask)
1158 cancelSelection = 0;
1160 relay = False;
1162 break;
1164 case WM_EMACSKEY_END:
1165 if (!control_pressed) {
1166 goto normal_key;
1168 #ifdef XK_KP_End
1169 case XK_KP_End:
1170 #endif
1171 case XK_End:
1172 if (!modified && !controled) {
1173 if (tPtr->cursorPosition < tPtr->textLen) {
1174 paintCursor(tPtr);
1175 tPtr->cursorPosition = tPtr->textLen;
1176 tPtr->viewPosition = 0;
1177 while (WMWidthOfString(tPtr->font,
1178 &(tPtr->text[tPtr->viewPosition]),
1179 tPtr->textLen-tPtr->viewPosition)
1180 > tPtr->usableWidth) {
1181 tPtr->viewPosition++;
1182 refresh = 1;
1184 if (!refresh)
1185 paintCursor(tPtr);
1187 if (event->xkey.state & ShiftMask)
1188 cancelSelection = 0;
1190 relay = False;
1192 break;
1194 case WM_EMACSKEY_BS:
1195 if (!control_pressed) {
1196 goto normal_key;
1198 case XK_BackSpace:
1199 if (!modified && !shifted && !controled) {
1200 if (tPtr->selection.count) {
1201 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1202 data = (void*)WMDeleteTextEvent;
1203 textEvent = WMTextDidChangeNotification;
1204 } else if (tPtr->cursorPosition > 0) {
1205 WMRange range;
1206 range.position = tPtr->cursorPosition - 1;
1207 range.count = 1;
1208 WMDeleteTextFieldRange(tPtr, range);
1209 data = (void*)WMDeleteTextEvent;
1210 textEvent = WMTextDidChangeNotification;
1213 relay = False;
1215 break;
1217 case WM_EMACSKEY_DEL:
1218 if (!control_pressed) {
1219 goto normal_key;
1221 #ifdef XK_KP_Delete
1222 case XK_KP_Delete:
1223 #endif
1224 case XK_Delete:
1225 if (!modified && !controled && !shifted) {
1226 if (tPtr->selection.count) {
1227 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1228 data = (void*)WMDeleteTextEvent;
1229 textEvent = WMTextDidChangeNotification;
1230 } else if (tPtr->cursorPosition < tPtr->textLen) {
1231 WMRange range;
1232 range.position = tPtr->cursorPosition;
1233 range.count = 1;
1234 WMDeleteTextFieldRange(tPtr, range);
1235 data = (void*)WMDeleteTextEvent;
1236 textEvent = WMTextDidChangeNotification;
1239 relay = False;
1241 break;
1243 normal_key:
1244 default:
1245 if (!controled && !modified) {
1246 if (count > 0 && isprint(buffer[0])) {
1247 if (tPtr->selection.count)
1248 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1249 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1250 data = (void*)WMInsertTextEvent;
1251 textEvent = WMTextDidChangeNotification;
1253 relay = False;
1256 break;
1259 if (relay) {
1260 WMRelayToNextResponder(W_VIEW(tPtr), event);
1261 return;
1264 /* Do not allow text selection in secure text fields */
1265 if (cancelSelection || tPtr->flags.secure) {
1266 lostHandler(tPtr->view, XA_PRIMARY, NULL);
1268 if (tPtr->selection.count) {
1269 tPtr->selection.count = 0;
1270 refresh = 1;
1272 tPtr->selection.position = tPtr->cursorPosition;
1273 } else {
1274 if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
1276 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1278 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1280 refresh = 1;
1284 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1286 if (textEvent) {
1287 WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);
1289 if (tPtr->delegate) {
1290 if (textEvent==WMTextDidBeginEditingNotification &&
1291 tPtr->delegate->didBeginEditing)
1292 (*tPtr->delegate->didBeginEditing)(tPtr->delegate, notif);
1294 else if (textEvent==WMTextDidEndEditingNotification &&
1295 tPtr->delegate->didEndEditing)
1296 (*tPtr->delegate->didEndEditing)(tPtr->delegate, notif);
1298 else if (textEvent==WMTextDidChangeNotification &&
1299 tPtr->delegate->didChange)
1300 (*tPtr->delegate->didChange)(tPtr->delegate, notif);
1303 WMPostNotification(notif);
1304 WMReleaseNotification(notif);
1307 if (refresh)
1308 paintTextField(tPtr);
1310 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1314 static int
1315 pointToCursorPosition(TextField *tPtr, int x)
1317 int a, b, mid;
1318 int tw;
1320 if (tPtr->flags.bordered)
1321 x -= 2;
1323 a = tPtr->viewPosition;
1324 b = tPtr->viewPosition + tPtr->textLen;
1325 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1326 tPtr->textLen - tPtr->viewPosition) < x)
1327 return tPtr->textLen;
1329 while (a < b && b-a>1) {
1330 mid = (a+b)/2;
1331 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1332 mid - tPtr->viewPosition);
1333 if (tw > x)
1334 b = mid;
1335 else if (tw < x)
1336 a = mid;
1337 else
1338 return mid;
1340 return (a+b)/2;
1345 static void
1346 pasteText(WMView *view, Atom selection, Atom target, Time timestamp,
1347 void *cdata, WMData *data)
1349 TextField *tPtr = (TextField*)view->self;
1350 char *str;
1352 tPtr->flags.waitingSelection = 0;
1354 if (data != NULL) {
1355 str = (char*)WMDataBytes(data);
1357 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1358 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1359 (void*)WMInsertTextEvent);
1360 } else {
1361 int n;
1363 str = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1365 if (str != NULL) {
1366 str[n] = 0;
1367 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1368 XFree(str);
1369 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1370 (void*)WMInsertTextEvent);
1376 static void
1377 handleTextFieldActionEvents(XEvent *event, void *data)
1379 TextField *tPtr = (TextField*)data;
1380 static int move = 0;
1381 static Time lastButtonReleasedEvent = 0;
1382 Display *dpy = event->xany.display;
1384 CHECK_CLASS(data, WC_TextField);
1386 switch (event->type) {
1387 case KeyPress:
1388 if (tPtr->flags.waitingSelection) {
1389 return;
1391 if (tPtr->flags.enabled && tPtr->flags.focused) {
1392 handleTextFieldKeyPress(tPtr, event);
1393 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1394 W_VIEW(tPtr)->screen->invisibleCursor);
1395 tPtr->flags.pointerGrabbed = 1;
1397 break;
1399 case MotionNotify:
1401 if (tPtr->flags.pointerGrabbed) {
1402 tPtr->flags.pointerGrabbed = 0;
1403 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1404 W_VIEW(tPtr)->screen->textCursor);
1406 if (tPtr->flags.waitingSelection) {
1407 return;
1410 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1412 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1413 tPtr->usableWidth) {
1414 if (WMWidthOfString(tPtr->font,
1415 &(tPtr->text[tPtr->viewPosition]),
1416 tPtr->cursorPosition-tPtr->viewPosition)
1417 > tPtr->usableWidth) {
1418 tPtr->viewPosition++;
1420 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1421 paintCursor(tPtr);
1422 tPtr->viewPosition--;
1425 tPtr->cursorPosition =
1426 pointToCursorPosition(tPtr, event->xmotion.x);
1428 /* Do not allow text selection in secure textfields */
1429 if (tPtr->flags.secure) {
1430 tPtr->selection.position = tPtr->cursorPosition;
1433 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1435 if (tPtr->selection.count != 0) {
1436 if (!tPtr->flags.ownsSelection) {
1437 WMCreateSelectionHandler(tPtr->view,
1438 XA_PRIMARY,
1439 event->xbutton.time,
1440 &selectionHandler, NULL);
1441 tPtr->flags.ownsSelection = 1;
1445 paintCursor(tPtr);
1446 paintTextField(tPtr);
1449 break;
1451 case ButtonPress:
1452 if (tPtr->flags.pointerGrabbed) {
1453 tPtr->flags.pointerGrabbed = 0;
1454 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1455 W_VIEW(tPtr)->screen->textCursor);
1456 break;
1459 if (tPtr->flags.waitingSelection) {
1460 break;
1463 move = 1;
1464 switch (tPtr->flags.alignment) {
1465 int textWidth;
1466 case WARight:
1467 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1468 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1469 WMSetFocusToWidget(tPtr);
1470 } else if (tPtr->flags.focused) {
1471 tPtr->selection.position = tPtr->cursorPosition;
1472 tPtr->selection.count = 0;
1474 if (textWidth < tPtr->usableWidth) {
1475 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1476 event->xbutton.x - tPtr->usableWidth
1477 + textWidth);
1478 } else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1479 event->xbutton.x);
1481 paintTextField(tPtr);
1482 break;
1484 case WALeft:
1485 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1486 WMSetFocusToWidget(tPtr);
1487 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1488 event->xbutton.x);
1489 paintTextField(tPtr);
1490 } else if (tPtr->flags.focused
1491 && event->xbutton.button == Button1) {
1492 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1493 event->xbutton.x);
1494 tPtr->selection.position = tPtr->cursorPosition;
1495 tPtr->selection.count = 0;
1496 paintTextField(tPtr);
1498 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1499 char *text;
1500 int n;
1502 if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
1503 event->xbutton.time,
1504 pasteText, NULL)) {
1505 text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1507 if (text) {
1508 text[n] = 0;
1509 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1510 XFree(text);
1511 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1512 (void*)WMInsertTextEvent);
1514 } else {
1515 tPtr->flags.waitingSelection = 1;
1518 break;
1519 default:
1520 break;
1522 break;
1524 case ButtonRelease:
1525 if (tPtr->flags.pointerGrabbed) {
1526 tPtr->flags.pointerGrabbed = 0;
1527 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1528 W_VIEW(tPtr)->screen->textCursor);
1530 if (tPtr->flags.waitingSelection) {
1531 break;
1534 if (tPtr->selection.count != 0) {
1535 int start, count;
1536 XRotateBuffers(dpy, 1);
1538 count = abs(tPtr->selection.count);
1539 if (tPtr->selection.count < 0)
1540 start = tPtr->selection.position - count;
1541 else
1542 start = tPtr->selection.position;
1544 XStoreBuffer(dpy, &tPtr->text[start], count, 0);
1547 move = 0;
1549 if (!tPtr->flags.secure &&
1550 event->xbutton.time - lastButtonReleasedEvent
1551 <= WINGsConfiguration.doubleClickDelay) {
1552 tPtr->selection.position = 0;
1553 tPtr->selection.count = tPtr->textLen;
1554 paintTextField(tPtr);
1556 if (!tPtr->flags.ownsSelection) {
1557 WMCreateSelectionHandler(tPtr->view,
1558 XA_PRIMARY,
1559 event->xbutton.time,
1560 &selectionHandler, NULL);
1561 tPtr->flags.ownsSelection = 1;
1564 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1566 lastButtonReleasedEvent = event->xbutton.time;
1568 break;
1573 static void
1574 destroyTextField(TextField *tPtr)
1576 #if 0
1577 if (tPtr->timerID)
1578 WMDeleteTimerHandler(tPtr->timerID);
1579 #endif
1581 WMReleaseFont(tPtr->font);
1582 WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
1583 WMRemoveNotificationObserver(tPtr);
1585 if (tPtr->text)
1586 wfree(tPtr->text);
1588 wfree(tPtr);