- Finished moving to the new proplist handling code in WINGs.
[wmaker-crm.git] / WINGs / wtextfield.c
bloba997e3dd575740831fa0a682e80906c99e200601
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 tPtr->flags.ownsSelection = 0;
289 tPtr->selection.count = 0;
290 paintTextField(tPtr);
294 static void
295 _notification(void *observerData, WMNotification *notification)
297 WMTextField *to = (WMTextField*)observerData;
298 WMTextField *tw = (WMTextField*)WMGetNotificationClientData(notification);
299 if (to != tw) lostHandler(to->view, XA_PRIMARY, NULL);
303 WMTextField*
304 WMCreateTextField(WMWidget *parent)
306 TextField *tPtr;
308 tPtr = wmalloc(sizeof(TextField));
309 memset(tPtr, 0, sizeof(TextField));
311 tPtr->widgetClass = WC_TextField;
313 tPtr->view = W_CreateView(W_VIEW(parent));
314 if (!tPtr->view) {
315 wfree(tPtr);
316 return NULL;
318 tPtr->view->self = tPtr;
320 tPtr->view->delegate = &_TextFieldViewDelegate;
322 tPtr->view->attribFlags |= CWCursor;
323 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
325 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
327 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
328 tPtr->text[0] = 0;
329 tPtr->textLen = 0;
330 tPtr->bufferSize = MIN_TEXT_BUFFER;
332 tPtr->flags.enabled = 1;
334 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
335 |FocusChangeMask, handleEvents, tPtr);
337 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
339 tPtr->flags.bordered = DEFAULT_BORDERED;
340 tPtr->flags.beveled = True;
341 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
342 tPtr->offsetWidth =
343 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
345 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
347 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
348 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
349 handleTextFieldActionEvents, tPtr);
351 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
354 tPtr->flags.cursorOn = 1;
356 return tPtr;
360 void
361 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
363 CHECK_CLASS(tPtr, WC_TextField);
365 tPtr->delegate = delegate;
369 void
370 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
372 int len;
374 CHECK_CLASS(tPtr, WC_TextField);
376 if (!text)
377 return;
379 len = strlen(text);
381 /* check if buffer will hold the text */
382 if (len + tPtr->textLen >= tPtr->bufferSize) {
383 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
384 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
387 if (position < 0 || position >= tPtr->textLen) {
388 /* append the text at the end */
389 strcat(tPtr->text, text);
391 incrToFit(tPtr);
393 tPtr->textLen += len;
394 tPtr->cursorPosition += len;
395 } else {
396 /* insert text at position */
397 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
398 tPtr->textLen-position+1);
400 memcpy(&(tPtr->text[position]), text, len);
402 tPtr->textLen += len;
403 if (position >= tPtr->cursorPosition) {
404 tPtr->cursorPosition += len;
405 incrToFit2(tPtr);
406 } else {
407 incrToFit(tPtr);
411 paintTextField(tPtr);
414 void
415 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
417 CHECK_CLASS(tPtr, WC_TextField);
419 normalizeRange(tPtr, &range);
421 if (!range.count)
422 return;
424 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
425 tPtr->textLen - (range.position+range.count) + 1);
427 tPtr->textLen -= range.count;
429 /* try to keep cursorPosition at the same place */
430 tPtr->viewPosition -= range.count;
431 if (tPtr->viewPosition < 0)
432 tPtr->viewPosition = 0;
433 tPtr->cursorPosition = range.position;
435 decrToFit(tPtr);
437 paintTextField(tPtr);
442 char*
443 WMGetTextFieldText(WMTextField *tPtr)
445 CHECK_CLASS(tPtr, WC_TextField);
447 return wstrdup(tPtr->text);
451 void
452 WMSetTextFieldText(WMTextField *tPtr, char *text)
454 CHECK_CLASS(tPtr, WC_TextField);
456 if ((text && strcmp(tPtr->text, text) == 0) ||
457 (!text && tPtr->textLen == 0))
458 return;
460 if (text==NULL) {
461 tPtr->text[0] = 0;
462 tPtr->textLen = 0;
463 } else {
464 tPtr->textLen = strlen(text);
466 if (tPtr->textLen >= tPtr->bufferSize) {
467 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
468 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
470 strcpy(tPtr->text, text);
473 tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
474 tPtr->viewPosition = 0;
475 tPtr->selection.count = 0;
477 if (tPtr->view->flags.realized)
478 paintTextField(tPtr);
482 void
483 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
485 CHECK_CLASS(tPtr, WC_TextField);
487 tPtr->flags.alignment = alignment;
489 if (alignment!=WALeft) {
490 wwarning("only left alignment is supported in textfields");
491 return;
494 if (tPtr->view->flags.realized) {
495 paintTextField(tPtr);
500 void
501 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
503 CHECK_CLASS(tPtr, WC_TextField);
505 tPtr->flags.bordered = bordered;
507 if (tPtr->view->flags.realized) {
508 paintTextField(tPtr);
513 void
514 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
516 CHECK_CLASS(tPtr, WC_TextField);
518 tPtr->flags.beveled = flag;
520 if (tPtr->view->flags.realized) {
521 paintTextField(tPtr);
527 void
528 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
530 CHECK_CLASS(tPtr, WC_TextField);
532 tPtr->flags.secure = flag;
534 if (tPtr->view->flags.realized) {
535 paintTextField(tPtr);
540 Bool
541 WMGetTextFieldEditable(WMTextField *tPtr)
543 CHECK_CLASS(tPtr, WC_TextField);
545 return tPtr->flags.enabled;
549 void
550 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
552 CHECK_CLASS(tPtr, WC_TextField);
554 tPtr->flags.enabled = flag;
556 if (tPtr->view->flags.realized) {
557 paintTextField(tPtr);
562 void
563 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
565 CHECK_CLASS(tPtr, WC_TextField);
567 if (tPtr->flags.enabled) {
568 normalizeRange(tPtr, &range);
570 tPtr->selection = range;
572 tPtr->cursorPosition = range.position + range.count;
574 if (tPtr->view->flags.realized) {
575 paintTextField(tPtr);
581 void
582 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
584 CHECK_CLASS(tPtr, WC_TextField);
586 if (tPtr->flags.enabled) {
587 if (position > tPtr->textLen)
588 position = tPtr->textLen;
590 tPtr->cursorPosition = position;
591 if (tPtr->view->flags.realized) {
592 paintTextField(tPtr);
598 void
599 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
601 CHECK_CLASS(tPtr, WC_TextField);
602 if (next == NULL) {
603 if (tPtr->view->nextFocusChain)
604 tPtr->view->nextFocusChain->prevFocusChain = NULL;
605 tPtr->view->nextFocusChain = NULL;
606 return;
609 CHECK_CLASS(next, WC_TextField);
611 if (tPtr->view->nextFocusChain)
612 tPtr->view->nextFocusChain->prevFocusChain = NULL;
613 if (next->view->prevFocusChain)
614 next->view->prevFocusChain->nextFocusChain = NULL;
616 tPtr->view->nextFocusChain = next->view;
617 next->view->prevFocusChain = tPtr->view;
621 void
622 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
624 CHECK_CLASS(tPtr, WC_TextField);
625 if (prev == NULL) {
626 if (tPtr->view->prevFocusChain)
627 tPtr->view->prevFocusChain->nextFocusChain = NULL;
628 tPtr->view->prevFocusChain = NULL;
629 return;
632 CHECK_CLASS(prev, WC_TextField);
634 if (tPtr->view->prevFocusChain)
635 tPtr->view->prevFocusChain->nextFocusChain = NULL;
636 if (prev->view->nextFocusChain)
637 prev->view->nextFocusChain->prevFocusChain = NULL;
639 tPtr->view->prevFocusChain = prev->view;
640 prev->view->nextFocusChain = tPtr->view;
644 void
645 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
647 CHECK_CLASS(tPtr, WC_TextField);
649 if (tPtr->font)
650 WMReleaseFont(tPtr->font);
651 tPtr->font = WMRetainFont(font);
653 tPtr->offsetWidth =
654 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
656 if (tPtr->view->flags.realized) {
657 paintTextField(tPtr);
663 WMFont*
664 WMGetTextFieldFont(WMTextField *tPtr)
666 return tPtr->font;
670 static void
671 didResizeTextField(W_ViewDelegate *self, WMView *view)
673 WMTextField *tPtr = (WMTextField*)view->self;
675 tPtr->offsetWidth =
676 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
678 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth /*+ 2*/;
682 static char*
683 makeHiddenString(int length)
685 char *data = wmalloc(length+1);
687 memset(data, '*', length);
688 data[length] = '\0';
690 return data;
694 static void
695 paintCursor(TextField *tPtr)
697 int cx;
698 WMScreen *screen = tPtr->view->screen;
699 int textWidth;
700 char *text;
702 if (tPtr->flags.secure)
703 text = makeHiddenString(strlen(tPtr->text));
704 else
705 text = tPtr->text;
707 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
708 tPtr->cursorPosition-tPtr->viewPosition);
710 switch (tPtr->flags.alignment) {
711 case WARight:
712 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
713 if (textWidth < tPtr->usableWidth)
714 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
715 else
716 cx += tPtr->offsetWidth + 1;
717 break;
718 case WALeft:
719 cx += tPtr->offsetWidth + 1;
720 break;
721 case WAJustified:
722 /* not supported */
723 case WACenter:
724 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
725 if (textWidth < tPtr->usableWidth)
726 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
727 else
728 cx += tPtr->offsetWidth;
729 break;
732 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
733 cx, tPtr->offsetWidth, 1,
734 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
735 printf("%d %d\n",cx,tPtr->cursorPosition);
737 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
738 cx, tPtr->offsetWidth, cx,
739 tPtr->view->size.height - tPtr->offsetWidth - 1);
741 if (tPtr->flags.secure)
742 wfree(text);
747 static void
748 drawRelief(WMView *view, Bool beveled)
750 WMScreen *scr = view->screen;
751 Display *dpy = scr->display;
752 GC wgc;
753 GC lgc;
754 GC dgc;
755 int width = view->size.width;
756 int height = view->size.height;
758 dgc = WMColorGC(scr->darkGray);
760 if (!beveled) {
761 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
763 return;
765 wgc = WMColorGC(scr->white);
766 lgc = WMColorGC(scr->gray);
768 /* top left */
769 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
770 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
772 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
773 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
775 /* bottom right */
776 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
777 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
779 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
780 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
784 static void
785 paintTextField(TextField *tPtr)
787 W_Screen *screen = tPtr->view->screen;
788 W_View *view = tPtr->view;
789 W_View viewbuffer;
790 int tx, ty, tw, th;
791 int rx;
792 int bd;
793 int totalWidth;
794 char *text;
795 Pixmap drawbuffer;
798 if (!view->flags.realized || !view->flags.mapped)
799 return;
801 if (!tPtr->flags.bordered) {
802 bd = 0;
803 } else {
804 bd = 2;
807 if (tPtr->flags.secure) {
808 text = makeHiddenString(strlen(tPtr->text));
809 } else {
810 text = tPtr->text;
813 totalWidth = tPtr->view->size.width - 2*bd;
815 drawbuffer = XCreatePixmap(screen->display, view->window,
816 view->size.width, view->size.height, screen->depth);
817 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
818 0,0, view->size.width,view->size.height);
819 /* this is quite dirty */
820 viewbuffer.screen = view->screen;
821 viewbuffer.size = view->size;
822 viewbuffer.window = drawbuffer;
825 if (tPtr->textLen > 0) {
826 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
827 tPtr->textLen - tPtr->viewPosition);
829 th = WMFontHeight(tPtr->font);
831 ty = tPtr->offsetWidth;
832 switch (tPtr->flags.alignment) {
833 case WALeft:
834 tx = tPtr->offsetWidth + 1;
835 if (tw < tPtr->usableWidth)
836 XFillRectangle(screen->display, drawbuffer,
837 WMColorGC(screen->white),
838 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
839 break;
841 case WACenter:
842 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
843 if (tw < tPtr->usableWidth)
844 XClearArea(screen->display, view->window, bd, bd,
845 totalWidth, view->size.height-2*bd, False);
846 break;
848 default:
849 case WARight:
850 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
851 if (tw < tPtr->usableWidth)
852 XClearArea(screen->display, view->window, bd, bd,
853 totalWidth-tw, view->size.height-2*bd, False);
854 break;
857 if (!tPtr->flags.enabled)
858 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
860 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
861 tPtr->font, tx, ty,
862 &(text[tPtr->viewPosition]),
863 tPtr->textLen - tPtr->viewPosition);
865 if (tPtr->selection.count) {
866 int count,count2;
868 count = tPtr->selection.count < 0
869 ? tPtr->selection.position + tPtr->selection.count
870 : tPtr->selection.position;
871 count2 = abs(tPtr->selection.count);
872 if (count < tPtr->viewPosition) {
873 count2 = abs(count2 - abs(tPtr->viewPosition - count));
874 count = tPtr->viewPosition;
878 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
879 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
881 XSetBackground(screen->display, screen->textFieldGC,
882 screen->gray->color.pixel);
884 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
885 tPtr->font, rx, ty, &(text[count]),
886 count2);
888 XSetBackground(screen->display, screen->textFieldGC,
889 screen->white->color.pixel);
892 if (!tPtr->flags.enabled)
893 WMSetColorInGC(screen->black, screen->textFieldGC);
894 } else {
895 XFillRectangle(screen->display, drawbuffer,
896 WMColorGC(screen->white),
897 bd,bd, totalWidth,view->size.height-2*bd);
900 /* draw relief */
901 if (tPtr->flags.bordered) {
902 drawRelief(&viewbuffer, tPtr->flags.beveled);
905 if (tPtr->flags.secure)
906 wfree(text);
907 XCopyArea(screen->display, drawbuffer, view->window,
908 screen->copyGC, 0,0, view->size.width,
909 view->size.height,0,0);
910 XFreePixmap(screen->display, drawbuffer);
912 /* draw cursor */
913 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
914 paintCursor(tPtr);
919 #if 0
920 static void
921 blinkCursor(void *data)
923 TextField *tPtr = (TextField*)data;
925 if (tPtr->flags.cursorOn) {
926 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
927 data);
928 } else {
929 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
930 data);
932 paintCursor(tPtr);
933 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
935 #endif
938 static void
939 handleEvents(XEvent *event, void *data)
941 TextField *tPtr = (TextField*)data;
943 CHECK_CLASS(data, WC_TextField);
946 switch (event->type) {
947 case FocusIn:
948 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
949 return;
950 tPtr->flags.focused = 1;
951 #if 0
952 if (!tPtr->timerID) {
953 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
954 blinkCursor, tPtr);
956 #endif
957 paintTextField(tPtr);
959 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
961 tPtr->flags.notIllegalMovement = 0;
962 break;
964 case FocusOut:
965 tPtr->flags.focused = 0;
966 #if 0
967 if (tPtr->timerID)
968 WMDeleteTimerHandler(tPtr->timerID);
969 tPtr->timerID = NULL;
970 #endif
972 paintTextField(tPtr);
973 if (!tPtr->flags.notIllegalMovement) {
974 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
975 (void*)WMIllegalTextMovement);
977 break;
979 case Expose:
980 if (event->xexpose.count!=0)
981 break;
982 paintTextField(tPtr);
983 break;
985 case DestroyNotify:
986 destroyTextField(tPtr);
987 break;
992 static void
993 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
995 char buffer[64];
996 KeySym ksym;
997 char *textEvent = NULL;
998 void *data = NULL;
999 int count, refresh = 0;
1000 int control_pressed = 0;
1001 int cancelSelection = 1;
1002 Bool shifted, controled, modified;
1003 Bool relay = True;
1005 /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
1006 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
1007 control_pressed = 1;
1009 shifted = event->xkey.state & ShiftMask;
1010 controled = event->xkey.state & ControlMask;
1011 if ((event->xkey.state & (ShiftMask|ControlMask)) != 0) {
1012 modified = True;
1013 } else {
1014 modified = False;
1017 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
1018 buffer[count] = '\0';
1020 switch (ksym) {
1021 case XK_Tab:
1022 #ifdef XK_ISO_Left_Tab
1023 case XK_ISO_Left_Tab:
1024 #endif
1025 if (!controled && !modified) {
1026 if (shifted) {
1027 if (tPtr->view->prevFocusChain) {
1028 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1029 tPtr->view->prevFocusChain);
1030 tPtr->flags.notIllegalMovement = 1;
1032 data = (void*)WMBacktabTextMovement;
1033 } else {
1034 if (tPtr->view->nextFocusChain) {
1035 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1036 tPtr->view->nextFocusChain);
1037 tPtr->flags.notIllegalMovement = 1;
1039 data = (void*)WMTabTextMovement;
1041 textEvent = WMTextDidEndEditingNotification;
1043 relay = False;
1045 break;
1047 case XK_Escape:
1048 if (!shifted && !controled && !modified) {
1049 data = (void*)WMEscapeTextMovement;
1050 textEvent = WMTextDidEndEditingNotification;
1052 relay = False;
1054 break;
1056 case XK_Return:
1057 if (!shifted && !controled && !modified) {
1058 data = (void*)WMReturnTextMovement;
1059 textEvent = WMTextDidEndEditingNotification;
1061 relay = False;
1063 break;
1065 case WM_EMACSKEY_LEFT:
1066 if (!control_pressed)
1067 goto normal_key;
1068 else
1069 modified = False;
1070 #ifdef XK_KP_Left
1071 case XK_KP_Left:
1072 #endif
1073 case XK_Left:
1074 if (!modified) {
1075 if (tPtr->cursorPosition > 0) {
1076 paintCursor(tPtr);
1077 if (event->xkey.state & ControlMask) {
1078 int i = tPtr->cursorPosition - 1;
1080 while (i > 0 && tPtr->text[i] != ' ') i--;
1081 while (i > 0 && tPtr->text[i] == ' ') i--;
1083 tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
1084 } else
1085 tPtr->cursorPosition--;
1087 if (tPtr->cursorPosition < tPtr->viewPosition) {
1088 tPtr->viewPosition = tPtr->cursorPosition;
1089 refresh = 1;
1090 } else
1091 paintCursor(tPtr);
1093 if (event->xkey.state & ShiftMask)
1094 cancelSelection = 0;
1096 relay = False;
1098 break;
1100 case WM_EMACSKEY_RIGHT:
1101 if (!control_pressed)
1102 goto normal_key;
1103 else
1104 modified = False;
1106 #ifdef XK_KP_Right
1107 case XK_KP_Right:
1108 #endif
1109 case XK_Right:
1110 if (!modified) {
1111 if (tPtr->cursorPosition < tPtr->textLen) {
1112 paintCursor(tPtr);
1113 if (event->xkey.state & ControlMask) {
1114 int i = tPtr->cursorPosition;
1116 while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
1117 while (tPtr->text[i] == ' ') i++;
1119 tPtr->cursorPosition = i;
1120 } else {
1121 tPtr->cursorPosition++;
1123 while (WMWidthOfString(tPtr->font,
1124 &(tPtr->text[tPtr->viewPosition]),
1125 tPtr->cursorPosition-tPtr->viewPosition)
1126 > tPtr->usableWidth) {
1127 tPtr->viewPosition++;
1128 refresh = 1;
1130 if (!refresh)
1131 paintCursor(tPtr);
1133 if (event->xkey.state & ShiftMask)
1134 cancelSelection = 0;
1136 relay = False;
1138 break;
1140 case WM_EMACSKEY_HOME:
1141 if (!control_pressed)
1142 goto normal_key;
1143 else {
1144 modified = False;
1145 controled = False;
1147 #ifdef XK_KP_Home
1148 case XK_KP_Home:
1149 #endif
1150 case XK_Home:
1151 if (!modified && !controled) {
1152 if (tPtr->cursorPosition > 0) {
1153 paintCursor(tPtr);
1154 tPtr->cursorPosition = 0;
1155 if (tPtr->viewPosition > 0) {
1156 tPtr->viewPosition = 0;
1157 refresh = 1;
1158 } else
1159 paintCursor(tPtr);
1161 if (event->xkey.state & ShiftMask)
1162 cancelSelection = 0;
1164 relay = False;
1166 break;
1168 case WM_EMACSKEY_END:
1169 if (!control_pressed)
1170 goto normal_key;
1171 else {
1172 modified = False;
1173 controled = False;
1175 #ifdef XK_KP_End
1176 case XK_KP_End:
1177 #endif
1178 case XK_End:
1179 if (!modified && !controled) {
1180 if (tPtr->cursorPosition < tPtr->textLen) {
1181 paintCursor(tPtr);
1182 tPtr->cursorPosition = tPtr->textLen;
1183 tPtr->viewPosition = 0;
1184 while (WMWidthOfString(tPtr->font,
1185 &(tPtr->text[tPtr->viewPosition]),
1186 tPtr->textLen-tPtr->viewPosition)
1187 > tPtr->usableWidth) {
1188 tPtr->viewPosition++;
1189 refresh = 1;
1191 if (!refresh)
1192 paintCursor(tPtr);
1194 if (event->xkey.state & ShiftMask)
1195 cancelSelection = 0;
1197 relay = False;
1199 break;
1201 case WM_EMACSKEY_BS:
1202 if (!control_pressed)
1203 goto normal_key;
1204 else {
1205 modified = False;
1206 controled = False;
1207 shifted = False;
1209 case XK_BackSpace:
1210 if (!modified && !shifted && !controled) {
1211 if (tPtr->selection.count) {
1212 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1213 data = (void*)WMDeleteTextEvent;
1214 textEvent = WMTextDidChangeNotification;
1215 } else if (tPtr->cursorPosition > 0) {
1216 WMRange range;
1217 range.position = tPtr->cursorPosition - 1;
1218 range.count = 1;
1219 WMDeleteTextFieldRange(tPtr, range);
1220 data = (void*)WMDeleteTextEvent;
1221 textEvent = WMTextDidChangeNotification;
1224 relay = False;
1226 break;
1228 case WM_EMACSKEY_DEL:
1229 if (!control_pressed)
1230 goto normal_key;
1231 else {
1232 modified = False;
1233 controled = False;
1234 shifted = False;
1236 #ifdef XK_KP_Delete
1237 case XK_KP_Delete:
1238 #endif
1239 case XK_Delete:
1240 if (!modified && !controled && !shifted) {
1241 if (tPtr->selection.count) {
1242 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1243 data = (void*)WMDeleteTextEvent;
1244 textEvent = WMTextDidChangeNotification;
1245 } else if (tPtr->cursorPosition < tPtr->textLen) {
1246 WMRange range;
1247 range.position = tPtr->cursorPosition;
1248 range.count = 1;
1249 WMDeleteTextFieldRange(tPtr, range);
1250 data = (void*)WMDeleteTextEvent;
1251 textEvent = WMTextDidChangeNotification;
1254 relay = False;
1256 break;
1258 normal_key:
1259 default:
1260 if (!controled) {
1261 if (count > 0 && !iscntrl(buffer[0])) {
1262 if (tPtr->selection.count)
1263 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1264 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1265 data = (void*)WMInsertTextEvent;
1266 textEvent = WMTextDidChangeNotification;
1268 relay = False;
1271 break;
1274 if (relay) {
1275 WMRelayToNextResponder(W_VIEW(tPtr), event);
1276 return;
1279 /* Do not allow text selection in secure text fields */
1280 if (cancelSelection || tPtr->flags.secure) {
1281 lostHandler(tPtr->view, XA_PRIMARY, NULL);
1283 if (tPtr->selection.count) {
1284 tPtr->selection.count = 0;
1285 refresh = 1;
1287 tPtr->selection.position = tPtr->cursorPosition;
1288 } else {
1289 if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
1291 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1293 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1295 refresh = 1;
1299 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1301 if (textEvent) {
1302 WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);
1304 if (tPtr->delegate) {
1305 if (textEvent==WMTextDidBeginEditingNotification &&
1306 tPtr->delegate->didBeginEditing)
1307 (*tPtr->delegate->didBeginEditing)(tPtr->delegate, notif);
1309 else if (textEvent==WMTextDidEndEditingNotification &&
1310 tPtr->delegate->didEndEditing)
1311 (*tPtr->delegate->didEndEditing)(tPtr->delegate, notif);
1313 else if (textEvent==WMTextDidChangeNotification &&
1314 tPtr->delegate->didChange)
1315 (*tPtr->delegate->didChange)(tPtr->delegate, notif);
1318 WMPostNotification(notif);
1319 WMReleaseNotification(notif);
1322 if (refresh)
1323 paintTextField(tPtr);
1325 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1329 static int
1330 pointToCursorPosition(TextField *tPtr, int x)
1332 int a, b, mid;
1333 int tw;
1335 if (tPtr->flags.bordered)
1336 x -= 2;
1338 a = tPtr->viewPosition;
1339 b = tPtr->viewPosition + tPtr->textLen;
1340 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1341 tPtr->textLen - tPtr->viewPosition) < x)
1342 return tPtr->textLen;
1344 while (a < b && b-a>1) {
1345 mid = (a+b)/2;
1346 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1347 mid - tPtr->viewPosition);
1348 if (tw > x)
1349 b = mid;
1350 else if (tw < x)
1351 a = mid;
1352 else
1353 return mid;
1355 return (a+b)/2;
1360 static void
1361 pasteText(WMView *view, Atom selection, Atom target, Time timestamp,
1362 void *cdata, WMData *data)
1364 TextField *tPtr = (TextField*)view->self;
1365 char *str;
1367 tPtr->flags.waitingSelection = 0;
1369 if (data != NULL) {
1370 str = (char*)WMDataBytes(data);
1372 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1373 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1374 (void*)WMInsertTextEvent);
1375 } else {
1376 int n;
1378 str = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1380 if (str != NULL) {
1381 str[n] = 0;
1382 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1383 XFree(str);
1384 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1385 (void*)WMInsertTextEvent);
1391 static void
1392 handleTextFieldActionEvents(XEvent *event, void *data)
1394 TextField *tPtr = (TextField*)data;
1395 static int move = 0;
1396 static Time lastButtonReleasedEvent = 0;
1397 Display *dpy = event->xany.display;
1399 CHECK_CLASS(data, WC_TextField);
1401 switch (event->type) {
1402 case KeyPress:
1403 if (tPtr->flags.waitingSelection) {
1404 return;
1406 if (tPtr->flags.enabled && tPtr->flags.focused) {
1407 handleTextFieldKeyPress(tPtr, event);
1408 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1409 W_VIEW(tPtr)->screen->invisibleCursor);
1410 tPtr->flags.pointerGrabbed = 1;
1412 break;
1414 case MotionNotify:
1416 if (tPtr->flags.pointerGrabbed) {
1417 tPtr->flags.pointerGrabbed = 0;
1418 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1419 W_VIEW(tPtr)->screen->textCursor);
1421 if (tPtr->flags.waitingSelection) {
1422 return;
1425 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1427 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1428 tPtr->usableWidth) {
1429 if (WMWidthOfString(tPtr->font,
1430 &(tPtr->text[tPtr->viewPosition]),
1431 tPtr->cursorPosition-tPtr->viewPosition)
1432 > tPtr->usableWidth) {
1433 tPtr->viewPosition++;
1435 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1436 paintCursor(tPtr);
1437 tPtr->viewPosition--;
1440 tPtr->cursorPosition =
1441 pointToCursorPosition(tPtr, event->xmotion.x);
1443 /* Do not allow text selection in secure textfields */
1444 if (tPtr->flags.secure) {
1445 tPtr->selection.position = tPtr->cursorPosition;
1448 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1450 if (tPtr->selection.count != 0) {
1451 if (!tPtr->flags.ownsSelection) {
1452 WMCreateSelectionHandler(tPtr->view,
1453 XA_PRIMARY,
1454 event->xbutton.time,
1455 &selectionHandler, NULL);
1456 tPtr->flags.ownsSelection = 1;
1460 paintCursor(tPtr);
1461 paintTextField(tPtr);
1464 break;
1466 case ButtonPress:
1467 if (tPtr->flags.pointerGrabbed) {
1468 tPtr->flags.pointerGrabbed = 0;
1469 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1470 W_VIEW(tPtr)->screen->textCursor);
1471 break;
1474 if (tPtr->flags.waitingSelection) {
1475 break;
1478 move = 1;
1479 switch (tPtr->flags.alignment) {
1480 int textWidth;
1481 case WARight:
1482 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1483 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1484 WMSetFocusToWidget(tPtr);
1485 } else if (tPtr->flags.focused) {
1486 tPtr->selection.position = tPtr->cursorPosition;
1487 tPtr->selection.count = 0;
1489 if (textWidth < tPtr->usableWidth) {
1490 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1491 event->xbutton.x - tPtr->usableWidth
1492 + textWidth);
1493 } else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1494 event->xbutton.x);
1496 paintTextField(tPtr);
1497 break;
1499 case WALeft:
1500 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1501 WMSetFocusToWidget(tPtr);
1502 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1503 event->xbutton.x);
1504 paintTextField(tPtr);
1505 } else if (tPtr->flags.focused
1506 && event->xbutton.button == Button1) {
1507 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1508 event->xbutton.x);
1509 tPtr->selection.position = tPtr->cursorPosition;
1510 tPtr->selection.count = 0;
1511 paintTextField(tPtr);
1513 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1514 char *text;
1515 int n;
1517 if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
1518 event->xbutton.time,
1519 pasteText, NULL)) {
1520 text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1522 if (text) {
1523 text[n] = 0;
1524 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1525 XFree(text);
1526 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1527 (void*)WMInsertTextEvent);
1529 } else {
1530 tPtr->flags.waitingSelection = 1;
1533 break;
1534 default:
1535 break;
1537 break;
1539 case ButtonRelease:
1540 if (tPtr->flags.pointerGrabbed) {
1541 tPtr->flags.pointerGrabbed = 0;
1542 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1543 W_VIEW(tPtr)->screen->textCursor);
1545 if (tPtr->flags.waitingSelection) {
1546 break;
1549 if (tPtr->selection.count != 0) {
1550 int start, count;
1551 XRotateBuffers(dpy, 1);
1553 count = abs(tPtr->selection.count);
1554 if (tPtr->selection.count < 0)
1555 start = tPtr->selection.position - count;
1556 else
1557 start = tPtr->selection.position;
1559 XStoreBuffer(dpy, &tPtr->text[start], count, 0);
1562 move = 0;
1564 if (!tPtr->flags.secure &&
1565 event->xbutton.time - lastButtonReleasedEvent
1566 <= WINGsConfiguration.doubleClickDelay) {
1567 tPtr->selection.position = 0;
1568 tPtr->selection.count = tPtr->textLen;
1569 paintTextField(tPtr);
1571 if (!tPtr->flags.ownsSelection) {
1572 WMCreateSelectionHandler(tPtr->view,
1573 XA_PRIMARY,
1574 event->xbutton.time,
1575 &selectionHandler, NULL);
1576 tPtr->flags.ownsSelection = 1;
1579 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1581 lastButtonReleasedEvent = event->xbutton.time;
1583 break;
1588 static void
1589 destroyTextField(TextField *tPtr)
1591 #if 0
1592 if (tPtr->timerID)
1593 WMDeleteTimerHandler(tPtr->timerID);
1594 #endif
1596 WMReleaseFont(tPtr->font);
1597 WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
1598 WMRemoveNotificationObserver(tPtr);
1600 if (tPtr->text)
1601 wfree(tPtr->text);
1603 wfree(tPtr);