changed indentation to use spaces only
[wmaker-crm.git] / WINGs / wtextfield.c
blobbc49a22255f0cdb5a00d71354f6edae5243a8b92
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 lostSelection(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 lostSelection,
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 lostSelection(WMView *view, Atom selection, void *cdata)
286 TextField *tPtr = (WMTextField*)view->self;
288 if (tPtr->flags.ownsSelection) {
289 WMDeleteSelectionHandler(view, selection, CurrentTime);
290 tPtr->flags.ownsSelection = 0;
292 if (tPtr->selection.count != 0) {
293 tPtr->selection.count = 0;
294 paintTextField(tPtr);
299 static void
300 selectionNotification(void *observerData, WMNotification *notification)
302 WMView *observerView = (WMView*)observerData;
303 WMView *newOwnerView = (WMView*)WMGetNotificationClientData(notification);
305 if (observerView != newOwnerView) {
307 //if (tPtr->flags.ownsSelection)
308 // WMDeleteSelectionHandler(observerView, XA_PRIMARY, CurrentTime);
310 lostSelection(observerView, XA_PRIMARY, NULL);
315 WMTextField*
316 WMCreateTextField(WMWidget *parent)
318 TextField *tPtr;
320 tPtr = wmalloc(sizeof(TextField));
321 memset(tPtr, 0, sizeof(TextField));
323 tPtr->widgetClass = WC_TextField;
325 tPtr->view = W_CreateView(W_VIEW(parent));
326 if (!tPtr->view) {
327 wfree(tPtr);
328 return NULL;
330 tPtr->view->self = tPtr;
332 tPtr->view->delegate = &_TextFieldViewDelegate;
334 tPtr->view->attribFlags |= CWCursor;
335 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
337 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
339 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
340 tPtr->text[0] = 0;
341 tPtr->textLen = 0;
342 tPtr->bufferSize = MIN_TEXT_BUFFER;
344 tPtr->flags.enabled = 1;
346 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
347 |FocusChangeMask, handleEvents, tPtr);
349 tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
351 tPtr->flags.bordered = DEFAULT_BORDERED;
352 tPtr->flags.beveled = True;
353 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
354 tPtr->offsetWidth =
355 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
357 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
359 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
360 |ButtonReleaseMask|ButtonPressMask|KeyPressMask|Button1MotionMask,
361 handleTextFieldActionEvents, tPtr);
363 WMAddNotificationObserver(selectionNotification, tPtr->view,
364 WMSelectionOwnerDidChangeNotification,
365 (void*)XA_PRIMARY);
368 tPtr->flags.cursorOn = 1;
370 return tPtr;
374 void
375 WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
377 CHECK_CLASS(tPtr, WC_TextField);
379 tPtr->delegate = delegate;
383 WMTextFieldDelegate*
384 WMGetTextFieldDelegate(WMTextField *tPtr)
386 CHECK_CLASS(tPtr, WC_TextField);
388 return tPtr->delegate;
392 void
393 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
395 int len;
397 CHECK_CLASS(tPtr, WC_TextField);
399 if (!text)
400 return;
402 len = strlen(text);
404 /* check if buffer will hold the text */
405 if (len + tPtr->textLen >= tPtr->bufferSize) {
406 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
407 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
410 if (position < 0 || position >= tPtr->textLen) {
411 /* append the text at the end */
412 strcat(tPtr->text, text);
414 incrToFit(tPtr);
416 tPtr->textLen += len;
417 tPtr->cursorPosition += len;
418 } else {
419 /* insert text at position */
420 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
421 tPtr->textLen-position+1);
423 memcpy(&(tPtr->text[position]), text, len);
425 tPtr->textLen += len;
426 if (position >= tPtr->cursorPosition) {
427 tPtr->cursorPosition += len;
428 incrToFit2(tPtr);
429 } else {
430 incrToFit(tPtr);
434 paintTextField(tPtr);
437 void
438 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
440 CHECK_CLASS(tPtr, WC_TextField);
442 normalizeRange(tPtr, &range);
444 if (!range.count)
445 return;
447 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
448 tPtr->textLen - (range.position+range.count) + 1);
450 tPtr->textLen -= range.count;
452 /* try to keep cursorPosition at the same place */
453 tPtr->viewPosition -= range.count;
454 if (tPtr->viewPosition < 0)
455 tPtr->viewPosition = 0;
456 tPtr->cursorPosition = range.position;
458 decrToFit(tPtr);
460 paintTextField(tPtr);
465 char*
466 WMGetTextFieldText(WMTextField *tPtr)
468 CHECK_CLASS(tPtr, WC_TextField);
470 return wstrdup(tPtr->text);
474 void
475 WMSetTextFieldText(WMTextField *tPtr, char *text)
477 CHECK_CLASS(tPtr, WC_TextField);
479 if ((text && strcmp(tPtr->text, text) == 0) ||
480 (!text && tPtr->textLen == 0))
481 return;
483 if (text==NULL) {
484 tPtr->text[0] = 0;
485 tPtr->textLen = 0;
486 } else {
487 tPtr->textLen = strlen(text);
489 if (tPtr->textLen >= tPtr->bufferSize) {
490 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
491 tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
493 strcpy(tPtr->text, text);
496 tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
497 tPtr->viewPosition = 0;
498 tPtr->selection.count = 0;
500 if (tPtr->view->flags.realized)
501 paintTextField(tPtr);
505 void
506 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
508 CHECK_CLASS(tPtr, WC_TextField);
510 tPtr->flags.alignment = alignment;
512 if (alignment!=WALeft) {
513 wwarning("only left alignment is supported in textfields");
514 return;
517 if (tPtr->view->flags.realized) {
518 paintTextField(tPtr);
523 void
524 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
526 CHECK_CLASS(tPtr, WC_TextField);
528 tPtr->flags.bordered = bordered;
530 if (tPtr->view->flags.realized) {
531 paintTextField(tPtr);
536 void
537 WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
539 CHECK_CLASS(tPtr, WC_TextField);
541 tPtr->flags.beveled = ((flag==0) ? 0 : 1);
543 if (tPtr->view->flags.realized) {
544 paintTextField(tPtr);
550 void
551 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
553 CHECK_CLASS(tPtr, WC_TextField);
555 tPtr->flags.secure = ((flag==0) ? 0 : 1);
557 if (tPtr->view->flags.realized) {
558 paintTextField(tPtr);
563 Bool
564 WMGetTextFieldEditable(WMTextField *tPtr)
566 CHECK_CLASS(tPtr, WC_TextField);
568 return tPtr->flags.enabled;
572 void
573 WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
575 CHECK_CLASS(tPtr, WC_TextField);
577 tPtr->flags.enabled = ((flag==0) ? 0 : 1);
579 if (tPtr->view->flags.realized) {
580 paintTextField(tPtr);
585 void
586 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
588 CHECK_CLASS(tPtr, WC_TextField);
590 if (tPtr->flags.enabled) {
591 normalizeRange(tPtr, &range);
593 tPtr->selection = range;
595 tPtr->cursorPosition = range.position + range.count;
597 if (tPtr->view->flags.realized) {
598 paintTextField(tPtr);
604 void
605 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
607 CHECK_CLASS(tPtr, WC_TextField);
609 if (tPtr->flags.enabled) {
610 if (position > tPtr->textLen)
611 position = tPtr->textLen;
613 tPtr->cursorPosition = position;
614 if (tPtr->view->flags.realized) {
615 paintTextField(tPtr);
621 void
622 WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
624 CHECK_CLASS(tPtr, WC_TextField);
625 if (next == NULL) {
626 if (tPtr->view->nextFocusChain)
627 tPtr->view->nextFocusChain->prevFocusChain = NULL;
628 tPtr->view->nextFocusChain = NULL;
629 return;
632 CHECK_CLASS(next, WC_TextField);
634 if (tPtr->view->nextFocusChain)
635 tPtr->view->nextFocusChain->prevFocusChain = NULL;
636 if (next->view->prevFocusChain)
637 next->view->prevFocusChain->nextFocusChain = NULL;
639 tPtr->view->nextFocusChain = next->view;
640 next->view->prevFocusChain = tPtr->view;
644 void
645 WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
647 CHECK_CLASS(tPtr, WC_TextField);
648 if (prev == NULL) {
649 if (tPtr->view->prevFocusChain)
650 tPtr->view->prevFocusChain->nextFocusChain = NULL;
651 tPtr->view->prevFocusChain = NULL;
652 return;
655 CHECK_CLASS(prev, WC_TextField);
657 if (tPtr->view->prevFocusChain)
658 tPtr->view->prevFocusChain->nextFocusChain = NULL;
659 if (prev->view->nextFocusChain)
660 prev->view->nextFocusChain->prevFocusChain = NULL;
662 tPtr->view->prevFocusChain = prev->view;
663 prev->view->nextFocusChain = tPtr->view;
667 void
668 WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
670 CHECK_CLASS(tPtr, WC_TextField);
672 if (tPtr->font)
673 WMReleaseFont(tPtr->font);
674 tPtr->font = WMRetainFont(font);
676 tPtr->offsetWidth =
677 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
679 if (tPtr->view->flags.realized) {
680 paintTextField(tPtr);
686 WMFont*
687 WMGetTextFieldFont(WMTextField *tPtr)
689 return tPtr->font;
693 static void
694 didResizeTextField(W_ViewDelegate *self, WMView *view)
696 WMTextField *tPtr = (WMTextField*)view->self;
698 tPtr->offsetWidth =
699 WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);
701 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth /*+ 2*/;
705 static char*
706 makeHiddenString(int length)
708 char *data = wmalloc(length+1);
710 memset(data, '*', length);
711 data[length] = '\0';
713 return data;
717 static void
718 paintCursor(TextField *tPtr)
720 int cx;
721 WMScreen *screen = tPtr->view->screen;
722 int textWidth;
723 char *text;
725 if (tPtr->flags.secure)
726 text = makeHiddenString(strlen(tPtr->text));
727 else
728 text = tPtr->text;
730 cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
731 tPtr->cursorPosition-tPtr->viewPosition);
733 switch (tPtr->flags.alignment) {
734 case WARight:
735 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
736 if (textWidth < tPtr->usableWidth)
737 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
738 else
739 cx += tPtr->offsetWidth + 1;
740 break;
741 case WALeft:
742 cx += tPtr->offsetWidth + 1;
743 break;
744 case WAJustified:
745 /* not supported */
746 case WACenter:
747 textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
748 if (textWidth < tPtr->usableWidth)
749 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
750 else
751 cx += tPtr->offsetWidth;
752 break;
755 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
756 cx, tPtr->offsetWidth, 1,
757 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
758 printf("%d %d\n",cx,tPtr->cursorPosition);
761 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
762 cx, tPtr->offsetWidth, cx,
763 tPtr->view->size.height - tPtr->offsetWidth - 1);
765 if (tPtr->flags.secure)
766 wfree(text);
771 static void
772 drawRelief(WMView *view, Bool beveled)
774 WMScreen *scr = view->screen;
775 Display *dpy = scr->display;
776 GC wgc;
777 GC lgc;
778 GC dgc;
779 int width = view->size.width;
780 int height = view->size.height;
782 dgc = WMColorGC(scr->darkGray);
784 if (!beveled) {
785 XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);
787 return;
789 wgc = WMColorGC(scr->white);
790 lgc = WMColorGC(scr->gray);
792 /* top left */
793 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
794 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
796 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
797 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
799 /* bottom right */
800 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
801 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
803 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
804 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
808 static void
809 paintTextField(TextField *tPtr)
811 W_Screen *screen = tPtr->view->screen;
812 W_View *view = tPtr->view;
813 W_View viewbuffer;
814 int tx, ty, tw, th;
815 int rx;
816 int bd;
817 int totalWidth;
818 char *text;
819 Pixmap drawbuffer;
820 WMColor *color;
823 if (!view->flags.realized || !view->flags.mapped)
824 return;
826 if (!tPtr->flags.bordered) {
827 bd = 0;
828 } else {
829 bd = 2;
832 if (tPtr->flags.secure) {
833 text = makeHiddenString(strlen(tPtr->text));
834 } else {
835 text = tPtr->text;
838 totalWidth = tPtr->view->size.width - 2*bd;
840 drawbuffer = XCreatePixmap(screen->display, view->window,
841 view->size.width, view->size.height, screen->depth);
842 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
843 0,0, view->size.width,view->size.height);
844 /* this is quite dirty */
845 viewbuffer.screen = view->screen;
846 viewbuffer.size = view->size;
847 viewbuffer.window = drawbuffer;
850 if (tPtr->textLen > 0) {
851 tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
852 tPtr->textLen - tPtr->viewPosition);
854 th = WMFontHeight(tPtr->font);
856 ty = tPtr->offsetWidth;
857 switch (tPtr->flags.alignment) {
858 case WALeft:
859 tx = tPtr->offsetWidth + 1;
860 if (tw < tPtr->usableWidth)
861 XFillRectangle(screen->display, drawbuffer,
862 WMColorGC(screen->white),
863 bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
864 break;
866 case WACenter:
867 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
868 if (tw < tPtr->usableWidth)
869 XClearArea(screen->display, view->window, bd, bd,
870 totalWidth, view->size.height-2*bd, False);
871 break;
873 default:
874 case WARight:
875 tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
876 if (tw < tPtr->usableWidth)
877 XClearArea(screen->display, view->window, bd, bd,
878 totalWidth-tw, view->size.height-2*bd, False);
879 break;
882 color = tPtr->flags.enabled ? screen->black : screen->darkGray;
884 WMDrawImageString(screen, drawbuffer, color, screen->white,
885 tPtr->font, tx, ty, &(text[tPtr->viewPosition]),
886 tPtr->textLen - tPtr->viewPosition);
888 if (tPtr->selection.count) {
889 int count,count2;
891 count = tPtr->selection.count < 0
892 ? tPtr->selection.position + tPtr->selection.count
893 : tPtr->selection.position;
894 count2 = abs(tPtr->selection.count);
895 if (count < tPtr->viewPosition) {
896 count2 = abs(count2 - abs(tPtr->viewPosition - count));
897 count = tPtr->viewPosition;
900 rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
901 - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);
903 WMDrawImageString(screen, drawbuffer, color, screen->gray,
904 tPtr->font, rx, ty, &(text[count]), count2);
906 } else {
907 XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
908 bd, bd, totalWidth,view->size.height-2*bd);
911 /* draw relief */
912 if (tPtr->flags.bordered) {
913 drawRelief(&viewbuffer, tPtr->flags.beveled);
916 if (tPtr->flags.secure)
917 wfree(text);
918 XCopyArea(screen->display, drawbuffer, view->window,
919 screen->copyGC, 0,0, view->size.width,
920 view->size.height,0,0);
921 XFreePixmap(screen->display, drawbuffer);
923 /* draw cursor */
924 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
925 paintCursor(tPtr);
930 #if 0
931 static void
932 blinkCursor(void *data)
934 TextField *tPtr = (TextField*)data;
936 if (tPtr->flags.cursorOn) {
937 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
938 data);
939 } else {
940 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
941 data);
943 paintCursor(tPtr);
944 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
946 #endif
949 static void
950 handleEvents(XEvent *event, void *data)
952 TextField *tPtr = (TextField*)data;
954 CHECK_CLASS(data, WC_TextField);
957 switch (event->type) {
958 case FocusIn:
959 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
960 return;
961 tPtr->flags.focused = 1;
962 #if 0
963 if (!tPtr->timerID) {
964 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
965 blinkCursor, tPtr);
967 #endif
968 paintTextField(tPtr);
970 NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
972 tPtr->flags.notIllegalMovement = 0;
973 break;
975 case FocusOut:
976 tPtr->flags.focused = 0;
977 #if 0
978 if (tPtr->timerID)
979 WMDeleteTimerHandler(tPtr->timerID);
980 tPtr->timerID = NULL;
981 #endif
983 paintTextField(tPtr);
984 if (!tPtr->flags.notIllegalMovement) {
985 NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
986 (void*)WMIllegalTextMovement);
988 break;
990 case Expose:
991 if (event->xexpose.count!=0)
992 break;
993 paintTextField(tPtr);
994 break;
996 case DestroyNotify:
997 destroyTextField(tPtr);
998 break;
1003 static void
1004 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
1006 char buffer[64];
1007 KeySym ksym;
1008 char *textEvent = NULL;
1009 void *data = NULL;
1010 int count, refresh = 0;
1011 int control_pressed = 0;
1012 int cancelSelection = 1;
1013 Bool shifted, controled, modified;
1014 Bool relay = True;
1016 /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
1017 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
1018 control_pressed = 1;
1020 shifted = (event->xkey.state & ShiftMask ? True : False);
1021 controled = (event->xkey.state & ControlMask ? True : False);
1022 modified = shifted || controled;
1024 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
1025 buffer[count] = '\0';
1027 switch (ksym) {
1028 case XK_Tab:
1029 #ifdef XK_ISO_Left_Tab
1030 case XK_ISO_Left_Tab:
1031 #endif
1032 if (!controled) {
1033 if (shifted) {
1034 if (tPtr->view->prevFocusChain) {
1035 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1036 tPtr->view->prevFocusChain);
1037 tPtr->flags.notIllegalMovement = 1;
1039 data = (void*)WMBacktabTextMovement;
1040 } else {
1041 if (tPtr->view->nextFocusChain) {
1042 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
1043 tPtr->view->nextFocusChain);
1044 tPtr->flags.notIllegalMovement = 1;
1046 data = (void*)WMTabTextMovement;
1048 textEvent = WMTextDidEndEditingNotification;
1050 cancelSelection = 0;
1052 relay = False;
1054 break;
1056 case XK_Escape:
1057 if (!modified) {
1058 data = (void*)WMEscapeTextMovement;
1059 textEvent = WMTextDidEndEditingNotification;
1061 relay = False;
1063 break;
1065 case XK_Return:
1066 if (!modified) {
1067 data = (void*)WMReturnTextMovement;
1068 textEvent = WMTextDidEndEditingNotification;
1070 relay = False;
1072 break;
1074 case WM_EMACSKEY_LEFT:
1075 if (!control_pressed)
1076 goto normal_key;
1077 else
1078 controled = False;
1080 #ifdef XK_KP_Left
1081 case XK_KP_Left:
1082 #endif
1083 case XK_Left:
1084 if (tPtr->cursorPosition > 0) {
1085 paintCursor(tPtr);
1086 if (controled) {
1087 int i = tPtr->cursorPosition - 1;
1089 while (i > 0 && tPtr->text[i] != ' ') i--;
1090 while (i > 0 && tPtr->text[i] == ' ') i--;
1092 tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
1093 } else
1094 tPtr->cursorPosition--;
1096 if (tPtr->cursorPosition < tPtr->viewPosition) {
1097 tPtr->viewPosition = tPtr->cursorPosition;
1098 refresh = 1;
1099 } else
1100 paintCursor(tPtr);
1102 if (shifted)
1103 cancelSelection = 0;
1105 relay = False;
1107 break;
1109 case WM_EMACSKEY_RIGHT:
1110 if (!control_pressed)
1111 goto normal_key;
1112 else
1113 controled = False;
1115 #ifdef XK_KP_Right
1116 case XK_KP_Right:
1117 #endif
1118 case XK_Right:
1119 if (tPtr->cursorPosition < tPtr->textLen) {
1120 paintCursor(tPtr);
1121 if (controled) {
1122 int i = tPtr->cursorPosition;
1124 while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
1125 while (tPtr->text[i] == ' ') i++;
1127 tPtr->cursorPosition = i;
1128 } else {
1129 tPtr->cursorPosition++;
1131 while (WMWidthOfString(tPtr->font,
1132 &(tPtr->text[tPtr->viewPosition]),
1133 tPtr->cursorPosition-tPtr->viewPosition)
1134 > tPtr->usableWidth) {
1135 tPtr->viewPosition++;
1136 refresh = 1;
1138 if (!refresh)
1139 paintCursor(tPtr);
1141 if (shifted)
1142 cancelSelection = 0;
1144 relay = False;
1146 break;
1148 case WM_EMACSKEY_HOME:
1149 if (!control_pressed)
1150 goto normal_key;
1151 else
1152 controled = False;
1154 #ifdef XK_KP_Home
1155 case XK_KP_Home:
1156 #endif
1157 case XK_Home:
1158 if (!controled) {
1159 if (tPtr->cursorPosition > 0) {
1160 paintCursor(tPtr);
1161 tPtr->cursorPosition = 0;
1162 if (tPtr->viewPosition > 0) {
1163 tPtr->viewPosition = 0;
1164 refresh = 1;
1165 } else
1166 paintCursor(tPtr);
1168 if (shifted)
1169 cancelSelection = 0;
1171 relay = False;
1173 break;
1175 case WM_EMACSKEY_END:
1176 if (!control_pressed)
1177 goto normal_key;
1178 else
1179 controled = False;
1181 #ifdef XK_KP_End
1182 case XK_KP_End:
1183 #endif
1184 case XK_End:
1185 if (!controled) {
1186 if (tPtr->cursorPosition < tPtr->textLen) {
1187 paintCursor(tPtr);
1188 tPtr->cursorPosition = tPtr->textLen;
1189 tPtr->viewPosition = 0;
1190 while (WMWidthOfString(tPtr->font,
1191 &(tPtr->text[tPtr->viewPosition]),
1192 tPtr->textLen-tPtr->viewPosition)
1193 > tPtr->usableWidth) {
1194 tPtr->viewPosition++;
1195 refresh = 1;
1197 if (!refresh)
1198 paintCursor(tPtr);
1200 if (shifted)
1201 cancelSelection = 0;
1203 relay = False;
1205 break;
1207 case WM_EMACSKEY_BS:
1208 if (!control_pressed)
1209 goto normal_key;
1210 else
1211 modified = False;
1213 case XK_BackSpace:
1214 if (!modified) {
1215 if (tPtr->selection.count) {
1216 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1217 data = (void*)WMDeleteTextEvent;
1218 textEvent = WMTextDidChangeNotification;
1219 } else if (tPtr->cursorPosition > 0) {
1220 WMRange range;
1221 range.position = tPtr->cursorPosition - 1;
1222 range.count = 1;
1223 WMDeleteTextFieldRange(tPtr, range);
1224 data = (void*)WMDeleteTextEvent;
1225 textEvent = WMTextDidChangeNotification;
1228 relay = False;
1230 break;
1232 case WM_EMACSKEY_DEL:
1233 if (!control_pressed)
1234 goto normal_key;
1235 else
1236 modified = False;
1238 #ifdef XK_KP_Delete
1239 case XK_KP_Delete:
1240 #endif
1241 case XK_Delete:
1242 if (!modified) {
1243 if (tPtr->selection.count) {
1244 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1245 data = (void*)WMDeleteTextEvent;
1246 textEvent = WMTextDidChangeNotification;
1247 } else if (tPtr->cursorPosition < tPtr->textLen) {
1248 WMRange range;
1249 range.position = tPtr->cursorPosition;
1250 range.count = 1;
1251 WMDeleteTextFieldRange(tPtr, range);
1252 data = (void*)WMDeleteTextEvent;
1253 textEvent = WMTextDidChangeNotification;
1256 relay = False;
1258 break;
1260 normal_key:
1261 default:
1262 if (!controled) {
1263 if (count > 0 && !iscntrl(buffer[0])) {
1264 if (tPtr->selection.count)
1265 WMDeleteTextFieldRange(tPtr, tPtr->selection);
1266 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
1267 data = (void*)WMInsertTextEvent;
1268 textEvent = WMTextDidChangeNotification;
1270 relay = False;
1273 break;
1276 if (relay) {
1277 WMRelayToNextResponder(W_VIEW(tPtr), event);
1278 return;
1281 /* Do not allow text selection in secure text fields */
1282 if (cancelSelection || tPtr->flags.secure) {
1283 lostSelection(tPtr->view, XA_PRIMARY, NULL);
1285 if (tPtr->selection.count) {
1286 tPtr->selection.count = 0;
1287 refresh = 1;
1289 tPtr->selection.position = tPtr->cursorPosition;
1290 } else {
1291 if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
1293 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
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 if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1339 tPtr->textLen - tPtr->viewPosition) < x)
1340 return tPtr->textLen;
1342 a = tPtr->viewPosition;
1343 b = tPtr->textLen;
1345 while (a < b && b-a>1) {
1346 mid = (a+b)/2;
1347 tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
1348 mid - tPtr->viewPosition);
1349 if (tw > x)
1350 b = mid;
1351 else if (tw < x)
1352 a = mid;
1353 else
1354 return mid;
1357 return (a+b)/2;
1362 static void
1363 pasteText(WMView *view, Atom selection, Atom target, Time timestamp,
1364 void *cdata, WMData *data)
1366 TextField *tPtr = (TextField*)view->self;
1367 char *str;
1369 tPtr->flags.waitingSelection = 0;
1371 if (data != NULL) {
1372 str = (char*)WMDataBytes(data);
1374 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1375 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1376 (void*)WMInsertTextEvent);
1377 } else {
1378 int n;
1380 str = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1382 if (str != NULL) {
1383 str[n] = 0;
1384 WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
1385 XFree(str);
1386 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1387 (void*)WMInsertTextEvent);
1393 static void
1394 handleTextFieldActionEvents(XEvent *event, void *data)
1396 TextField *tPtr = (TextField*)data;
1397 static int move = 0;
1398 static Time lastButtonReleasedEvent = 0;
1399 static Time lastButtonReleasedEvent2 = 0;
1400 Display *dpy = event->xany.display;
1402 CHECK_CLASS(data, WC_TextField);
1404 switch (event->type) {
1405 case KeyPress:
1406 if (tPtr->flags.waitingSelection) {
1407 return;
1409 if (tPtr->flags.enabled && tPtr->flags.focused) {
1410 handleTextFieldKeyPress(tPtr, event);
1411 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1412 W_VIEW(tPtr)->screen->invisibleCursor);
1413 tPtr->flags.pointerGrabbed = 1;
1415 break;
1417 case MotionNotify:
1419 if (tPtr->flags.pointerGrabbed) {
1420 tPtr->flags.pointerGrabbed = 0;
1421 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1422 W_VIEW(tPtr)->screen->textCursor);
1424 if (tPtr->flags.waitingSelection) {
1425 return;
1428 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
1430 if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
1431 tPtr->usableWidth) {
1432 if (WMWidthOfString(tPtr->font,
1433 &(tPtr->text[tPtr->viewPosition]),
1434 tPtr->cursorPosition-tPtr->viewPosition)
1435 > tPtr->usableWidth) {
1436 tPtr->viewPosition++;
1438 } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
1439 paintCursor(tPtr);
1440 tPtr->viewPosition--;
1443 tPtr->cursorPosition =
1444 pointToCursorPosition(tPtr, event->xmotion.x);
1446 /* Do not allow text selection in secure textfields */
1447 if (tPtr->flags.secure) {
1448 tPtr->selection.position = tPtr->cursorPosition;
1451 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
1453 paintCursor(tPtr);
1454 paintTextField(tPtr);
1457 break;
1459 case ButtonPress:
1460 if (tPtr->flags.pointerGrabbed) {
1461 tPtr->flags.pointerGrabbed = 0;
1462 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1463 W_VIEW(tPtr)->screen->textCursor);
1464 break;
1467 if (tPtr->flags.waitingSelection) {
1468 break;
1471 move = 1;
1472 switch (tPtr->flags.alignment) {
1473 int textWidth;
1474 case WARight:
1475 textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
1476 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1477 WMSetFocusToWidget(tPtr);
1479 if (tPtr->flags.focused) {
1480 tPtr->selection.position = tPtr->cursorPosition;
1481 tPtr->selection.count = 0;
1483 if (textWidth < tPtr->usableWidth) {
1484 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1485 event->xbutton.x - tPtr->usableWidth
1486 + textWidth);
1487 } else tPtr->cursorPosition = pointToCursorPosition(tPtr,
1488 event->xbutton.x);
1490 paintTextField(tPtr);
1491 break;
1493 case WALeft:
1494 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1495 WMSetFocusToWidget(tPtr);
1497 if (tPtr->flags.focused && event->xbutton.button == Button1) {
1498 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1499 event->xbutton.x);
1500 tPtr->selection.position = tPtr->cursorPosition;
1501 tPtr->selection.count = 0;
1502 paintTextField(tPtr);
1504 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1505 char *text;
1506 int n;
1508 if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
1509 event->xbutton.time,
1510 pasteText, NULL)) {
1511 text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
1513 if (text) {
1514 text[n] = 0;
1515 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1516 XFree(text);
1517 NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
1518 (void*)WMInsertTextEvent);
1520 } else {
1521 tPtr->flags.waitingSelection = 1;
1524 break;
1525 default:
1526 break;
1528 break;
1530 case ButtonRelease:
1531 if (tPtr->flags.pointerGrabbed) {
1532 tPtr->flags.pointerGrabbed = 0;
1533 XDefineCursor(dpy, W_VIEW(tPtr)->window,
1534 W_VIEW(tPtr)->screen->textCursor);
1536 if (tPtr->flags.waitingSelection) {
1537 break;
1540 if (!tPtr->flags.secure && tPtr->selection.count!=0) {
1541 int start, count;
1542 XRotateBuffers(dpy, 1);
1544 count = abs(tPtr->selection.count);
1545 if (tPtr->selection.count < 0)
1546 start = tPtr->selection.position - count;
1547 else
1548 start = tPtr->selection.position;
1550 XStoreBuffer(dpy, &tPtr->text[start], count, 0);
1553 move = 0;
1555 if (!tPtr->flags.secure &&
1556 event->xbutton.time - lastButtonReleasedEvent
1557 <= WINGsConfiguration.doubleClickDelay) {
1559 if (event->xbutton.time - lastButtonReleasedEvent2 <= 2*WINGsConfiguration.doubleClickDelay) {
1560 tPtr->selection.position = 0;
1561 tPtr->selection.count = tPtr->textLen;
1562 } else {
1563 int pos, cnt;
1564 char *txt;
1565 pos = tPtr->selection.position;
1566 cnt = tPtr->selection.count;
1567 txt = tPtr->text;
1568 while(pos >= 0) {
1569 if (txt[pos] == ' ' || txt[pos] == '\t') break;
1570 pos--;
1572 pos++;
1574 while(pos + cnt < tPtr->textLen) {
1575 if (txt[pos + cnt] == ' ' || txt[pos + cnt] == '\t')
1576 break;
1577 cnt++;
1579 tPtr->selection.position = pos;
1580 tPtr->selection.count = cnt;
1582 paintTextField(tPtr);
1584 if (!tPtr->flags.ownsSelection) {
1585 tPtr->flags.ownsSelection =
1586 WMCreateSelectionHandler(tPtr->view,
1587 XA_PRIMARY,
1588 event->xbutton.time,
1589 &selectionHandler, NULL);
1591 } else if (!tPtr->flags.secure && tPtr->selection.count!=0 &&
1592 !tPtr->flags.ownsSelection) {
1593 tPtr->flags.ownsSelection =
1594 WMCreateSelectionHandler(tPtr->view,
1595 XA_PRIMARY,
1596 event->xbutton.time,
1597 &selectionHandler, NULL);
1600 lastButtonReleasedEvent2 = lastButtonReleasedEvent;
1601 lastButtonReleasedEvent = event->xbutton.time;
1603 break;
1608 static void
1609 destroyTextField(TextField *tPtr)
1611 #if 0
1612 if (tPtr->timerID)
1613 WMDeleteTimerHandler(tPtr->timerID);
1614 #endif
1616 WMReleaseFont(tPtr->font);
1617 /*// use lostSelection() instead of WMDeleteSelectionHandler here?*/
1618 WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
1619 WMRemoveNotificationObserver(tPtr);
1621 if (tPtr->text)
1622 wfree(tPtr->text);
1624 wfree(tPtr);