7 #include <X11/keysym.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
{
25 struct W_TextField
*nextField
; /* next textfield in the chain */
26 struct W_TextField
*prevField
;
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 */
37 short offsetWidth
; /* offset of text from border */
40 WMHandlerID timerID
; /* for cursor blinking */
43 WMAlignment alignment
:2;
45 unsigned int bordered
:1;
47 unsigned int enabled
:1;
49 unsigned int focused
:1;
51 unsigned int cursorOn
:1;
53 unsigned int secure
:1; /* password entry style */
56 unsigned int notIllegalMovement
:1;
61 #define MIN_TEXT_BUFFER 2
62 #define TEXT_BUFFER_INCR 8
65 #define WM_EMACSKEYMASK ControlMask
67 #define WM_EMACSKEY_LEFT XK_b
68 #define WM_EMACSKEY_RIGHT XK_f
69 #define WM_EMACSKEY_HOME XK_a
70 #define WM_EMACSKEY_END XK_e
71 #define WM_EMACSKEY_BS XK_h
72 #define WM_EMACSKEY_DEL XK_d
76 #define DEFAULT_WIDTH 60
77 #define DEFAULT_HEIGHT 20
78 #define DEFAULT_BORDERED True
79 #define DEFAULT_ALIGNMENT WALeft
83 static void destroyTextField(TextField
*tPtr
);
84 static void paintTextField(TextField
*tPtr
);
86 static void handleEvents(XEvent
*event
, void *data
);
87 static void handleTextFieldActionEvents(XEvent
*event
, void *data
);
88 static void resizeTextField();
90 struct W_ViewProcedureTable _TextFieldViewProcedures
= {
97 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->view->screen->normalFont, \
98 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
100 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->view->screen->normalFont, \
101 &((tPtr)->text[(start)]), (end) - (start) + 1))
105 memmv(char *dest
, char *src
, int size
)
110 for (i
=size
-1; i
>=0; i
--) {
113 } else if (dest
< src
) {
114 for (i
=0; i
<size
; i
++) {
122 incrToFit(TextField
*tPtr
)
124 int vp
= tPtr
->viewPosition
;
126 while (TEXT_WIDTH(tPtr
, tPtr
->viewPosition
) > tPtr
->usableWidth
) {
127 tPtr
->viewPosition
++;
129 return vp
!=tPtr
->viewPosition
;
133 incrToFit2(TextField
*tPtr
)
135 int vp
= tPtr
->viewPosition
;
136 while (TEXT_WIDTH2(tPtr
, tPtr
->viewPosition
, tPtr
->cursorPosition
)
137 >= tPtr
->usableWidth
)
138 tPtr
->viewPosition
++;
141 return vp
!=tPtr
->viewPosition
;
146 decrToFit(TextField
*tPtr
)
148 while (TEXT_WIDTH(tPtr
, tPtr
->viewPosition
-1) < tPtr
->usableWidth
149 && tPtr
->viewPosition
>0)
150 tPtr
->viewPosition
--;
158 WMCreateTextField(WMWidget
*parent
)
163 tPtr
= wmalloc(sizeof(TextField
));
164 memset(tPtr
, 0, sizeof(TextField
));
166 tPtr
->widgetClass
= WC_TextField
;
168 tPtr
->view
= W_CreateView(W_VIEW(parent
));
173 tPtr
->view
->self
= tPtr
;
175 tPtr
->view
->attribFlags
|= CWCursor
;
176 tPtr
->view
->attribs
.cursor
= tPtr
->view
->screen
->textCursor
;
178 W_SetViewBackgroundColor(tPtr
->view
, tPtr
->view
->screen
->white
);
180 tPtr
->text
= wmalloc(MIN_TEXT_BUFFER
);
183 tPtr
->bufferSize
= MIN_TEXT_BUFFER
;
185 tPtr
->flags
.enabled
= 1;
187 WMCreateEventHandler(tPtr
->view
, ExposureMask
|StructureNotifyMask
188 |FocusChangeMask
, handleEvents
, tPtr
);
190 W_ResizeView(tPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
191 WMSetTextFieldBordered(tPtr
, DEFAULT_BORDERED
);
192 tPtr
->flags
.alignment
= DEFAULT_ALIGNMENT
;
193 tPtr
->offsetWidth
= (tPtr
->view
->size
.height
194 - tPtr
->view
->screen
->normalFont
->height
)/2;
196 WMCreateEventHandler(tPtr
->view
, EnterWindowMask
|LeaveWindowMask
197 |ButtonPressMask
|KeyPressMask
|Button1MotionMask
,
198 handleTextFieldActionEvents
, tPtr
);
200 tPtr
->flags
.cursorOn
= 1;
207 WMInsertTextFieldText(WMTextField
*tPtr
, char *text
, int position
)
211 CHECK_CLASS(tPtr
, WC_TextField
);
218 /* check if buffer will hold the text */
219 if (len
+ tPtr
->textLen
>= tPtr
->bufferSize
) {
220 tPtr
->bufferSize
= tPtr
->textLen
+ len
+ TEXT_BUFFER_INCR
;
221 tPtr
->text
= realloc(tPtr
->text
, tPtr
->bufferSize
);
224 if (position
< 0 || position
>= tPtr
->textLen
) {
225 /* append the text at the end */
226 strcat(tPtr
->text
, text
);
230 tPtr
->textLen
+= len
;
231 tPtr
->cursorPosition
+= len
;
233 /* insert text at position */
234 memmv(&(tPtr
->text
[position
+len
]), &(tPtr
->text
[position
]),
235 tPtr
->textLen
-position
+1);
237 memcpy(&(tPtr
->text
[position
]), text
, len
);
239 tPtr
->textLen
+= len
;
240 if (position
>= tPtr
->cursorPosition
) {
241 tPtr
->cursorPosition
+= len
;
248 paintTextField(tPtr
);
253 WMDeleteTextFieldRange(WMTextField
*tPtr
, WMRange range
)
255 CHECK_CLASS(tPtr
, WC_TextField
);
257 if (range
.position
>= tPtr
->textLen
)
260 if (range
.count
< 1) {
261 if (range
.position
< 0)
263 tPtr
->text
[range
.position
] = 0;
264 tPtr
->textLen
= range
.position
;
266 tPtr
->cursorPosition
= 0;
267 tPtr
->viewPosition
= 0;
269 if (range
.position
+ range
.count
> tPtr
->textLen
)
270 range
.count
= tPtr
->textLen
- range
.position
;
271 memmv(&(tPtr
->text
[range
.position
]), &(tPtr
->text
[range
.position
+range
.count
]),
272 tPtr
->textLen
- (range
.position
+range
.count
) + 1);
273 tPtr
->textLen
-= range
.count
;
275 if (tPtr
->cursorPosition
> range
.position
)
276 tPtr
->cursorPosition
-= range
.count
;
281 paintTextField(tPtr
);
287 WMGetTextFieldText(WMTextField
*tPtr
)
289 CHECK_CLASS(tPtr
, WC_TextField
);
291 return wstrdup(tPtr
->text
);
296 WMSetTextFieldText(WMTextField
*tPtr
, char *text
)
302 tPtr
->textLen
= strlen(text
);
304 if (tPtr
->textLen
>= tPtr
->bufferSize
) {
305 tPtr
->bufferSize
= tPtr
->textLen
+ TEXT_BUFFER_INCR
;
306 tPtr
->text
= realloc(tPtr
->text
, tPtr
->bufferSize
);
308 strcpy(tPtr
->text
, text
);
310 if (tPtr
->textLen
< tPtr
->cursorPosition
)
311 tPtr
->cursorPosition
= tPtr
->textLen
;
313 if (tPtr
->view
->flags
.realized
)
314 paintTextField(tPtr
);
319 WMSetTextFieldAlignment(WMTextField
*tPtr
, WMAlignment alignment
)
321 tPtr
->flags
.alignment
= alignment
;
322 if (alignment
!=WALeft
) {
323 wwarning("only left alignment is supported in textfields");
327 if (tPtr
->view
->flags
.realized
) {
328 paintTextField(tPtr
);
334 WMSetTextFieldBordered(WMTextField
*tPtr
, Bool bordered
)
336 tPtr
->flags
.bordered
= bordered
;
338 if (tPtr
->view
->flags
.realized
) {
339 paintTextField(tPtr
);
346 WMSetTextFieldSecure(WMTextField
*tPtr
, Bool flag
)
348 tPtr
->flags
.secure
= flag
;
350 if (tPtr
->view
->flags
.realized
) {
351 paintTextField(tPtr
);
357 WMSetTextFieldEnabled(WMTextField
*tPtr
, Bool flag
)
359 tPtr
->flags
.enabled
= flag
;
361 if (tPtr
->view
->flags
.realized
) {
362 paintTextField(tPtr
);
368 resizeTextField(WMTextField
*tPtr
, unsigned int width
, unsigned int height
)
370 W_ResizeView(tPtr
->view
, width
, height
);
372 tPtr
->offsetWidth
= (tPtr
->view
->size
.height
373 - tPtr
->view
->screen
->normalFont
->height
)/2;
375 tPtr
->usableWidth
= tPtr
->view
->size
.width
- 2*tPtr
->offsetWidth
;
380 paintCursor(TextField
*tPtr
)
383 WMScreen
*screen
= tPtr
->view
->screen
;
386 cx
= WMWidthOfString(screen
->normalFont
,
387 &(tPtr
->text
[tPtr
->viewPosition
]),
388 tPtr
->cursorPosition
-tPtr
->viewPosition
);
390 switch (tPtr
->flags
.alignment
) {
392 textWidth
= WMWidthOfString(screen
->normalFont
, tPtr
->text
,
394 if (textWidth
< tPtr
->usableWidth
)
395 cx
+= tPtr
->offsetWidth
+ tPtr
->usableWidth
- textWidth
;
397 cx
+= tPtr
->offsetWidth
;
400 cx
+= tPtr
->offsetWidth
;
403 textWidth
= WMWidthOfString(screen
->normalFont
, tPtr
->text
,
405 if (textWidth
< tPtr
->usableWidth
)
406 cx
+= tPtr
->offsetWidth
+ (tPtr
->usableWidth
-textWidth
)/2;
408 cx
+= tPtr
->offsetWidth
;
412 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
413 cx, tPtr->offsetWidth, 1,
414 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
416 XDrawLine(screen
->display
, tPtr
->view
->window
, screen
->xorGC
,
417 cx
, tPtr
->offsetWidth
, cx
,
418 tPtr
->view
->size
.height
- tPtr
->offsetWidth
- 1);
424 drawRelief(WMView
*view
)
426 WMScreen
*scr
= view
->screen
;
427 Display
*dpy
= scr
->display
;
431 int width
= view
->size
.width
;
432 int height
= view
->size
.height
;
434 wgc
= W_GC(scr
->white
);
435 dgc
= W_GC(scr
->darkGray
);
436 lgc
= W_GC(scr
->gray
);
439 XDrawLine(dpy
, view
->window
, dgc
, 0, 0, width
-1, 0);
440 XDrawLine(dpy
, view
->window
, dgc
, 0, 1, width
-2, 1);
442 XDrawLine(dpy
, view
->window
, dgc
, 0, 0, 0, height
-2);
443 XDrawLine(dpy
, view
->window
, dgc
, 1, 0, 1, height
-3);
446 XDrawLine(dpy
, view
->window
, wgc
, 0, height
-1, width
-1, height
-1);
447 XDrawLine(dpy
, view
->window
, lgc
, 1, height
-2, width
-2, height
-2);
449 XDrawLine(dpy
, view
->window
, wgc
, width
-1, 0, width
-1, height
-1);
450 XDrawLine(dpy
, view
->window
, lgc
, width
-2, 1, width
-2, height
-3);
455 paintTextField(TextField
*tPtr
)
457 W_Screen
*screen
= tPtr
->view
->screen
;
458 W_View
*view
= tPtr
->view
;
464 if (!view
->flags
.realized
|| !view
->flags
.mapped
)
467 if (!tPtr
->flags
.bordered
) {
473 totalWidth
= tPtr
->view
->size
.width
- 2*bd
;
475 if (tPtr
->textLen
> 0) {
476 tw
= WMWidthOfString(screen
->normalFont
,
477 &(tPtr
->text
[tPtr
->viewPosition
]),
478 tPtr
->textLen
- tPtr
->viewPosition
);
480 th
= screen
->normalFont
->height
;
482 ty
= tPtr
->offsetWidth
;
483 switch (tPtr
->flags
.alignment
) {
485 tx
= tPtr
->offsetWidth
;
486 if (tw
< tPtr
->usableWidth
)
487 XClearArea(screen
->display
, view
->window
, bd
+tw
, bd
,
488 totalWidth
-tw
, view
->size
.height
-2*bd
,
493 tx
= tPtr
->offsetWidth
+ (tPtr
->usableWidth
- tw
) / 2;
494 if (tw
< tPtr
->usableWidth
)
495 XClearArea(screen
->display
, view
->window
, bd
, bd
,
496 totalWidth
, view
->size
.height
-2*bd
, False
);
501 tx
= tPtr
->offsetWidth
+ tPtr
->usableWidth
- tw
;
502 if (tw
< tPtr
->usableWidth
)
503 XClearArea(screen
->display
, view
->window
, bd
, bd
,
504 totalWidth
-tw
, view
->size
.height
-2*bd
, False
);
508 if (!tPtr
->flags
.secure
) {
509 if (!tPtr
->flags
.enabled
)
510 WMSetColorInGC(screen
->darkGray
, screen
->textFieldGC
);
512 WMDrawImageString(screen
, view
->window
, screen
->textFieldGC
,
513 screen
->normalFont
, tx
, ty
,
514 &(tPtr
->text
[tPtr
->viewPosition
]),
515 tPtr
->textLen
- tPtr
->viewPosition
);
517 if (!tPtr
->flags
.enabled
)
518 WMSetColorInGC(screen
->black
, screen
->textFieldGC
);
521 XClearArea(screen
->display
, view
->window
, bd
, bd
, totalWidth
,
522 view
->size
.height
- 2*bd
, False
);
526 if (tPtr
->flags
.focused
&& tPtr
->flags
.enabled
&& tPtr
->flags
.cursorOn
) {
531 if (tPtr
->flags
.bordered
) {
539 blinkCursor(void *data
)
541 TextField
*tPtr
= (TextField
*)data
;
543 if (tPtr
->flags
.cursorOn
) {
544 tPtr
->timerID
= WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY
, blinkCursor
,
547 tPtr
->timerID
= WMAddTimerHandler(CURSOR_BLINK_ON_DELAY
, blinkCursor
,
551 tPtr
->flags
.cursorOn
= !tPtr
->flags
.cursorOn
;
556 handleEvents(XEvent
*event
, void *data
)
558 TextField
*tPtr
= (TextField
*)data
;
560 CHECK_CLASS(data
, WC_TextField
);
563 switch (event
->type
) {
565 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr
->view
))!=tPtr
->view
)
567 tPtr
->flags
.focused
= 1;
569 if (!tPtr
->timerID
) {
570 tPtr
->timerID
= WMAddTimerHandler(CURSOR_BLINK_ON_DELAY
,
574 paintTextField(tPtr
);
576 WMPostNotificationName(WMTextDidBeginEditingNotification
, tPtr
, NULL
);
578 tPtr
->flags
.notIllegalMovement
= 0;
582 tPtr
->flags
.focused
= 0;
585 WMDeleteTimerHandler(tPtr
->timerID
);
586 tPtr
->timerID
= NULL
;
589 paintTextField(tPtr
);
590 if (!tPtr
->flags
.notIllegalMovement
) {
591 WMPostNotificationName(WMTextDidEndEditingNotification
, tPtr
,
592 (void*)WMIllegalTextMovement
);
597 if (event
->xexpose
.count
!=0)
599 paintTextField(tPtr
);
603 destroyTextField(tPtr
);
610 handleTextFieldKeyPress(TextField
*tPtr
, XEvent
*event
)
614 int count
, refresh
= 0;
615 int control_pressed
= 0;
617 WMScreen
*scr
= tPtr
->view
->screen
;
621 if (((XKeyEvent
*) event
)->state
& WM_EMACSKEYMASK
) {
625 count
= XLookupString(&event
->xkey
, buffer
, 63, &ksym
, NULL
);
626 buffer
[count
] = '\0';
630 if (event
->xkey
.state
& ShiftMask
) {
631 if (tPtr
->view
->prevFocusChain
) {
632 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr
->view
),
633 tPtr
->view
->prevFocusChain
);
634 tPtr
->flags
.notIllegalMovement
= 1;
636 WMPostNotificationName(WMTextDidEndEditingNotification
, tPtr
,
637 (void*)WMBacktabTextMovement
);
639 if (tPtr
->view
->nextFocusChain
) {
640 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr
->view
),
641 tPtr
->view
->nextFocusChain
);
642 tPtr
->flags
.notIllegalMovement
= 1;
644 WMPostNotificationName(WMTextDidEndEditingNotification
,
645 tPtr
, (void*)WMTabTextMovement
);
650 WMPostNotificationName(WMTextDidEndEditingNotification
, tPtr
,
651 (void*)WMReturnTextMovement
);
654 case WM_EMACSKEY_LEFT
:
655 if (!control_pressed
) {
660 if (tPtr
->cursorPosition
> 0) {
662 tPtr
->cursorPosition
--;
663 if (tPtr
->cursorPosition
< tPtr
->viewPosition
) {
664 tPtr
->viewPosition
= tPtr
->cursorPosition
;
672 case WM_EMACSKEY_RIGHT
:
673 if (!control_pressed
) {
678 if (tPtr
->cursorPosition
< tPtr
->textLen
) {
680 tPtr
->cursorPosition
++;
681 while (WMWidthOfString(scr
->normalFont
,
682 &(tPtr
->text
[tPtr
->viewPosition
]),
683 tPtr
->cursorPosition
-tPtr
->viewPosition
)
684 > tPtr
->usableWidth
) {
685 tPtr
->viewPosition
++;
693 case WM_EMACSKEY_HOME
:
694 if (!control_pressed
) {
699 if (tPtr
->cursorPosition
> 0) {
701 tPtr
->cursorPosition
= 0;
702 if (tPtr
->viewPosition
> 0) {
703 tPtr
->viewPosition
= 0;
711 case WM_EMACSKEY_END
:
712 if (!control_pressed
) {
717 if (tPtr
->cursorPosition
< tPtr
->textLen
) {
719 tPtr
->cursorPosition
= tPtr
->textLen
;
720 tPtr
->viewPosition
= 0;
721 while (WMWidthOfString(scr
->normalFont
,
722 &(tPtr
->text
[tPtr
->viewPosition
]),
723 tPtr
->textLen
-tPtr
->viewPosition
)
724 >= tPtr
->usableWidth
) {
725 tPtr
->viewPosition
++;
734 if (!control_pressed
) {
738 if (tPtr
->cursorPosition
> 0) {
741 range
.position
= tPtr
->cursorPosition
-1;
743 WMDeleteTextFieldRange(tPtr
, range
);
747 case WM_EMACSKEY_DEL
:
748 if (!control_pressed
) {
753 if (tPtr
->cursorPosition
< tPtr
->textLen
) {
756 range
.position
= tPtr
->cursorPosition
;
758 WMDeleteTextFieldRange(tPtr
, range
);
764 if (count
> 0 && !iscntrl(buffer
[0])) {
766 WMInsertTextFieldText(tPtr
, buffer
, tPtr
->cursorPosition
);
770 paintTextField(tPtr
);
774 WMPostNotificationName(WMTextDidChangeNotification
, tPtr
, NULL
);
780 pointToCursorPosition(TextField
*tPtr
, int x
)
782 WMFont
*font
= tPtr
->view
->screen
->normalFont
;
786 if (tPtr
->flags
.bordered
)
789 a
= tPtr
->viewPosition
;
790 b
= tPtr
->viewPosition
+ tPtr
->textLen
;
791 if (WMWidthOfString(font
, &(tPtr
->text
[tPtr
->viewPosition
]),
792 tPtr
->textLen
-tPtr
->viewPosition
) < x
)
793 return tPtr
->textLen
;
795 while (a
< b
&& b
-a
>1) {
797 tw
= WMWidthOfString(font
, &(tPtr
->text
[tPtr
->viewPosition
]),
798 mid
- tPtr
->viewPosition
);
811 handleTextFieldActionEvents(XEvent
*event
, void *data
)
813 TextField
*tPtr
= (TextField
*)data
;
815 CHECK_CLASS(data
, WC_TextField
);
817 switch (event
->type
) {
819 if (tPtr
->flags
.enabled
)
820 handleTextFieldKeyPress(tPtr
, event
);
824 if (tPtr
->flags
.enabled
&& (event
->xmotion
.state
& Button1Mask
)) {
825 tPtr
->cursorPosition
= pointToCursorPosition(tPtr
,
827 paintTextField(tPtr
);
832 if (tPtr
->flags
.enabled
&& !tPtr
->flags
.focused
) {
833 WMSetFocusToWidget(tPtr
);
834 tPtr
->cursorPosition
= pointToCursorPosition(tPtr
,
836 paintTextField(tPtr
);
837 } else if (tPtr
->flags
.focused
) {
838 tPtr
->cursorPosition
= pointToCursorPosition(tPtr
,
840 paintTextField(tPtr
);
842 if (event
->xbutton
.button
== Button2
&& tPtr
->flags
.enabled
) {
845 text
= W_GetTextSelection(tPtr
->view
->screen
, XA_PRIMARY
);
847 text
= W_GetTextSelection(tPtr
->view
->screen
, XA_CUT_BUFFER0
);
850 WMInsertTextFieldText(tPtr
, text
, tPtr
->cursorPosition
);
852 WMPostNotificationName(WMTextDidChangeNotification
, tPtr
,
866 destroyTextField(TextField
*tPtr
)
870 WMDeleteTimerHandler(tPtr
->timerID
);