Latest fixes for released 0.51.0
[wmaker-crm.git] / WINGs / wtextfield.c
blob235bad38bb46e3840a7ba06f04305a4d224343ac
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
16 char *WMTextDidChangeNotification = "WMTextDidChangeNotification";
17 char *WMTextDidBeginEditingNotification = "WMTextDidBeginEditingNotification";
18 char *WMTextDidEndEditingNotification = "WMTextDidEndEditingNotification";
21 typedef struct W_TextField {
22 W_Class widgetClass;
23 W_View *view;
25 struct W_TextField *nextField; /* next textfield in the chain */
26 struct W_TextField *prevField;
28 char *text;
29 int textLen; /* size of text */
30 int bufferSize; /* memory allocated for text */
32 int viewPosition; /* position of text being shown */
34 int cursorPosition; /* position of the insertion cursor */
36 short usableWidth;
37 short offsetWidth; /* offset of text from border */
39 WMRange selection;
40 WMRange prevselection;
42 #if 0
43 WMHandlerID timerID; /* for cursor blinking */
44 #endif
45 struct {
46 WMAlignment alignment:2;
48 unsigned int bordered:1;
50 unsigned int enabled:1;
52 unsigned int focused:1;
54 unsigned int cursorOn:1;
56 unsigned int secure:1; /* password entry style */
58 /**/
59 unsigned int notIllegalMovement:1;
60 } flags;
61 } TextField;
64 #define MIN_TEXT_BUFFER 2
65 #define TEXT_BUFFER_INCR 8
68 #define WM_EMACSKEYMASK ControlMask
70 #define WM_EMACSKEY_LEFT XK_b
71 #define WM_EMACSKEY_RIGHT XK_f
72 #define WM_EMACSKEY_HOME XK_a
73 #define WM_EMACSKEY_END XK_e
74 #define WM_EMACSKEY_BS XK_h
75 #define WM_EMACSKEY_DEL XK_d
79 #define DEFAULT_WIDTH 60
80 #define DEFAULT_HEIGHT 20
81 #define DEFAULT_BORDERED True
82 #define DEFAULT_ALIGNMENT WALeft
86 static void destroyTextField(TextField *tPtr);
87 static void paintTextField(TextField *tPtr);
89 static void handleEvents(XEvent *event, void *data);
90 static void handleTextFieldActionEvents(XEvent *event, void *data);
91 static void resizeTextField();
93 struct W_ViewProcedureTable _TextFieldViewProcedures = {
94 NULL,
95 resizeTextField,
96 NULL
100 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->view->screen->normalFont, \
101 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
103 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->view->screen->normalFont, \
104 &((tPtr)->text[(start)]), (end) - (start) + 1))
107 static void
108 memmv(char *dest, char *src, int size)
110 int i;
112 if (dest > src) {
113 for (i=size-1; i>=0; i--) {
114 dest[i] = src[i];
116 } else if (dest < src) {
117 for (i=0; i<size; i++) {
118 dest[i] = src[i];
124 static int
125 incrToFit(TextField *tPtr)
127 int vp = tPtr->viewPosition;
129 while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
130 tPtr->viewPosition++;
132 return vp!=tPtr->viewPosition;
136 static int
137 incrToFit2(TextField *tPtr)
139 int vp = tPtr->viewPosition;
140 while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
141 >= tPtr->usableWidth)
142 tPtr->viewPosition++;
145 return vp!=tPtr->viewPosition;
149 static void
150 decrToFit(TextField *tPtr)
152 while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->usableWidth
153 && tPtr->viewPosition>0)
154 tPtr->viewPosition--;
157 #undef TEXT_WIDTH
158 #undef TEXT_WIDTH2
161 WMTextField*
162 WMCreateTextField(WMWidget *parent)
164 TextField *tPtr;
167 tPtr = wmalloc(sizeof(TextField));
168 memset(tPtr, 0, sizeof(TextField));
170 tPtr->widgetClass = WC_TextField;
172 tPtr->view = W_CreateView(W_VIEW(parent));
173 if (!tPtr->view) {
174 free(tPtr);
175 return NULL;
177 tPtr->view->self = tPtr;
179 tPtr->view->attribFlags |= CWCursor;
180 tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
182 W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
184 tPtr->text = wmalloc(MIN_TEXT_BUFFER);
185 tPtr->text[0] = 0;
186 tPtr->textLen = 0;
187 tPtr->bufferSize = MIN_TEXT_BUFFER;
189 tPtr->flags.enabled = 1;
191 WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
192 |FocusChangeMask, handleEvents, tPtr);
194 W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
195 WMSetTextFieldBordered(tPtr, DEFAULT_BORDERED);
196 tPtr->flags.alignment = DEFAULT_ALIGNMENT;
197 tPtr->offsetWidth = (tPtr->view->size.height
198 - WMFontHeight(tPtr->view->screen->normalFont))/2;
200 WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
201 |ButtonPressMask|KeyPressMask|Button1MotionMask,
202 handleTextFieldActionEvents, tPtr);
204 tPtr->flags.cursorOn = 1;
206 return tPtr;
210 void
211 WMInsertTextFieldText(WMTextField *tPtr, char *text, int position)
213 int len;
215 CHECK_CLASS(tPtr, WC_TextField);
217 if (!text)
218 return;
220 len = strlen(text);
222 /* check if buffer will hold the text */
223 if (len + tPtr->textLen >= tPtr->bufferSize) {
224 tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
225 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
228 if (position < 0 || position >= tPtr->textLen) {
229 /* append the text at the end */
230 strcat(tPtr->text, text);
232 incrToFit(tPtr);
234 tPtr->textLen += len;
235 tPtr->cursorPosition += len;
236 } else {
237 /* insert text at position */
238 memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
239 tPtr->textLen-position+1);
241 memcpy(&(tPtr->text[position]), text, len);
243 tPtr->textLen += len;
244 if (position >= tPtr->cursorPosition) {
245 tPtr->cursorPosition += len;
246 incrToFit2(tPtr);
247 } else {
248 incrToFit(tPtr);
252 paintTextField(tPtr);
254 WMPostNotificationName(WMTextDidChangeNotification, tPtr,
255 (void*)WMInsertTextEvent);
259 static void
260 deleteTextFieldRange(WMTextField *tPtr, WMRange range)
262 CHECK_CLASS(tPtr, WC_TextField);
264 if (range.position >= tPtr->textLen)
265 return;
267 if (range.count < 1) {
268 if (range.position < 0)
269 range.position = 0;
270 tPtr->text[range.position] = 0;
271 tPtr->textLen = range.position;
273 tPtr->cursorPosition = 0;
274 tPtr->viewPosition = 0;
275 } else {
276 if (range.position + range.count > tPtr->textLen)
277 range.count = tPtr->textLen - range.position;
278 memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
279 tPtr->textLen - (range.position+range.count) + 1);
280 tPtr->textLen -= range.count;
282 if (tPtr->cursorPosition > range.position)
283 tPtr->cursorPosition -= range.count;
285 decrToFit(tPtr);
288 paintTextField(tPtr);
292 void
293 WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
295 deleteTextFieldRange(tPtr, range);
296 WMPostNotificationName(WMTextDidChangeNotification, tPtr,
297 (void*)WMDeleteTextEvent);
302 char*
303 WMGetTextFieldText(WMTextField *tPtr)
305 CHECK_CLASS(tPtr, WC_TextField);
307 return wstrdup(tPtr->text);
311 void
312 WMSetTextFieldText(WMTextField *tPtr, char *text)
314 /* We do not set text if it's the same. This will also help
315 * to avoid some infinite loops if this function is called from
316 * a function called by a notification observer. -Dan
318 if ((text && strcmp(tPtr->text, text) == 0) ||
319 (!text && tPtr->textLen == 0))
320 return;
322 if (text==NULL) {
323 tPtr->text[0] = 0;
324 tPtr->textLen = 0;
325 } else {
326 tPtr->textLen = strlen(text);
328 if (tPtr->textLen >= tPtr->bufferSize) {
329 tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
330 tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
332 strcpy(tPtr->text, text);
335 if (tPtr->textLen < tPtr->cursorPosition)
336 tPtr->cursorPosition = tPtr->textLen;
338 tPtr->cursorPosition = tPtr->textLen;
339 tPtr->viewPosition = 0;
340 tPtr->selection.count = 0;
342 if (tPtr->view->flags.realized)
343 paintTextField(tPtr);
345 WMPostNotificationName(WMTextDidChangeNotification, tPtr,
346 (void*)WMSetTextEvent);
350 void
351 WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
353 tPtr->flags.alignment = alignment;
354 if (alignment!=WALeft) {
355 wwarning("only left alignment is supported in textfields");
356 return;
359 if (tPtr->view->flags.realized) {
360 paintTextField(tPtr);
365 void
366 WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
368 tPtr->flags.bordered = bordered;
370 if (tPtr->view->flags.realized) {
371 paintTextField(tPtr);
377 void
378 WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
380 tPtr->flags.secure = flag;
382 if (tPtr->view->flags.realized) {
383 paintTextField(tPtr);
388 void
389 WMSetTextFieldEnabled(WMTextField *tPtr, Bool flag)
391 tPtr->flags.enabled = flag;
393 if (tPtr->view->flags.realized) {
394 paintTextField(tPtr);
399 void
400 WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
402 if (tPtr->flags.enabled) {
403 if (range.position < 0) {
404 range.count += range.position;
405 range.count = (range.count < 0) ? 0 : range.count;
406 range.position = 0;
407 } else if (range.position > tPtr->textLen) {
408 range.position = tPtr->textLen;
409 range.count = 0;
412 if (range.position + range.count > tPtr->textLen)
413 range.count = tPtr->textLen - range.position;
415 tPtr->prevselection = tPtr->selection; /* check if this is needed */
417 tPtr->selection = range;
419 if (tPtr->view->flags.realized) {
420 paintTextField(tPtr);
426 void
427 WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
429 if (tPtr->flags.enabled) {
430 if (position > tPtr->textLen)
431 position = tPtr->textLen;
433 tPtr->cursorPosition = position;
434 if (tPtr->view->flags.realized) {
435 paintTextField(tPtr);
441 static void
442 resizeTextField(WMTextField *tPtr, unsigned int width, unsigned int height)
444 W_ResizeView(tPtr->view, width, height);
446 tPtr->offsetWidth = (tPtr->view->size.height
447 - WMFontHeight(tPtr->view->screen->normalFont))/2;
449 tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth;
453 static void
454 paintCursor(TextField *tPtr)
456 int cx;
457 WMScreen *screen = tPtr->view->screen;
458 int textWidth;
460 cx = WMWidthOfString(screen->normalFont,
461 &(tPtr->text[tPtr->viewPosition]),
462 tPtr->cursorPosition-tPtr->viewPosition);
464 switch (tPtr->flags.alignment) {
465 case WARight:
466 textWidth = WMWidthOfString(screen->normalFont, tPtr->text,
467 tPtr->textLen);
468 if (textWidth < tPtr->usableWidth)
469 cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth;
470 else
471 cx += tPtr->offsetWidth;
472 break;
473 case WALeft:
474 cx += tPtr->offsetWidth;
475 break;
476 case WAJustified:
477 /* not supported */
478 case WACenter:
479 textWidth = WMWidthOfString(screen->normalFont, tPtr->text,
480 tPtr->textLen);
481 if (textWidth < tPtr->usableWidth)
482 cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
483 else
484 cx += tPtr->offsetWidth;
485 break;
488 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
489 cx, tPtr->offsetWidth, 1,
490 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
492 XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
493 cx, tPtr->offsetWidth, cx,
494 tPtr->view->size.height - tPtr->offsetWidth - 1);
499 static void
500 drawRelief(WMView *view)
502 WMScreen *scr = view->screen;
503 Display *dpy = scr->display;
504 GC wgc;
505 GC lgc;
506 GC dgc;
507 int width = view->size.width;
508 int height = view->size.height;
510 wgc = W_GC(scr->white);
511 dgc = W_GC(scr->darkGray);
512 lgc = W_GC(scr->gray);
514 /* top left */
515 XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
516 XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);
518 XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
519 XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);
521 /* bottom right */
522 XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
523 XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);
525 XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
526 XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
530 static void
531 paintTextField(TextField *tPtr)
533 W_Screen *screen = tPtr->view->screen;
534 W_View *view = tPtr->view;
535 int tx, ty, tw, th;
536 int rx;
537 int bd;
538 int totalWidth;
541 if (!view->flags.realized || !view->flags.mapped)
542 return;
544 if (!tPtr->flags.bordered) {
545 bd = 0;
546 } else {
547 bd = 2;
550 totalWidth = tPtr->view->size.width - 2*bd;
552 if (tPtr->textLen > 0) {
553 tw = WMWidthOfString(screen->normalFont,
554 &(tPtr->text[tPtr->viewPosition]),
555 tPtr->textLen - tPtr->viewPosition);
557 th = WMFontHeight(screen->normalFont);
559 ty = tPtr->offsetWidth;
560 switch (tPtr->flags.alignment) {
561 case WALeft:
562 tx = tPtr->offsetWidth;
563 if (tw < tPtr->usableWidth)
564 XClearArea(screen->display, view->window, bd+tw, bd,
565 totalWidth-tw, view->size.height-2*bd,
566 False);
567 break;
569 case WACenter:
570 tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
571 if (tw < tPtr->usableWidth)
572 XClearArea(screen->display, view->window, bd, bd,
573 totalWidth, view->size.height-2*bd, False);
574 break;
576 default:
577 case WARight:
578 tx = tPtr->offsetWidth + tPtr->usableWidth - tw;
579 if (tw < tPtr->usableWidth)
580 XClearArea(screen->display, view->window, bd, bd,
581 totalWidth-tw, view->size.height-2*bd, False);
582 break;
585 if (!tPtr->flags.secure) {
586 if (!tPtr->flags.enabled)
587 WMSetColorInGC(screen->darkGray, screen->textFieldGC);
589 WMDrawImageString(screen, view->window, screen->textFieldGC,
590 screen->normalFont, tx, ty,
591 &(tPtr->text[tPtr->viewPosition]),
592 tPtr->textLen - tPtr->viewPosition);
594 if (tPtr->selection.count) {
595 int count;
597 count = tPtr->selection.count < 0
598 ? tPtr->selection.position + tPtr->selection.count
599 : tPtr->selection.position;
601 rx = tx + WMWidthOfString(screen->normalFont,
602 &(tPtr->text[tPtr->viewPosition]),
603 count);
605 XSetBackground(screen->display, screen->textFieldGC,
606 screen->gray->color.pixel);
608 WMDrawImageString(screen, view->window, screen->textFieldGC,
609 screen->normalFont, rx, ty,
610 &(tPtr->text[count]),
611 abs(tPtr->selection.count));
613 XSetBackground(screen->display, screen->textFieldGC,
614 screen->white->color.pixel);
617 if (!tPtr->flags.enabled)
618 WMSetColorInGC(screen->black, screen->textFieldGC);
620 } else {
621 XClearArea(screen->display, view->window, bd, bd, totalWidth,
622 view->size.height - 2*bd, False);
625 /* draw cursor */
626 if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn
627 && !tPtr->flags.secure) {
628 paintCursor(tPtr);
631 /* draw relief */
632 if (tPtr->flags.bordered) {
633 drawRelief(view);
638 #if 0
639 static void
640 blinkCursor(void *data)
642 TextField *tPtr = (TextField*)data;
644 if (tPtr->flags.cursorOn) {
645 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
646 data);
647 } else {
648 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
649 data);
651 paintCursor(tPtr);
652 tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
654 #endif
657 static void
658 handleEvents(XEvent *event, void *data)
660 TextField *tPtr = (TextField*)data;
662 CHECK_CLASS(data, WC_TextField);
665 switch (event->type) {
666 case FocusIn:
667 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
668 return;
669 tPtr->flags.focused = 1;
670 #if 0
671 if (!tPtr->timerID) {
672 tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
673 blinkCursor, tPtr);
675 #endif
676 paintTextField(tPtr);
678 WMPostNotificationName(WMTextDidBeginEditingNotification, tPtr, NULL);
680 tPtr->flags.notIllegalMovement = 0;
681 break;
683 case FocusOut:
684 tPtr->flags.focused = 0;
685 #if 0
686 if (tPtr->timerID)
687 WMDeleteTimerHandler(tPtr->timerID);
688 tPtr->timerID = NULL;
689 #endif
691 paintTextField(tPtr);
692 if (!tPtr->flags.notIllegalMovement) {
693 WMPostNotificationName(WMTextDidEndEditingNotification, tPtr,
694 (void*)WMIllegalTextMovement);
696 break;
698 case Expose:
699 if (event->xexpose.count!=0)
700 break;
701 paintTextField(tPtr);
702 break;
704 case DestroyNotify:
705 destroyTextField(tPtr);
706 break;
711 static void
712 handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
714 char buffer[64];
715 KeySym ksym;
716 int count, refresh = 0;
717 int control_pressed = 0;
718 WMScreen *scr = tPtr->view->screen;
720 if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK) {
721 control_pressed = 1;
724 count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
725 buffer[count] = '\0';
727 if (!(event->xkey.state & ShiftMask)) {
728 if (tPtr->selection.count)
729 refresh = 1;
730 tPtr->prevselection = tPtr->selection;
731 tPtr->selection.position = tPtr->cursorPosition;
732 tPtr->selection.count = 0;
735 /* Be careful in any case in this switch statement, never to call
736 * to more than 2 functions at the same time, that can generate text
737 * change notifications. Only one text change notification should be sent
738 * in any case. Else hazardous things can happen.
739 * Maybe we need a better solution than the function wrapper to inform
740 * functions that change text in text fields, if they need to send a
741 * change notification or not. -Dan
743 switch (ksym) {
744 case XK_Tab:
745 if (event->xkey.state & ShiftMask) {
746 if (tPtr->view->prevFocusChain) {
747 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
748 tPtr->view->prevFocusChain);
749 tPtr->flags.notIllegalMovement = 1;
751 WMPostNotificationName(WMTextDidEndEditingNotification, tPtr,
752 (void*)WMBacktabTextMovement);
753 } else {
754 if (tPtr->view->nextFocusChain) {
755 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
756 tPtr->view->nextFocusChain);
757 tPtr->flags.notIllegalMovement = 1;
759 WMPostNotificationName(WMTextDidEndEditingNotification,
760 tPtr, (void*)WMTabTextMovement);
762 break;
764 case XK_Return:
765 WMPostNotificationName(WMTextDidEndEditingNotification, tPtr,
766 (void*)WMReturnTextMovement);
767 break;
769 case WM_EMACSKEY_LEFT:
770 if (!control_pressed) {
771 goto normal_key;
773 case XK_KP_Left:
774 case XK_Left:
775 if (tPtr->cursorPosition > 0) {
776 paintCursor(tPtr);
777 if (event->xkey.state & ControlMask) {
778 int i;
779 for (i = tPtr->cursorPosition - 1; i >= 0; i--)
780 if (tPtr->text[i] == ' ' || i == 0) {
781 tPtr->cursorPosition = i;
782 break;
784 } else {
785 tPtr->cursorPosition--;
787 if (tPtr->cursorPosition < tPtr->viewPosition) {
788 tPtr->viewPosition = tPtr->cursorPosition;
789 refresh = 1;
790 } else {
791 paintCursor(tPtr);
794 break;
796 case WM_EMACSKEY_RIGHT:
797 if (!control_pressed) {
798 goto normal_key;
800 case XK_KP_Right:
801 case XK_Right:
802 if (tPtr->cursorPosition < tPtr->textLen) {
803 paintCursor(tPtr);
804 if (event->xkey.state & ControlMask) {
805 int i;
806 for (i = tPtr->cursorPosition + 1; i <= tPtr->textLen; i++)
807 if (tPtr->text[i] == ' ' || i == tPtr->textLen) {
808 tPtr->cursorPosition = i;
809 break;
811 } else {
812 tPtr->cursorPosition++;
814 while (WMWidthOfString(scr->normalFont,
815 &(tPtr->text[tPtr->viewPosition]),
816 tPtr->cursorPosition-tPtr->viewPosition)
817 > tPtr->usableWidth) {
818 tPtr->viewPosition++;
819 refresh = 1;
821 if (!refresh)
822 paintCursor(tPtr);
824 break;
826 case WM_EMACSKEY_HOME:
827 if (!control_pressed) {
828 goto normal_key;
830 case XK_KP_Home:
831 case XK_Home:
832 if (tPtr->cursorPosition > 0) {
833 paintCursor(tPtr);
834 tPtr->cursorPosition = 0;
835 if (tPtr->viewPosition > 0) {
836 tPtr->viewPosition = 0;
837 refresh = 1;
838 } else {
839 paintCursor(tPtr);
842 break;
844 case WM_EMACSKEY_END:
845 if (!control_pressed) {
846 goto normal_key;
848 case XK_KP_End:
849 case XK_End:
850 if (tPtr->cursorPosition < tPtr->textLen) {
851 paintCursor(tPtr);
852 tPtr->cursorPosition = tPtr->textLen;
853 tPtr->viewPosition = 0;
854 while (WMWidthOfString(scr->normalFont,
855 &(tPtr->text[tPtr->viewPosition]),
856 tPtr->textLen-tPtr->viewPosition)
857 > tPtr->usableWidth) {
858 tPtr->viewPosition++;
859 refresh = 1;
861 if (!refresh)
862 paintCursor(tPtr);
864 break;
866 case WM_EMACSKEY_BS:
867 if (!control_pressed) {
868 goto normal_key;
870 case XK_BackSpace:
871 if (tPtr->cursorPosition > 0) {
872 WMRange range;
873 if (tPtr->prevselection.count) {
874 range.position = tPtr->prevselection.count < 0
875 ? tPtr->prevselection.position + tPtr->prevselection.count
876 : tPtr->prevselection.position;
878 range.count = abs(tPtr->prevselection.count);
879 } else {
880 range.position = tPtr->cursorPosition - 1;
881 range.count = 1;
883 WMDeleteTextFieldRange(tPtr, range);
885 break;
887 case WM_EMACSKEY_DEL:
888 if (!control_pressed) {
889 goto normal_key;
891 case XK_KP_Delete:
892 case XK_Delete:
893 if (tPtr->cursorPosition < tPtr->textLen || tPtr->prevselection.count) {
894 WMRange range;
895 if (tPtr->prevselection.count) {
896 range.position = tPtr->prevselection.count < 0
897 ? tPtr->prevselection.position + tPtr->prevselection.count
898 : tPtr->prevselection.position;
900 range.count = abs(tPtr->prevselection.count);
901 } else {
902 range.position = tPtr->cursorPosition;
903 range.count = 1;
905 WMDeleteTextFieldRange(tPtr, range);
907 break;
909 normal_key:
910 default:
911 if (count > 0 && !iscntrl(buffer[0])) {
912 WMRange range;
913 if (tPtr->prevselection.count) {
914 range.position = tPtr->prevselection.count < 0
915 ? tPtr->prevselection.position + tPtr->prevselection.count
916 : tPtr->prevselection.position;
918 range.count = abs(tPtr->prevselection.count);
919 } else {
920 range.position = tPtr->cursorPosition;
921 range.count = 1;
923 if (tPtr->prevselection.count)
924 deleteTextFieldRange(tPtr, range);
925 WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
926 } else {
927 return;
929 break;
931 if (event->xkey.state & ShiftMask) {
932 if (tPtr->selection.count == 0)
933 tPtr->selection.position = tPtr->cursorPosition;
934 tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
935 refresh = 1;
937 tPtr->prevselection.count = 0;
938 if (refresh) {
939 paintTextField(tPtr);
944 static int
945 pointToCursorPosition(TextField *tPtr, int x)
947 WMFont *font = tPtr->view->screen->normalFont;
948 int a, b, mid;
949 int tw;
951 if (tPtr->flags.bordered)
952 x -= 2;
954 a = tPtr->viewPosition;
955 b = tPtr->viewPosition + tPtr->textLen;
956 if (WMWidthOfString(font, &(tPtr->text[tPtr->viewPosition]),
957 tPtr->textLen-tPtr->viewPosition) < x)
958 return tPtr->textLen;
960 while (a < b && b-a>1) {
961 mid = (a+b)/2;
962 tw = WMWidthOfString(font, &(tPtr->text[tPtr->viewPosition]),
963 mid - tPtr->viewPosition);
964 if (tw > x)
965 b = mid;
966 else if (tw < x)
967 a = mid;
968 else
969 return mid;
971 return (a+b)/2;
975 static void
976 handleTextFieldActionEvents(XEvent *event, void *data)
978 TextField *tPtr = (TextField*)data;
980 CHECK_CLASS(data, WC_TextField);
982 switch (event->type) {
983 case KeyPress:
984 if (tPtr->flags.enabled)
985 handleTextFieldKeyPress(tPtr, event);
986 break;
988 case MotionNotify:
989 if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
991 if (!tPtr->selection.count) {
992 tPtr->selection.position = tPtr->cursorPosition;
995 tPtr->cursorPosition = pointToCursorPosition(tPtr,
996 event->xmotion.x);
998 tPtr->selection.count = tPtr->cursorPosition
999 - tPtr->selection.position;
1001 paintTextField(tPtr);
1003 break;
1005 case ButtonPress:
1006 if (tPtr->flags.enabled && !tPtr->flags.focused) {
1007 WMSetFocusToWidget(tPtr);
1008 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1009 event->xbutton.x);
1010 paintTextField(tPtr);
1011 } else if (tPtr->flags.focused) {
1012 tPtr->cursorPosition = pointToCursorPosition(tPtr,
1013 event->xbutton.x);
1014 tPtr->selection.count = 0;
1015 paintTextField(tPtr);
1017 if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
1018 char *text;
1020 text = W_GetTextSelection(tPtr->view->screen,
1021 tPtr->view->screen->clipboardAtom);
1022 if (!text) {
1023 text = W_GetTextSelection(tPtr->view->screen, XA_CUT_BUFFER0);
1025 if (text) {
1026 WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
1027 XFree(text);
1028 WMPostNotificationName(WMTextDidChangeNotification, tPtr,
1029 NULL);
1032 break;
1034 case ButtonRelease:
1036 break;
1041 static void
1042 destroyTextField(TextField *tPtr)
1044 #if 0
1045 if (tPtr->timerID)
1046 WMDeleteTimerHandler(tPtr->timerID);
1047 #endif
1049 if (tPtr->text)
1050 free(tPtr->text);
1052 free(tPtr);