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 */
42 WMHandlerID timerID
; /* for cursor blinking */
45 WMAlignment alignment
:2;
47 unsigned int bordered
:1;
49 unsigned int enabled
:1;
51 unsigned int focused
:1;
53 unsigned int cursorOn
:1;
55 unsigned int secure
:1; /* password entry style */
58 unsigned int notIllegalMovement
:1;
63 #define MIN_TEXT_BUFFER 2
64 #define TEXT_BUFFER_INCR 8
67 #define WM_EMACSKEYMASK ControlMask
69 #define WM_EMACSKEY_LEFT XK_b
70 #define WM_EMACSKEY_RIGHT XK_f
71 #define WM_EMACSKEY_HOME XK_a
72 #define WM_EMACSKEY_END XK_e
73 #define WM_EMACSKEY_BS XK_h
74 #define WM_EMACSKEY_DEL XK_d
78 #define DEFAULT_WIDTH 60
79 #define DEFAULT_HEIGHT 20
80 #define DEFAULT_BORDERED True
81 #define DEFAULT_ALIGNMENT WALeft
85 static void destroyTextField(TextField
*tPtr
);
86 static void paintTextField(TextField
*tPtr
);
88 static void handleEvents(XEvent
*event
, void *data
);
89 static void handleTextFieldActionEvents(XEvent
*event
, void *data
);
90 static void resizeTextField();
92 struct W_ViewProcedureTable _TextFieldViewProcedures
= {
99 #define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->view->screen->normalFont, \
100 &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))
102 #define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->view->screen->normalFont, \
103 &((tPtr)->text[(start)]), (end) - (start) + 1))
107 memmv(char *dest
, char *src
, int size
)
112 for (i
=size
-1; i
>=0; i
--) {
115 } else if (dest
< src
) {
116 for (i
=0; i
<size
; i
++) {
124 incrToFit(TextField
*tPtr
)
126 int vp
= tPtr
->viewPosition
;
128 while (TEXT_WIDTH(tPtr
, tPtr
->viewPosition
) > tPtr
->usableWidth
) {
129 tPtr
->viewPosition
++;
131 return vp
!=tPtr
->viewPosition
;
135 incrToFit2(TextField
*tPtr
)
137 int vp
= tPtr
->viewPosition
;
138 while (TEXT_WIDTH2(tPtr
, tPtr
->viewPosition
, tPtr
->cursorPosition
)
139 >= tPtr
->usableWidth
)
140 tPtr
->viewPosition
++;
143 return vp
!=tPtr
->viewPosition
;
148 decrToFit(TextField
*tPtr
)
150 while (TEXT_WIDTH(tPtr
, tPtr
->viewPosition
-1) < tPtr
->usableWidth
151 && tPtr
->viewPosition
>0)
152 tPtr
->viewPosition
--;
160 WMCreateTextField(WMWidget
*parent
)
165 tPtr
= wmalloc(sizeof(TextField
));
166 memset(tPtr
, 0, sizeof(TextField
));
168 tPtr
->widgetClass
= WC_TextField
;
170 tPtr
->view
= W_CreateView(W_VIEW(parent
));
175 tPtr
->view
->self
= tPtr
;
177 tPtr
->view
->attribFlags
|= CWCursor
;
178 tPtr
->view
->attribs
.cursor
= tPtr
->view
->screen
->textCursor
;
180 W_SetViewBackgroundColor(tPtr
->view
, tPtr
->view
->screen
->white
);
182 tPtr
->text
= wmalloc(MIN_TEXT_BUFFER
);
185 tPtr
->bufferSize
= MIN_TEXT_BUFFER
;
187 tPtr
->flags
.enabled
= 1;
189 WMCreateEventHandler(tPtr
->view
, ExposureMask
|StructureNotifyMask
190 |FocusChangeMask
, handleEvents
, tPtr
);
192 W_ResizeView(tPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
193 WMSetTextFieldBordered(tPtr
, DEFAULT_BORDERED
);
194 tPtr
->flags
.alignment
= DEFAULT_ALIGNMENT
;
195 tPtr
->offsetWidth
= (tPtr
->view
->size
.height
196 - WMFontHeight(tPtr
->view
->screen
->normalFont
))/2;
198 WMCreateEventHandler(tPtr
->view
, EnterWindowMask
|LeaveWindowMask
199 |ButtonPressMask
|KeyPressMask
|Button1MotionMask
,
200 handleTextFieldActionEvents
, tPtr
);
202 tPtr
->flags
.cursorOn
= 1;
209 WMInsertTextFieldText(WMTextField
*tPtr
, char *text
, int position
)
213 CHECK_CLASS(tPtr
, WC_TextField
);
220 /* check if buffer will hold the text */
221 if (len
+ tPtr
->textLen
>= tPtr
->bufferSize
) {
222 tPtr
->bufferSize
= tPtr
->textLen
+ len
+ TEXT_BUFFER_INCR
;
223 tPtr
->text
= realloc(tPtr
->text
, tPtr
->bufferSize
);
226 if (position
< 0 || position
>= tPtr
->textLen
) {
227 /* append the text at the end */
228 strcat(tPtr
->text
, text
);
232 tPtr
->textLen
+= len
;
233 tPtr
->cursorPosition
+= len
;
235 /* insert text at position */
236 memmv(&(tPtr
->text
[position
+len
]), &(tPtr
->text
[position
]),
237 tPtr
->textLen
-position
+1);
239 memcpy(&(tPtr
->text
[position
]), text
, len
);
241 tPtr
->textLen
+= len
;
242 if (position
>= tPtr
->cursorPosition
) {
243 tPtr
->cursorPosition
+= len
;
250 paintTextField(tPtr
);
255 WMDeleteTextFieldRange(WMTextField
*tPtr
, WMRange range
)
257 CHECK_CLASS(tPtr
, WC_TextField
);
259 if (range
.position
>= tPtr
->textLen
)
262 if (range
.count
< 1) {
263 if (range
.position
< 0)
265 tPtr
->text
[range
.position
] = 0;
266 tPtr
->textLen
= range
.position
;
268 tPtr
->cursorPosition
= 0;
269 tPtr
->viewPosition
= 0;
271 if (range
.position
+ range
.count
> tPtr
->textLen
)
272 range
.count
= tPtr
->textLen
- range
.position
;
273 memmv(&(tPtr
->text
[range
.position
]), &(tPtr
->text
[range
.position
+range
.count
]),
274 tPtr
->textLen
- (range
.position
+range
.count
) + 1);
275 tPtr
->textLen
-= range
.count
;
277 if (tPtr
->cursorPosition
> range
.position
)
278 tPtr
->cursorPosition
-= range
.count
;
283 paintTextField(tPtr
);
289 WMGetTextFieldText(WMTextField
*tPtr
)
291 CHECK_CLASS(tPtr
, WC_TextField
);
293 return wstrdup(tPtr
->text
);
298 WMSetTextFieldText(WMTextField
*tPtr
, char *text
)
304 tPtr
->textLen
= strlen(text
);
306 if (tPtr
->textLen
>= tPtr
->bufferSize
) {
307 tPtr
->bufferSize
= tPtr
->textLen
+ TEXT_BUFFER_INCR
;
308 tPtr
->text
= realloc(tPtr
->text
, tPtr
->bufferSize
);
310 strcpy(tPtr
->text
, text
);
313 if (tPtr->textLen < tPtr->cursorPosition)
314 tPtr->cursorPosition = tPtr->textLen;
316 tPtr
->cursorPosition
= 0;
317 tPtr
->viewPosition
= 0;
319 if (tPtr
->view
->flags
.realized
)
320 paintTextField(tPtr
);
325 WMSetTextFieldAlignment(WMTextField
*tPtr
, WMAlignment alignment
)
327 tPtr
->flags
.alignment
= alignment
;
328 if (alignment
!=WALeft
) {
329 wwarning("only left alignment is supported in textfields");
333 if (tPtr
->view
->flags
.realized
) {
334 paintTextField(tPtr
);
340 WMSetTextFieldBordered(WMTextField
*tPtr
, Bool bordered
)
342 tPtr
->flags
.bordered
= bordered
;
344 if (tPtr
->view
->flags
.realized
) {
345 paintTextField(tPtr
);
352 WMSetTextFieldSecure(WMTextField
*tPtr
, Bool flag
)
354 tPtr
->flags
.secure
= flag
;
356 if (tPtr
->view
->flags
.realized
) {
357 paintTextField(tPtr
);
363 WMSetTextFieldEnabled(WMTextField
*tPtr
, Bool flag
)
365 tPtr
->flags
.enabled
= flag
;
367 if (tPtr
->view
->flags
.realized
) {
368 paintTextField(tPtr
);
374 resizeTextField(WMTextField
*tPtr
, unsigned int width
, unsigned int height
)
376 W_ResizeView(tPtr
->view
, width
, height
);
378 tPtr
->offsetWidth
= (tPtr
->view
->size
.height
379 - WMFontHeight(tPtr
->view
->screen
->normalFont
))/2;
381 tPtr
->usableWidth
= tPtr
->view
->size
.width
- 2*tPtr
->offsetWidth
;
386 paintCursor(TextField
*tPtr
)
389 WMScreen
*screen
= tPtr
->view
->screen
;
392 cx
= WMWidthOfString(screen
->normalFont
,
393 &(tPtr
->text
[tPtr
->viewPosition
]),
394 tPtr
->cursorPosition
-tPtr
->viewPosition
);
396 switch (tPtr
->flags
.alignment
) {
398 textWidth
= WMWidthOfString(screen
->normalFont
, tPtr
->text
,
400 if (textWidth
< tPtr
->usableWidth
)
401 cx
+= tPtr
->offsetWidth
+ tPtr
->usableWidth
- textWidth
;
403 cx
+= tPtr
->offsetWidth
;
406 cx
+= tPtr
->offsetWidth
;
409 textWidth
= WMWidthOfString(screen
->normalFont
, tPtr
->text
,
411 if (textWidth
< tPtr
->usableWidth
)
412 cx
+= tPtr
->offsetWidth
+ (tPtr
->usableWidth
-textWidth
)/2;
414 cx
+= tPtr
->offsetWidth
;
418 XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
419 cx, tPtr->offsetWidth, 1,
420 tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
422 XDrawLine(screen
->display
, tPtr
->view
->window
, screen
->xorGC
,
423 cx
, tPtr
->offsetWidth
, cx
,
424 tPtr
->view
->size
.height
- tPtr
->offsetWidth
- 1);
430 drawRelief(WMView
*view
)
432 WMScreen
*scr
= view
->screen
;
433 Display
*dpy
= scr
->display
;
437 int width
= view
->size
.width
;
438 int height
= view
->size
.height
;
440 wgc
= W_GC(scr
->white
);
441 dgc
= W_GC(scr
->darkGray
);
442 lgc
= W_GC(scr
->gray
);
445 XDrawLine(dpy
, view
->window
, dgc
, 0, 0, width
-1, 0);
446 XDrawLine(dpy
, view
->window
, dgc
, 0, 1, width
-2, 1);
448 XDrawLine(dpy
, view
->window
, dgc
, 0, 0, 0, height
-2);
449 XDrawLine(dpy
, view
->window
, dgc
, 1, 0, 1, height
-3);
452 XDrawLine(dpy
, view
->window
, wgc
, 0, height
-1, width
-1, height
-1);
453 XDrawLine(dpy
, view
->window
, lgc
, 1, height
-2, width
-2, height
-2);
455 XDrawLine(dpy
, view
->window
, wgc
, width
-1, 0, width
-1, height
-1);
456 XDrawLine(dpy
, view
->window
, lgc
, width
-2, 1, width
-2, height
-3);
461 paintTextField(TextField
*tPtr
)
463 W_Screen
*screen
= tPtr
->view
->screen
;
464 W_View
*view
= tPtr
->view
;
470 if (!view
->flags
.realized
|| !view
->flags
.mapped
)
473 if (!tPtr
->flags
.bordered
) {
479 totalWidth
= tPtr
->view
->size
.width
- 2*bd
;
481 if (tPtr
->textLen
> 0) {
482 tw
= WMWidthOfString(screen
->normalFont
,
483 &(tPtr
->text
[tPtr
->viewPosition
]),
484 tPtr
->textLen
- tPtr
->viewPosition
);
486 th
= WMFontHeight(screen
->normalFont
);
488 ty
= tPtr
->offsetWidth
;
489 switch (tPtr
->flags
.alignment
) {
491 tx
= tPtr
->offsetWidth
;
492 if (tw
< tPtr
->usableWidth
)
493 XClearArea(screen
->display
, view
->window
, bd
+tw
, bd
,
494 totalWidth
-tw
, view
->size
.height
-2*bd
,
499 tx
= tPtr
->offsetWidth
+ (tPtr
->usableWidth
- tw
) / 2;
500 if (tw
< tPtr
->usableWidth
)
501 XClearArea(screen
->display
, view
->window
, bd
, bd
,
502 totalWidth
, view
->size
.height
-2*bd
, False
);
507 tx
= tPtr
->offsetWidth
+ tPtr
->usableWidth
- tw
;
508 if (tw
< tPtr
->usableWidth
)
509 XClearArea(screen
->display
, view
->window
, bd
, bd
,
510 totalWidth
-tw
, view
->size
.height
-2*bd
, False
);
514 if (!tPtr
->flags
.secure
) {
515 if (!tPtr
->flags
.enabled
)
516 WMSetColorInGC(screen
->darkGray
, screen
->textFieldGC
);
518 WMDrawImageString(screen
, view
->window
, screen
->textFieldGC
,
519 screen
->normalFont
, tx
, ty
,
520 &(tPtr
->text
[tPtr
->viewPosition
]),
521 tPtr
->textLen
- tPtr
->viewPosition
);
523 if (!tPtr
->flags
.enabled
)
524 WMSetColorInGC(screen
->black
, screen
->textFieldGC
);
527 XClearArea(screen
->display
, view
->window
, bd
, bd
, totalWidth
,
528 view
->size
.height
- 2*bd
, False
);
532 if (tPtr
->flags
.focused
&& tPtr
->flags
.enabled
&& tPtr
->flags
.cursorOn
) {
537 if (tPtr
->flags
.bordered
) {
545 blinkCursor(void *data
)
547 TextField
*tPtr
= (TextField
*)data
;
549 if (tPtr
->flags
.cursorOn
) {
550 tPtr
->timerID
= WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY
, blinkCursor
,
553 tPtr
->timerID
= WMAddTimerHandler(CURSOR_BLINK_ON_DELAY
, blinkCursor
,
557 tPtr
->flags
.cursorOn
= !tPtr
->flags
.cursorOn
;
562 handleEvents(XEvent
*event
, void *data
)
564 TextField
*tPtr
= (TextField
*)data
;
566 CHECK_CLASS(data
, WC_TextField
);
569 switch (event
->type
) {
571 if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr
->view
))!=tPtr
->view
)
573 tPtr
->flags
.focused
= 1;
575 if (!tPtr
->timerID
) {
576 tPtr
->timerID
= WMAddTimerHandler(CURSOR_BLINK_ON_DELAY
,
580 paintTextField(tPtr
);
582 WMPostNotificationName(WMTextDidBeginEditingNotification
, tPtr
, NULL
);
584 tPtr
->flags
.notIllegalMovement
= 0;
588 tPtr
->flags
.focused
= 0;
591 WMDeleteTimerHandler(tPtr
->timerID
);
592 tPtr
->timerID
= NULL
;
595 paintTextField(tPtr
);
596 if (!tPtr
->flags
.notIllegalMovement
) {
597 WMPostNotificationName(WMTextDidEndEditingNotification
, tPtr
,
598 (void*)WMIllegalTextMovement
);
603 if (event
->xexpose
.count
!=0)
605 paintTextField(tPtr
);
609 destroyTextField(tPtr
);
616 handleTextFieldKeyPress(TextField
*tPtr
, XEvent
*event
)
620 int count
, refresh
= 0;
621 int control_pressed
= 0;
623 WMScreen
*scr
= tPtr
->view
->screen
;
627 if (((XKeyEvent
*) event
)->state
& WM_EMACSKEYMASK
) {
631 count
= XLookupString(&event
->xkey
, buffer
, 63, &ksym
, NULL
);
632 buffer
[count
] = '\0';
636 if (event
->xkey
.state
& ShiftMask
) {
637 if (tPtr
->view
->prevFocusChain
) {
638 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr
->view
),
639 tPtr
->view
->prevFocusChain
);
640 tPtr
->flags
.notIllegalMovement
= 1;
642 WMPostNotificationName(WMTextDidEndEditingNotification
, tPtr
,
643 (void*)WMBacktabTextMovement
);
645 if (tPtr
->view
->nextFocusChain
) {
646 W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr
->view
),
647 tPtr
->view
->nextFocusChain
);
648 tPtr
->flags
.notIllegalMovement
= 1;
650 WMPostNotificationName(WMTextDidEndEditingNotification
,
651 tPtr
, (void*)WMTabTextMovement
);
656 WMPostNotificationName(WMTextDidEndEditingNotification
, tPtr
,
657 (void*)WMReturnTextMovement
);
660 case WM_EMACSKEY_LEFT
:
661 if (!control_pressed
) {
666 if (tPtr
->cursorPosition
> 0) {
668 tPtr
->cursorPosition
--;
669 if (tPtr
->cursorPosition
< tPtr
->viewPosition
) {
670 tPtr
->viewPosition
= tPtr
->cursorPosition
;
678 case WM_EMACSKEY_RIGHT
:
679 if (!control_pressed
) {
684 if (tPtr
->cursorPosition
< tPtr
->textLen
) {
686 tPtr
->cursorPosition
++;
687 while (WMWidthOfString(scr
->normalFont
,
688 &(tPtr
->text
[tPtr
->viewPosition
]),
689 tPtr
->cursorPosition
-tPtr
->viewPosition
)
690 > tPtr
->usableWidth
) {
691 tPtr
->viewPosition
++;
699 case WM_EMACSKEY_HOME
:
700 if (!control_pressed
) {
705 if (tPtr
->cursorPosition
> 0) {
707 tPtr
->cursorPosition
= 0;
708 if (tPtr
->viewPosition
> 0) {
709 tPtr
->viewPosition
= 0;
717 case WM_EMACSKEY_END
:
718 if (!control_pressed
) {
723 if (tPtr
->cursorPosition
< tPtr
->textLen
) {
725 tPtr
->cursorPosition
= tPtr
->textLen
;
726 tPtr
->viewPosition
= 0;
727 while (WMWidthOfString(scr
->normalFont
,
728 &(tPtr
->text
[tPtr
->viewPosition
]),
729 tPtr
->textLen
-tPtr
->viewPosition
)
730 >= tPtr
->usableWidth
) {
731 tPtr
->viewPosition
++;
740 if (!control_pressed
) {
744 if (tPtr
->cursorPosition
> 0) {
747 range
.position
= tPtr
->cursorPosition
-1;
749 WMDeleteTextFieldRange(tPtr
, range
);
753 case WM_EMACSKEY_DEL
:
754 if (!control_pressed
) {
759 if (tPtr
->cursorPosition
< tPtr
->textLen
) {
762 range
.position
= tPtr
->cursorPosition
;
764 WMDeleteTextFieldRange(tPtr
, range
);
770 if (count
> 0 && !iscntrl(buffer
[0])) {
772 WMInsertTextFieldText(tPtr
, buffer
, tPtr
->cursorPosition
);
776 paintTextField(tPtr
);
780 WMPostNotificationName(WMTextDidChangeNotification
, tPtr
, NULL
);
786 pointToCursorPosition(TextField
*tPtr
, int x
)
788 WMFont
*font
= tPtr
->view
->screen
->normalFont
;
792 if (tPtr
->flags
.bordered
)
795 a
= tPtr
->viewPosition
;
796 b
= tPtr
->viewPosition
+ tPtr
->textLen
;
797 if (WMWidthOfString(font
, &(tPtr
->text
[tPtr
->viewPosition
]),
798 tPtr
->textLen
-tPtr
->viewPosition
) < x
)
799 return tPtr
->textLen
;
801 while (a
< b
&& b
-a
>1) {
803 tw
= WMWidthOfString(font
, &(tPtr
->text
[tPtr
->viewPosition
]),
804 mid
- tPtr
->viewPosition
);
817 handleTextFieldActionEvents(XEvent
*event
, void *data
)
819 TextField
*tPtr
= (TextField
*)data
;
821 CHECK_CLASS(data
, WC_TextField
);
823 switch (event
->type
) {
825 if (tPtr
->flags
.enabled
)
826 handleTextFieldKeyPress(tPtr
, event
);
830 if (tPtr
->flags
.enabled
&& (event
->xmotion
.state
& Button1Mask
)) {
831 tPtr
->cursorPosition
= pointToCursorPosition(tPtr
,
833 paintTextField(tPtr
);
838 if (tPtr
->flags
.enabled
&& !tPtr
->flags
.focused
) {
839 WMSetFocusToWidget(tPtr
);
840 tPtr
->cursorPosition
= pointToCursorPosition(tPtr
,
842 paintTextField(tPtr
);
843 } else if (tPtr
->flags
.focused
) {
844 tPtr
->cursorPosition
= pointToCursorPosition(tPtr
,
846 paintTextField(tPtr
);
848 if (event
->xbutton
.button
== Button2
&& tPtr
->flags
.enabled
) {
851 text
= W_GetTextSelection(tPtr
->view
->screen
, XA_PRIMARY
);
853 text
= W_GetTextSelection(tPtr
->view
->screen
, XA_CUT_BUFFER0
);
856 WMInsertTextFieldText(tPtr
, text
, tPtr
->cursorPosition
);
858 WMPostNotificationName(WMTextDidChangeNotification
, tPtr
,
872 destroyTextField(TextField
*tPtr
)
876 WMDeleteTimerHandler(tPtr
->timerID
);