drag and drop!
[wmaker-crm.git] / WINGs / wtextfield.c
blob52d23e816bd33aea1da8eadfa5273eff9430ecd9
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);
301 WMTextField*
302 WMCreateTextField(WMWidget *parent)
304 TextField *tPtr;
306 tPtr = wmalloc(sizeof(TextField));
307 memset(tPtr, 0, sizeof(TextField));
309 tPtr->widgetClass = WC_TextField;
311 tPtr->view = W_CreateView(W_VIEW(parent));
312 if (!tPtr->view) {
313 wfree(tPtr);
314 return NULL;
316 tPtr->view->self = tPtr;
318 tPtr->view->delegate = &_TextFieldViewDelegate;
320 tPtr->view->attribFlags |= CWCursor;
321 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
323 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
325 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
326 tPtr->text[0] = 0;
327 tPtr->textLen = 0;
328 tPtr->bufferSize = MIN_TEXT_BUFFER;
330 tPtr->flags.enabled = 1;
332 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
333 |FocusChangeMask, handleEvents, tPtr);
335 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
337 tPtr->flags.bordered = DEFAULT_BORDERED;
338 tPtr->flags.beveled = True;
339 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
340 tPtr->offsetWidth =
341 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
343 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
345 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
346 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
347 handleTextFieldActionEvents, tPtr);
349 WMAddNotificationObserver(_notification, tPtr, "_lostOwnership", tPtr);
352 tPtr->flags.cursorOn = 1;
354 return tPtr;
358 void
359 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
361 CHECK_CLASS(tPtr, WC_TextField);
363 tPtr->delegate = delegate;
367 void
368 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
370 int len;
372 CHECK_CLASS(tPtr, WC_TextField);
374 if (!text)
375 return;
377 len = strlen(text);
379 /* check if buffer will hold the text */
380 if (len + tPtr->textLen >= tPtr->bufferSize) {
381 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
382 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
385 if (position < 0 || position >= tPtr->textLen) {
386 /* append the text at the end */
387 strcat(tPtr->text, text);
389 incrToFit(tPtr);
391 tPtr->textLen += len;
392 tPtr->cursorPosition += len;
393 } else {
394 /* insert text at position */
395 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
396 tPtr->textLen-position+1);
398 memcpy(&(tPtr->text[position]), text, len);
400 tPtr->textLen += len;
401 if (position >= tPtr->cursorPosition) {
402 tPtr->cursorPosition += len;
403 incrToFit2(tPtr);
404 } else {
405 incrToFit(tPtr);
409 paintTextField(tPtr);
412 void
413 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
415 CHECK_CLASS(tPtr, WC_TextField);
417 normalizeRange(tPtr, &range);
419 if (!range.count)
420 return;
422 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
423 tPtr->textLen - (range.position+range.count) + 1);
425 tPtr->textLen -= range.count;
427 /* try to keep cursorPosition at the same place */
428 tPtr->viewPosition -= range.count;
429 if (tPtr->viewPosition < 0)
430 tPtr->viewPosition = 0;
431 tPtr->cursorPosition = range.position;
433 decrToFit(tPtr);
435 paintTextField(tPtr);
440 char*
441 WMGetTextFieldText(WMTextField *tPtr)
443 CHECK_CLASS(tPtr, WC_TextField);
445 return wstrdup(tPtr->text);
449 void
450 WMSetTextFieldText(WMTextField *tPtr, char *text)
452 CHECK_CLASS(tPtr, WC_TextField);
454 if ((text && strcmp(tPtr->text, text) == 0) ||
455 (!text && tPtr->textLen == 0))
456 return;
458 if (text==NULL) {
459 tPtr->text[0] = 0;
460 tPtr->textLen = 0;
461 } else {
462 tPtr->textLen = strlen(text);
464 if (tPtr->textLen >= tPtr->bufferSize) {
465 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
466 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
468 strcpy(tPtr->text, text);
471 tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
472 tPtr->viewPosition = 0;
473 tPtr->selection.count = 0;
475 if (tPtr->view->flags.realized)
476 paintTextField(tPtr);
480 void
481 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
483 CHECK_CLASS(tPtr, WC_TextField);
485 tPtr->flags.alignment = alignment;
487 if (alignment!=WALeft) {
488 wwarning("only left alignment is supported in textfields");
489 return;
492 if (tPtr->view->flags.realized) {
493 paintTextField(tPtr);
498 void
499 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
501 CHECK_CLASS(tPtr, WC_TextField);
503 tPtr->flags.bordered = bordered;
505 if (tPtr->view->flags.realized) {
506 paintTextField(tPtr);
511 void
512 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
514 CHECK_CLASS(tPtr, WC_TextField);
516 tPtr->flags.beveled = flag;
518 if (tPtr->view->flags.realized) {
519 paintTextField(tPtr);
525 void
526 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
528 CHECK_CLASS(tPtr, WC_TextField);
530 tPtr->flags.secure = flag;
532 if (tPtr->view->flags.realized) {
533 paintTextField(tPtr);
538 Bool
539 WMGetTextFieldEditable(WMTextField *tPtr)
541 CHECK_CLASS(tPtr, WC_TextField);
543 return tPtr->flags.enabled;
547 void
548 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
550 CHECK_CLASS(tPtr, WC_TextField);
552 tPtr->flags.enabled = flag;
554 if (tPtr->view->flags.realized) {
555 paintTextField(tPtr);
560 void
561 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
563 CHECK_CLASS(tPtr, WC_TextField);
565 if (tPtr->flags.enabled) {
566 normalizeRange(tPtr, &range);
568 tPtr->selection = range;
570 tPtr->cursorPosition = range.position + range.count;
572 if (tPtr->view->flags.realized) {
573 paintTextField(tPtr);
579 void
580 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
582 CHECK_CLASS(tPtr, WC_TextField);
584 if (tPtr->flags.enabled) {
585 if (position > tPtr->textLen)
586 position = tPtr->textLen;
588 tPtr->cursorPosition = position;
589 if (tPtr->view->flags.realized) {
590 paintTextField(tPtr);
596 void
597 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
599 CHECK_CLASS(tPtr, WC_TextField);
600 if (next == NULL) {
601 if (tPtr->view->nextFocusChain)
602 tPtr->view->nextFocusChain->prevFocusChain = NULL;
603 tPtr->view->nextFocusChain = NULL;
604 return;
607 CHECK_CLASS(next, WC_TextField);
609 if (tPtr->view->nextFocusChain)
610 tPtr->view->nextFocusChain->prevFocusChain = NULL;
611 if (next->view->prevFocusChain)
612 next->view->prevFocusChain->nextFocusChain = NULL;
614 tPtr->view->nextFocusChain = next->view;
615 next->view->prevFocusChain = tPtr->view;
619 void
620 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
622 CHECK_CLASS(tPtr, WC_TextField);
623 if (prev == NULL) {
624 if (tPtr->view->prevFocusChain)
625 tPtr->view->prevFocusChain->nextFocusChain = NULL;
626 tPtr->view->prevFocusChain = NULL;
627 return;
630 CHECK_CLASS(prev, WC_TextField);
632 if (tPtr->view->prevFocusChain)
633 tPtr->view->prevFocusChain->nextFocusChain = NULL;
634 if (prev->view->nextFocusChain)
635 prev->view->nextFocusChain->prevFocusChain = NULL;
637 tPtr->view->prevFocusChain = prev->view;
638 prev->view->nextFocusChain = tPtr->view;
642 void
643 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
645 CHECK_CLASS(tPtr, WC_TextField);
647 if (tPtr->font)
648 WMReleaseFont(tPtr->font);
649 tPtr->font = WMRetainFont(font);
651 tPtr->offsetWidth =
652 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
654 if (tPtr->view->flags.realized) {
655 paintTextField(tPtr);
661 WMFont*
662 WMGetTextFieldFont(WMTextField *tPtr)
664 return tPtr->font;
668 static void
669 didResizeTextField(W_ViewDelegate *self, WMView *view)
671 WMTextField *tPtr = (WMTextField*)view->self;
673 tPtr->offsetWidth =
674 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
676 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth + 2;
680 static char*
681 makeHiddenString(int length)
683 char *data = wmalloc(length+1);
685 memset(data, '*', length);
686 data[length] = '\0';
688 return data;
692 static void
693 paintCursor(TextField *tPtr)
695 int cx;
696 WMScreen *screen = tPtr->view->screen;
697 int textWidth;
698 char *text;
700 if (tPtr->flags.secure)
701 text = makeHiddenString(strlen(tPtr->text));
702 else
703 text = tPtr->text;
705 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
706 tPtr->cursorPosition-tPtr->viewPosition);
708 switch (tPtr->flags.alignment) {
709 case WARight:
710 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
711 if (textWidth < tPtr->usableWidth)
712 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
713 else
714 cx += tPtr->offsetWidth + 1;
715 break;
716 case WALeft:
717 cx += tPtr->offsetWidth + 1;
718 break;
719 case WAJustified:
720 /* not supported */
721 case WACenter:
722 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
723 if (textWidth < tPtr->usableWidth)
724 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
725 else
726 cx += tPtr->offsetWidth;
727 break;
730 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
731 cx, tPtr->offsetWidth, 1,
732 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
733 printf("%d %d\n",cx,tPtr->cursorPosition);
735 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
736 cx, tPtr->offsetWidth, cx,
737 tPtr->view->size.height - tPtr->offsetWidth - 1);
739 if (tPtr->flags.secure)
740 wfree(text);
745 static void
746 drawRelief(WMView *view, Bool beveled)
748 WMScreen *scr = view->screen;
749 Display *dpy = scr->display;
750 GC wgc;
751 GC lgc;
752 GC dgc;
753 int width = view->size.width;
754 int height = view->size.height;
756 dgc = WMColorGC(scr->darkGray);
758 if (!beveled) {
759 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
761 return;
763 wgc = WMColorGC(scr->white);
764 lgc = WMColorGC(scr->gray);
766 /* top left */
767 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
768 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
770 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
771 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
773 /* bottom right */
774 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
775 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
777 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
778 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
782 static void
783 paintTextField(TextField *tPtr)
785 W_Screen *screen = tPtr->view->screen;
786 W_View *view = tPtr->view;
787 W_View viewbuffer;
788 int tx, ty, tw, th;
789 int rx;
790 int bd;
791 int totalWidth;
792 char *text;
793 Pixmap drawbuffer;
796 if (!view->flags.realized || !view->flags.mapped)
797 return;
799 if (!tPtr->flags.bordered) {
800 bd = 0;
801 } else {
802 bd = 2;
805 if (tPtr->flags.secure) {
806 text = makeHiddenString(strlen(tPtr->text));
807 } else {
808 text = tPtr->text;
811 totalWidth = tPtr->view->size.width - 2*bd;
813 drawbuffer = XCreatePixmap(screen->display, view->window,
814 view->size.width, view->size.height, screen->depth);
815 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
816 0,0, view->size.width,view->size.height);
817 /* this is quite dirty */
818 viewbuffer.screen = view->screen;
819 viewbuffer.size = view->size;
820 viewbuffer.window = drawbuffer;
823 if (tPtr->textLen > 0) {
824 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
825 tPtr->textLen - tPtr->viewPosition);
827 th = WMFontHeight(tPtr->font);
829 ty = tPtr->offsetWidth;
830 switch (tPtr->flags.alignment) {
831 case WALeft:
832 tx = tPtr->offsetWidth + 1;
833 if (tw < tPtr->usableWidth)
834 XFillRectangle(screen->display, drawbuffer,
835 WMColorGC(screen->white),
836 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
837 break;
839 case WACenter:
840 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
841 if (tw < tPtr->usableWidth)
842 XClearArea(screen->display, view->window, bd, bd,
843 totalWidth, view->size.height-2*bd, False);
844 break;
846 default:
847 case WARight:
848 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
849 if (tw < tPtr->usableWidth)
850 XClearArea(screen->display, view->window, bd, bd,
851 totalWidth-tw, view->size.height-2*bd, False);
852 break;
855 if (!tPtr->flags.enabled)
856 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
858 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
859 tPtr->font, tx, ty,
860 &(text[tPtr->viewPosition]),
861 tPtr->textLen - tPtr->viewPosition);
863 if (tPtr->selection.count) {
864 int count,count2;
866 count = tPtr->selection.count < 0
867 ? tPtr->selection.position + tPtr->selection.count
868 : tPtr->selection.position;
869 count2 = abs(tPtr->selection.count);
870 if (count < tPtr->viewPosition) {
871 count2 = abs(count2 - abs(tPtr->viewPosition - count));
872 count = tPtr->viewPosition;
876 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
877 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
879 XSetBackground(screen->display, screen->textFieldGC,
880 screen->gray->color.pixel);
882 WMDrawImageString(screen, drawbuffer, screen->textFieldGC,
883 tPtr->font, rx, ty, &(text[count]),
884 count2);
886 XSetBackground(screen->display, screen->textFieldGC,
887 screen->white->color.pixel);
890 if (!tPtr->flags.enabled)
891 WMSetColorInGC(screen->black, screen->textFieldGC);
892 } else {
893 XFillRectangle(screen->display, drawbuffer,
894 WMColorGC(screen->white),
895 bd,bd, totalWidth,view->size.height-2*bd);
898 /* draw relief */
899 if (tPtr->flags.bordered) {
900 drawRelief(&viewbuffer, tPtr->flags.beveled);
903 if (tPtr->flags.secure)
904 wfree(text);
905 XCopyArea(screen->display, drawbuffer, view->window,
906 screen->copyGC, 0,0, view->size.width,
907 view->size.height,0,0);
908 XFreePixmap(screen->display, drawbuffer);
910 /* draw cursor */
911 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
912 paintCursor(tPtr);
917 #if 0
918 static void
919 blinkCursor(void *data)
921 TextField *tPtr = (TextField*)data;
923 if (tPtr->flags.cursorOn) {
924 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
925 data);
926 } else {
927 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
928 data);
930 paintCursor(tPtr);
931 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
933 #endif
936 static void
937 handleEvents(XEvent *event, void *data)
939 TextField *tPtr = (TextField*)data;
941 CHECK_CLASS(data, WC_TextField);
944 switch (event->type) {
945 case FocusIn:
946 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
947 return;
948 tPtr->flags.focused = 1;
949 #if 0
950 if (!tPtr->timerID) {
951 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
952 blinkCursor, tPtr);
954 #endif
955 paintTextField(tPtr);
957 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
959 tPtr->flags.notIllegalMovement = 0;
960 break;
962 case FocusOut:
963 tPtr->flags.focused = 0;
964 #if 0
965 if (tPtr->timerID)
966 WMDeleteTimerHandler(tPtr->timerID);
967 tPtr->timerID = NULL;
968 #endif
970 paintTextField(tPtr);
971 if (!tPtr->flags.notIllegalMovement) {
972 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
973 (void*)WMIllegalTextMovement);
975 break;
977 case Expose:
978 if (event->xexpose.count!=0)
979 break;
980 paintTextField(tPtr);
981 break;
983 case DestroyNotify:
984 destroyTextField(tPtr);
985 break;
990 static void
991 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
993 char buffer[64];
994 KeySym ksym;
995 char *textEvent = NULL;
996 void *data = NULL;
997 int count, refresh = 0;
998 int control_pressed = 0;
999 int cancelSelection = 1;
1001 /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
1002 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
1003 control_pressed = 1;
1005 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
1006 buffer[count] = '\0';
1008 switch (ksym) {
1009 case XK_Tab:
1010 #ifdef XK_ISO_Left_Tab
1011 case XK_ISO_Left_Tab:
1012 #endif
1013 if (event->xkey.state & ShiftMask) {
1014 if (tPtr->view->prevFocusChain) {
1015 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1016 tPtr->view->prevFocusChain);
1017 tPtr->flags.notIllegalMovement = 1;
1019 data = (void*)WMBacktabTextMovement;
1020 } else {
1021 if (tPtr->view->nextFocusChain) {
1022 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1023 tPtr->view->nextFocusChain);
1024 tPtr->flags.notIllegalMovement = 1;
1026 data = (void*)WMTabTextMovement;
1028 textEvent = WMTextDidEndEditingNotification;
1029 break;
1031 case XK_Return:
1032 data = (void*)WMReturnTextMovement;
1033 textEvent = WMTextDidEndEditingNotification;
1034 break;
1036 case WM_EMACSKEY_LEFT:
1037 if (!control_pressed) {
1038 goto normal_key;
1040 #ifdef XK_KP_Left
1041 case XK_KP_Left:
1042 #endif
1043 case XK_Left:
1044 if (tPtr->cursorPosition > 0) {
1045 paintCursor(tPtr);
1046 if (event->xkey.state & ControlMask) {
1047 int i = tPtr->cursorPosition - 1;
1049 while (i > 0 && tPtr->text[i] != ' ') i--;
1050 while (i > 0 && tPtr->text[i] == ' ') i--;
1052 tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
1053 } else
1054 tPtr->cursorPosition--;
1056 if (tPtr->cursorPosition < tPtr->viewPosition) {
1057 tPtr->viewPosition = tPtr->cursorPosition;
1058 refresh = 1;
1059 } else
1060 paintCursor(tPtr);
1062 if (event->xkey.state & ShiftMask)
1063 cancelSelection = 0;
1064 break;
1066 case WM_EMACSKEY_RIGHT:
1067 if (!control_pressed) {
1068 goto normal_key;
1070 #ifdef XK_KP_Right
1071 case XK_KP_Right:
1072 #endif
1073 case XK_Right:
1074 if (tPtr->cursorPosition < tPtr->textLen) {
1075 paintCursor(tPtr);
1076 if (event->xkey.state & ControlMask) {
1077 int i = tPtr->cursorPosition;
1079 while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
1080 while (tPtr->text[i] == ' ') i++;
1082 tPtr->cursorPosition = i;
1083 } else {
1084 tPtr->cursorPosition++;
1086 while (WMWidthOfString(tPtr->font,
1087 &(tPtr->text[tPtr->viewPosition]),
1088 tPtr->cursorPosition-tPtr->viewPosition)
1089 > tPtr->usableWidth) {
1090 tPtr->viewPosition++;
1091 refresh = 1;
1093 if (!refresh)
1094 paintCursor(tPtr);
1096 if (event->xkey.state & ShiftMask)
1097 cancelSelection = 0;
1098 break;
1100 case WM_EMACSKEY_HOME:
1101 if (!control_pressed) {
1102 goto normal_key;
1104 #ifdef XK_KP_Home
1105 case XK_KP_Home:
1106 #endif
1107 case XK_Home:
1108 if (tPtr->cursorPosition > 0) {
1109 paintCursor(tPtr);
1110 tPtr->cursorPosition = 0;
1111 if (tPtr->viewPosition > 0) {
1112 tPtr->viewPosition = 0;
1113 refresh = 1;
1114 } else
1115 paintCursor(tPtr);
1117 if (event->xkey.state & ShiftMask)
1118 cancelSelection = 0;
1119 break;
1121 case WM_EMACSKEY_END:
1122 if (!control_pressed) {
1123 goto normal_key;
1125 #ifdef XK_KP_End
1126 case XK_KP_End:
1127 #endif
1128 case XK_End:
1129 if (tPtr->cursorPosition < tPtr->textLen) {
1130 paintCursor(tPtr);
1131 tPtr->cursorPosition = tPtr->textLen;
1132 tPtr->viewPosition = 0;
1133 while (WMWidthOfString(tPtr->font,
1134 &(tPtr->text[tPtr->viewPosition]),
1135 tPtr->textLen-tPtr->viewPosition)
1136 > tPtr->usableWidth) {
1137 tPtr->viewPosition++;
1138 refresh = 1;
1140 if (!refresh)
1141 paintCursor(tPtr);
1143 if (event->xkey.state & ShiftMask)
1144 cancelSelection = 0;
1145 break;
1147 case WM_EMACSKEY_BS:
1148 if (!control_pressed) {
1149 goto normal_key;
1151 case XK_BackSpace:
1152 if (tPtr->selection.count) {
1153 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1154 data = (void*)WMDeleteTextEvent;
1155 textEvent = WMTextDidChangeNotification;
1156 } else if (tPtr->cursorPosition > 0) {
1157 WMRange range;
1158 range.position = tPtr->cursorPosition - 1;
1159 range.count = 1;
1160 WMDeleteTextFieldRange(tPtr, range);
1161 data = (void*)WMDeleteTextEvent;
1162 textEvent = WMTextDidChangeNotification;
1164 break;
1166 case WM_EMACSKEY_DEL:
1167 if (!control_pressed) {
1168 goto normal_key;
1170 #ifdef XK_KP_Delete
1171 case XK_KP_Delete:
1172 #endif
1173 case XK_Delete:
1174 if (tPtr->selection.count) {
1175 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1176 data = (void*)WMDeleteTextEvent;
1177 textEvent = WMTextDidChangeNotification;
1178 } else if (tPtr->cursorPosition < tPtr->textLen) {
1179 WMRange range;
1180 range.position = tPtr->cursorPosition;
1181 range.count = 1;
1182 WMDeleteTextFieldRange(tPtr, range);
1183 data = (void*)WMDeleteTextEvent;
1184 textEvent = WMTextDidChangeNotification;
1186 break;
1188 normal_key:
1189 default:
1190 if (count > 0 && isprint(buffer[0])) {
1191 if (tPtr->selection.count)
1192 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1193 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1194 data = (void*)WMInsertTextEvent;
1195 textEvent = WMTextDidChangeNotification;
1196 } else {
1197 /* should we rather break and goto cancel selection below? -Dan */
1198 return;
1200 break;
1203 if (!cancelSelection) {
1204 if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
1206 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1208 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1210 refresh = 1;
1212 } else {
1214 lostHandler(tPtr->view, XA_PRIMARY, NULL);
1216 if (tPtr->selection.count) {
1217 tPtr->selection.count = 0;
1218 refresh = 1;
1220 tPtr->selection.position = tPtr->cursorPosition;
1223 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1225 if (textEvent) {
1226 WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);
1228 if (tPtr->delegate) {
1229 if (textEvent==WMTextDidBeginEditingNotification &&
1230 tPtr->delegate->didBeginEditing)
1231 (*tPtr->delegate->didBeginEditing)(tPtr->delegate, notif);
1232 else if (textEvent==WMTextDidEndEditingNotification &&
1233 tPtr->delegate->didEndEditing)
1234 (*tPtr->delegate->didEndEditing)(tPtr->delegate, notif);
1235 else if (textEvent==WMTextDidChangeNotification &&
1236 tPtr->delegate->didChange)
1237 (*tPtr->delegate->didChange)(tPtr->delegate, notif);
1240 WMPostNotification(notif);
1241 WMReleaseNotification(notif);
1244 if (refresh)
1245 paintTextField(tPtr);
1247 /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
1251 static int
1252 pointToCursorPosition(TextField *tPtr, int x)
1254 int a, b, mid;
1255 int tw;
1257 if (tPtr->flags.bordered)
1258 x -= 2;
1260 a = tPtr->viewPosition;
1261 b = tPtr->viewPosition + tPtr->textLen;
1262 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1263 tPtr->textLen - tPtr->viewPosition) < x)
1264 return tPtr->textLen;
1266 while (a < b && b-a>1) {
1267 mid = (a+b)/2;
1268 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1269 mid - tPtr->viewPosition);
1270 if (tw > x)
1271 b = mid;
1272 else if (tw < x)
1273 a = mid;
1274 else
1275 return mid;
1277 return (a+b)/2;
1282 static void
1283 pasteText(WMView *view, Atom selection, Atom target, Time timestamp,
1284 void *cdata, WMData *data)
1286 TextField *tPtr = (TextField*)view->self;
1287 char *str;
1289 tPtr->flags.waitingSelection = 0;
1291 if (data != NULL) {
1292 str = (char*)WMDataBytes(data);
1294 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1295 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1296 (void*)WMInsertTextEvent);
1297 } else {
1298 int n;
1300 str = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1302 if (str != NULL) {
1303 str[n] = 0;
1304 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1305 XFree(str);
1306 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1307 (void*)WMInsertTextEvent);
1313 static void
1314 handleTextFieldActionEvents(XEvent *event, void *data)
1316 TextField *tPtr = (TextField*)data;
1317 static int move = 0;
1318 static Time lastButtonReleasedEvent = 0;
1319 Display *dpy = event->xany.display;
1321 CHECK_CLASS(data, WC_TextField);
1323 switch (event->type) {
1324 case KeyPress:
1325 if (tPtr->flags.waitingSelection) {
1326 return;
1328 if (tPtr->flags.enabled && tPtr->flags.focused) {
1329 handleTextFieldKeyPress(tPtr, event);
1330 XGrabPointer(dpy, W_VIEW(tPtr)->window, False,
1331 PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
1332 GrabModeAsync, GrabModeAsync, None,
1333 W_VIEW(tPtr)->screen->invisibleCursor,
1334 CurrentTime);
1335 tPtr->flags.pointerGrabbed = 1;
1337 break;
1339 case MotionNotify:
1341 if (tPtr->flags.pointerGrabbed) {
1342 tPtr->flags.pointerGrabbed = 0;
1343 XUngrabPointer(dpy, CurrentTime);
1345 if (tPtr->flags.waitingSelection) {
1346 return;
1349 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1351 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1352 tPtr->usableWidth) {
1353 if (WMWidthOfString(tPtr->font,
1354 &(tPtr->text[tPtr->viewPosition]),
1355 tPtr->cursorPosition-tPtr->viewPosition)
1356 > tPtr->usableWidth) {
1357 tPtr->viewPosition++;
1359 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1360 paintCursor(tPtr);
1361 tPtr->viewPosition--;
1364 tPtr->cursorPosition =
1365 pointToCursorPosition(tPtr, event->xmotion.x);
1367 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1369 if (tPtr->selection.count != 0) {
1370 if (!tPtr->flags.ownsSelection) {
1371 WMCreateSelectionHandler(tPtr->view,
1372 XA_PRIMARY,
1373 event->xbutton.time,
1374 &selectionHandler, NULL);
1375 tPtr->flags.ownsSelection = 1;
1379 paintCursor(tPtr);
1380 paintTextField(tPtr);
1383 break;
1385 case ButtonPress:
1386 if (tPtr->flags.pointerGrabbed) {
1387 tPtr->flags.pointerGrabbed = 0;
1388 XUngrabPointer(dpy, CurrentTime);
1389 break;
1392 if (tPtr->flags.waitingSelection) {
1393 break;
1396 move = 1;
1397 switch (tPtr->flags.alignment) {
1398 int textWidth;
1399 case WARight:
1400 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1401 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1402 WMSetFocusToWidget(tPtr);
1404 } else if (tPtr->flags.focused) {
1405 tPtr->selection.position = tPtr->cursorPosition;
1406 tPtr->selection.count = 0;
1408 if(textWidth < tPtr->usableWidth) {
1409 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1410 event->xbutton.x - tPtr->usableWidth
1411 + textWidth);
1412 } else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1413 event->xbutton.x);
1415 paintTextField(tPtr);
1416 break;
1418 case WALeft:
1419 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1420 WMSetFocusToWidget(tPtr);
1421 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1422 event->xbutton.x);
1423 paintTextField(tPtr);
1424 } else if (tPtr->flags.focused
1425 && event->xbutton.button == Button1) {
1426 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1427 event->xbutton.x);
1428 tPtr->selection.position = tPtr->cursorPosition;
1429 tPtr->selection.count = 0;
1430 paintTextField(tPtr);
1432 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1433 char *text;
1434 int n;
1436 if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
1437 event->xbutton.time,
1438 pasteText, NULL)) {
1439 text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1441 if (text) {
1442 text[n] = 0;
1443 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1444 XFree(text);
1445 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1446 (void*)WMInsertTextEvent);
1448 } else {
1449 tPtr->flags.waitingSelection = 1;
1452 break;
1453 default:
1454 break;
1456 break;
1458 case ButtonRelease:
1459 if (tPtr->flags.pointerGrabbed) {
1460 tPtr->flags.pointerGrabbed = 0;
1461 XUngrabPointer(dpy, CurrentTime);
1463 if (tPtr->flags.waitingSelection) {
1464 break;
1467 if (tPtr->selection.count != 0) {
1468 int start, count;
1469 XRotateBuffers(dpy, 1);
1471 count = abs(tPtr->selection.count);
1472 if (tPtr->selection.count < 0)
1473 start = tPtr->selection.position - count;
1474 else
1475 start = tPtr->selection.position;
1477 XStoreBuffer(dpy, &tPtr->text[start], count, 0);
1480 move = 0;
1482 if (event->xbutton.time - lastButtonReleasedEvent
1483 <= WINGsConfiguration.doubleClickDelay) {
1484 tPtr->selection.position = 0;
1485 tPtr->selection.count = tPtr->textLen;
1486 paintTextField(tPtr);
1488 if (!tPtr->flags.ownsSelection) {
1489 WMCreateSelectionHandler(tPtr->view,
1490 XA_PRIMARY,
1491 event->xbutton.time,
1492 &selectionHandler, NULL);
1493 tPtr->flags.ownsSelection = 1;
1496 WMPostNotificationName("_lostOwnership", NULL, tPtr);
1498 lastButtonReleasedEvent = event->xbutton.time;
1500 break;
1505 static void
1506 destroyTextField(TextField *tPtr)
1508 #if 0
1509 if (tPtr->timerID)
1510 WMDeleteTimerHandler(tPtr->timerID);
1511 #endif
1513 WMReleaseFont(tPtr->font);
1514 WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
1515 WMRemoveNotificationObserver(tPtr);
1517 if (tPtr->text)
1518 wfree(tPtr->text);
1520 wfree(tPtr);