mirror of
https://github.com/gryf/wmaker.git
synced 2025-12-18 03:50:30 +01:00
Because the previous patch brought a (welcome) change in the public API, seize the opportunity to go further in the improvement.
1581 lines
38 KiB
C
1581 lines
38 KiB
C
|
|
#include "WINGsP.h"
|
|
#include "wconfig.h"
|
|
|
|
#include <X11/keysym.h>
|
|
#include <X11/Xatom.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#define CURSOR_BLINK_ON_DELAY 600
|
|
#define CURSOR_BLINK_OFF_DELAY 300
|
|
|
|
const char *WMTextDidChangeNotification = "WMTextDidChangeNotification";
|
|
const char *WMTextDidBeginEditingNotification = "WMTextDidBeginEditingNotification";
|
|
const char *WMTextDidEndEditingNotification = "WMTextDidEndEditingNotification";
|
|
|
|
typedef struct W_TextField {
|
|
W_Class widgetClass;
|
|
W_View *view;
|
|
|
|
#if 0
|
|
struct W_TextField *nextField; /* next textfield in the chain */
|
|
struct W_TextField *prevField;
|
|
#endif
|
|
|
|
char *text;
|
|
int textLen; /* size of text */
|
|
int bufferSize; /* memory allocated for text */
|
|
|
|
int viewPosition; /* position of text being shown */
|
|
|
|
int cursorPosition; /* position of the insertion cursor */
|
|
|
|
short usableWidth;
|
|
short offsetWidth; /* offset of text from border */
|
|
|
|
WMRange selection;
|
|
|
|
WMFont *font;
|
|
|
|
WMTextFieldDelegate *delegate;
|
|
|
|
WMHandlerID timerID; /* for cursor blinking */
|
|
|
|
struct {
|
|
WMAlignment alignment:2;
|
|
|
|
unsigned int bordered:1;
|
|
|
|
unsigned int beveled:1;
|
|
|
|
unsigned int enabled:1;
|
|
|
|
unsigned int focused:1;
|
|
|
|
unsigned int cursorOn:1;
|
|
|
|
unsigned int secure:1; /* password entry style */
|
|
|
|
unsigned int pointerGrabbed:1;
|
|
|
|
unsigned int ownsSelection:1;
|
|
|
|
unsigned int waitingSelection:1; /* requested selection, but
|
|
* didnt get yet */
|
|
|
|
unsigned int notIllegalMovement:1;
|
|
} flags;
|
|
} TextField;
|
|
|
|
#define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\
|
|
if ((T)->delegate && (T)->delegate->C)\
|
|
(*(T)->delegate->C)((T)->delegate,notif);\
|
|
WMPostNotification(notif);\
|
|
WMReleaseNotification(notif);}
|
|
|
|
#define MIN_TEXT_BUFFER 2
|
|
#define TEXT_BUFFER_INCR 8
|
|
|
|
#define WM_EMACSKEYMASK ControlMask
|
|
|
|
#define WM_EMACSKEY_LEFT XK_b
|
|
#define WM_EMACSKEY_RIGHT XK_f
|
|
#define WM_EMACSKEY_HOME XK_a
|
|
#define WM_EMACSKEY_END XK_e
|
|
#define WM_EMACSKEY_BS XK_h
|
|
#define WM_EMACSKEY_DEL XK_d
|
|
|
|
#define DEFAULT_WIDTH 60
|
|
#define DEFAULT_HEIGHT 20
|
|
#define DEFAULT_BORDERED True
|
|
#define DEFAULT_ALIGNMENT WALeft
|
|
|
|
static void destroyTextField(TextField * tPtr);
|
|
static void paintTextField(TextField * tPtr);
|
|
|
|
static void handleEvents(XEvent * event, void *data);
|
|
static void handleTextFieldActionEvents(XEvent * event, void *data);
|
|
static void didResizeTextField(W_ViewDelegate * self, WMView * view);
|
|
|
|
struct W_ViewDelegate _TextFieldViewDelegate = {
|
|
NULL,
|
|
NULL,
|
|
didResizeTextField,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static void lostSelection(WMView * view, Atom selection, void *cdata);
|
|
|
|
static WMData *requestHandler(WMView * view, Atom selection, Atom target, void *cdata, Atom * type);
|
|
|
|
static WMSelectionProcs selectionHandler = {
|
|
requestHandler,
|
|
lostSelection,
|
|
NULL
|
|
};
|
|
|
|
#define TEXT_WIDTH(tPtr, start) (WMWidthOfString((tPtr)->font, \
|
|
&((tPtr)->text[(start)]), (tPtr)->textLen - (start)))
|
|
|
|
#define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
|
|
&((tPtr)->text[(start)]), (end) - (start)))
|
|
|
|
static inline int oneUTF8CharBackward(const char *str, int len)
|
|
{
|
|
const unsigned char *ustr = (const unsigned char *)str;
|
|
int pos = 0;
|
|
|
|
while (len-- > 0 && ustr[--pos] >= 0x80 && ustr[pos] <= 0xbf) ;
|
|
return pos;
|
|
}
|
|
|
|
static inline int oneUTF8CharForward(const char *str, int len)
|
|
{
|
|
const unsigned char *ustr = (const unsigned char *)str;
|
|
int pos = 0;
|
|
|
|
while (len-- > 0 && ustr[++pos] >= 0x80 && ustr[pos] <= 0xbf) ;
|
|
return pos;
|
|
}
|
|
|
|
// find the beginning of the UTF8 char pointed by str
|
|
static inline int seekUTF8CharStart(const char *str, int len)
|
|
{
|
|
const unsigned char *ustr = (const unsigned char *)str;
|
|
int pos = 0;
|
|
|
|
while (len-- > 0 && ustr[pos] >= 0x80 && ustr[pos] <= 0xbf)
|
|
--pos;
|
|
return pos;
|
|
}
|
|
|
|
static void normalizeRange(TextField * tPtr, WMRange * range)
|
|
{
|
|
if (range->position < 0 && range->count < 0)
|
|
range->count = 0;
|
|
|
|
if (range->count == 0) {
|
|
/*range->position = 0; why is this? */
|
|
return;
|
|
}
|
|
|
|
/* (1,-2) ~> (0,1) ; (1,-1) ~> (0,1) ; (2,-1) ~> (1,1) */
|
|
if (range->count < 0) { /* && range->position >= 0 */
|
|
if (range->position + range->count < 0) {
|
|
range->count = range->position;
|
|
range->position = 0;
|
|
} else {
|
|
range->count = -range->count;
|
|
range->position -= range->count;
|
|
}
|
|
/* (-2,1) ~> (0,0) ; (-1,1) ~> (0,0) ; (-1,2) ~> (0,1) */
|
|
} else if (range->position < 0) { /* && range->count > 0 */
|
|
if (range->position + range->count < 0) {
|
|
range->position = range->count = 0;
|
|
} else {
|
|
range->count += range->position;
|
|
range->position = 0;
|
|
}
|
|
}
|
|
|
|
if (range->position + range->count > tPtr->textLen)
|
|
range->count = tPtr->textLen - range->position;
|
|
}
|
|
|
|
static void memmv(char *dest, const char *src, int size)
|
|
{
|
|
int i;
|
|
|
|
if (dest > src) {
|
|
for (i = size - 1; i >= 0; i--) {
|
|
dest[i] = src[i];
|
|
}
|
|
} else if (dest < src) {
|
|
for (i = 0; i < size; i++) {
|
|
dest[i] = src[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
static int incrToFit(TextField * tPtr)
|
|
{
|
|
int vp = tPtr->viewPosition;
|
|
|
|
while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
|
|
tPtr->viewPosition += oneUTF8CharForward(&tPtr->text[tPtr->viewPosition],
|
|
tPtr->textLen - tPtr->viewPosition);
|
|
}
|
|
return vp != tPtr->viewPosition;
|
|
}
|
|
|
|
static int incrToFit2(TextField * tPtr)
|
|
{
|
|
int vp = tPtr->viewPosition;
|
|
|
|
while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
|
|
>= tPtr->usableWidth)
|
|
tPtr->viewPosition += oneUTF8CharForward(&tPtr->text[tPtr->viewPosition],
|
|
tPtr->cursorPosition - tPtr->viewPosition);
|
|
return vp != tPtr->viewPosition;
|
|
}
|
|
|
|
static void decrToFit(TextField * tPtr)
|
|
{
|
|
int vp = tPtr->viewPosition;
|
|
|
|
while (vp > 0 && (vp += oneUTF8CharBackward(&tPtr->text[vp], vp),
|
|
TEXT_WIDTH(tPtr, vp)) < tPtr->usableWidth) {
|
|
tPtr->viewPosition = vp;
|
|
}
|
|
}
|
|
|
|
#undef TEXT_WIDTH
|
|
#undef TEXT_WIDTH2
|
|
|
|
static WMData *requestHandler(WMView * view, Atom selection, Atom target, void *cdata, Atom * type)
|
|
{
|
|
TextField *tPtr = view->self;
|
|
int count;
|
|
Display *dpy = tPtr->view->screen->display;
|
|
Atom _TARGETS;
|
|
Atom TEXT = XInternAtom(dpy, "TEXT", False);
|
|
Atom COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", False);
|
|
WMData *data;
|
|
|
|
/* Parameter not used, but tell the compiler that it is ok */
|
|
(void) selection;
|
|
(void) cdata;
|
|
|
|
count = tPtr->selection.count < 0
|
|
? tPtr->selection.position + tPtr->selection.count : tPtr->selection.position;
|
|
|
|
if (target == XA_STRING || target == TEXT || target == COMPOUND_TEXT) {
|
|
|
|
data = WMCreateDataWithBytes(&(tPtr->text[count]), abs(tPtr->selection.count));
|
|
WMSetDataFormat(data, 8);
|
|
*type = target;
|
|
|
|
return data;
|
|
}
|
|
|
|
_TARGETS = XInternAtom(dpy, "TARGETS", False);
|
|
if (target == _TARGETS) {
|
|
Atom supported_type[4];
|
|
|
|
supported_type[0] = _TARGETS;
|
|
supported_type[1] = XA_STRING;
|
|
supported_type[2] = TEXT;
|
|
supported_type[3] = COMPOUND_TEXT;
|
|
|
|
data = WMCreateDataWithBytes(supported_type, sizeof(supported_type));
|
|
WMSetDataFormat(data, 32);
|
|
|
|
*type = target;
|
|
return data;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static void lostSelection(WMView * view, Atom selection, void *cdata)
|
|
{
|
|
TextField *tPtr = (WMTextField *) view->self;
|
|
|
|
/* Parameter not used, but tell the compiler that it is ok */
|
|
(void) cdata;
|
|
|
|
if (tPtr->flags.ownsSelection) {
|
|
WMDeleteSelectionHandler(view, selection, CurrentTime);
|
|
tPtr->flags.ownsSelection = 0;
|
|
}
|
|
if (tPtr->selection.count != 0) {
|
|
tPtr->selection.count = 0;
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
static void selectionNotification(void *observerData, WMNotification * notification)
|
|
{
|
|
WMView *observerView = (WMView *) observerData;
|
|
WMView *newOwnerView = (WMView *) WMGetNotificationClientData(notification);
|
|
|
|
if (observerView != newOwnerView) {
|
|
/*
|
|
//if (tPtr->flags.ownsSelection)
|
|
// WMDeleteSelectionHandler(observerView, XA_PRIMARY, CurrentTime);
|
|
*/
|
|
lostSelection(observerView, XA_PRIMARY, NULL);
|
|
}
|
|
}
|
|
|
|
static void realizeObserver(void *self, WMNotification * not)
|
|
{
|
|
/* Parameter not used, but tell the compiler that it is ok */
|
|
(void) not;
|
|
|
|
W_CreateIC(((TextField *) self)->view);
|
|
}
|
|
|
|
WMTextField *WMCreateTextField(WMWidget * parent)
|
|
{
|
|
TextField *tPtr;
|
|
|
|
tPtr = wmalloc(sizeof(TextField));
|
|
tPtr->widgetClass = WC_TextField;
|
|
|
|
tPtr->view = W_CreateView(W_VIEW(parent));
|
|
if (!tPtr->view) {
|
|
wfree(tPtr);
|
|
return NULL;
|
|
}
|
|
tPtr->view->self = tPtr;
|
|
|
|
tPtr->view->delegate = &_TextFieldViewDelegate;
|
|
|
|
tPtr->view->attribFlags |= CWCursor;
|
|
tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;
|
|
|
|
W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);
|
|
|
|
tPtr->text = wmalloc(MIN_TEXT_BUFFER);
|
|
tPtr->textLen = 0;
|
|
tPtr->bufferSize = MIN_TEXT_BUFFER;
|
|
|
|
tPtr->flags.enabled = 1;
|
|
|
|
WMCreateEventHandler(tPtr->view, ExposureMask | StructureNotifyMask | FocusChangeMask, handleEvents, tPtr);
|
|
|
|
tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);
|
|
|
|
tPtr->flags.bordered = DEFAULT_BORDERED;
|
|
tPtr->flags.beveled = True;
|
|
tPtr->flags.alignment = DEFAULT_ALIGNMENT;
|
|
tPtr->offsetWidth = WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font)) / 2, 1);
|
|
|
|
W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
|
|
WMCreateEventHandler(tPtr->view, EnterWindowMask | LeaveWindowMask
|
|
| ButtonReleaseMask | ButtonPressMask | KeyPressMask | Button1MotionMask,
|
|
handleTextFieldActionEvents, tPtr);
|
|
|
|
WMAddNotificationObserver(selectionNotification, tPtr->view,
|
|
WMSelectionOwnerDidChangeNotification, (void *)XA_PRIMARY);
|
|
|
|
WMAddNotificationObserver(realizeObserver, tPtr, WMViewRealizedNotification, tPtr->view);
|
|
|
|
tPtr->flags.cursorOn = 1;
|
|
|
|
return tPtr;
|
|
}
|
|
|
|
void WMSetTextFieldDelegate(WMTextField * tPtr, WMTextFieldDelegate * delegate)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
tPtr->delegate = delegate;
|
|
}
|
|
|
|
WMTextFieldDelegate *WMGetTextFieldDelegate(WMTextField * tPtr)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
return tPtr->delegate;
|
|
}
|
|
|
|
void WMInsertTextFieldText(WMTextField * tPtr, const char *text, int position)
|
|
{
|
|
int len;
|
|
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
if (!text)
|
|
return;
|
|
|
|
len = strlen(text);
|
|
|
|
/* check if buffer will hold the text */
|
|
if (len + tPtr->textLen >= tPtr->bufferSize) {
|
|
tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
|
|
tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
|
|
}
|
|
|
|
if (position < 0 || position >= tPtr->textLen) {
|
|
/* append the text at the end */
|
|
wstrlcat(tPtr->text, text, tPtr->bufferSize);
|
|
tPtr->textLen += len;
|
|
tPtr->cursorPosition += len;
|
|
incrToFit(tPtr);
|
|
} else {
|
|
/* insert text at position */
|
|
memmv(&(tPtr->text[position + len]), &(tPtr->text[position]), tPtr->textLen - position + 1);
|
|
|
|
memcpy(&(tPtr->text[position]), text, len);
|
|
|
|
tPtr->textLen += len;
|
|
if (position >= tPtr->cursorPosition) {
|
|
tPtr->cursorPosition += len;
|
|
incrToFit2(tPtr);
|
|
} else {
|
|
incrToFit(tPtr);
|
|
}
|
|
}
|
|
|
|
paintTextField(tPtr);
|
|
}
|
|
|
|
void WMDeleteTextFieldRange(WMTextField * tPtr, WMRange range)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
normalizeRange(tPtr, &range);
|
|
|
|
if (!range.count)
|
|
return;
|
|
|
|
memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position + range.count]),
|
|
tPtr->textLen - (range.position + range.count) + 1);
|
|
|
|
/* better than nothing ;) */
|
|
if (tPtr->cursorPosition > range.position)
|
|
tPtr->viewPosition += oneUTF8CharBackward(&tPtr->text[tPtr->viewPosition], tPtr->viewPosition);
|
|
tPtr->textLen -= range.count;
|
|
tPtr->cursorPosition = range.position;
|
|
|
|
decrToFit(tPtr);
|
|
|
|
paintTextField(tPtr);
|
|
}
|
|
|
|
char *WMGetTextFieldText(WMTextField * tPtr)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
return wstrdup(tPtr->text);
|
|
}
|
|
|
|
void WMSetTextFieldText(WMTextField * tPtr, const char *text)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
if ((text && strcmp(tPtr->text, text) == 0) || (!text && tPtr->textLen == 0))
|
|
return;
|
|
|
|
if (text == NULL) {
|
|
tPtr->text[0] = 0;
|
|
tPtr->textLen = 0;
|
|
} else {
|
|
tPtr->textLen = strlen(text);
|
|
|
|
if (tPtr->textLen >= tPtr->bufferSize) {
|
|
tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
|
|
tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
|
|
}
|
|
wstrlcpy(tPtr->text, text, tPtr->bufferSize);
|
|
}
|
|
|
|
tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
|
|
tPtr->viewPosition = 0;
|
|
tPtr->selection.count = 0;
|
|
|
|
if (tPtr->view->flags.realized)
|
|
paintTextField(tPtr);
|
|
}
|
|
|
|
void WMSetTextFieldAlignment(WMTextField * tPtr, WMAlignment alignment)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
tPtr->flags.alignment = alignment;
|
|
|
|
if (alignment != WALeft) {
|
|
wwarning(_("only left alignment is supported in textfields"));
|
|
return;
|
|
}
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
void WMSetTextFieldBordered(WMTextField * tPtr, Bool bordered)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
tPtr->flags.bordered = bordered;
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
void WMSetTextFieldBeveled(WMTextField * tPtr, Bool flag)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
tPtr->flags.beveled = ((flag == 0) ? 0 : 1);
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
void WMSetTextFieldSecure(WMTextField * tPtr, Bool flag)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
tPtr->flags.secure = ((flag == 0) ? 0 : 1);
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
Bool WMGetTextFieldEditable(WMTextField * tPtr)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
return tPtr->flags.enabled;
|
|
}
|
|
|
|
void WMSetTextFieldEditable(WMTextField * tPtr, Bool flag)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
tPtr->flags.enabled = ((flag == 0) ? 0 : 1);
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
void WMSelectTextFieldRange(WMTextField * tPtr, WMRange range)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
if (tPtr->flags.enabled) {
|
|
normalizeRange(tPtr, &range);
|
|
|
|
tPtr->selection = range;
|
|
|
|
tPtr->cursorPosition = range.position + range.count;
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WMSetTextFieldCursorPosition(WMTextField * tPtr, unsigned int position)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
if (tPtr->flags.enabled) {
|
|
if (position > tPtr->textLen)
|
|
position = tPtr->textLen;
|
|
|
|
tPtr->cursorPosition = position;
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned WMGetTextFieldCursorPosition(WMTextField *tPtr)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
return tPtr->cursorPosition;
|
|
}
|
|
|
|
void WMSetTextFieldNextTextField(WMTextField * tPtr, WMTextField * next)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
if (next == NULL) {
|
|
if (tPtr->view->nextFocusChain)
|
|
tPtr->view->nextFocusChain->prevFocusChain = NULL;
|
|
tPtr->view->nextFocusChain = NULL;
|
|
return;
|
|
}
|
|
|
|
CHECK_CLASS(next, WC_TextField);
|
|
|
|
if (tPtr->view->nextFocusChain)
|
|
tPtr->view->nextFocusChain->prevFocusChain = NULL;
|
|
if (next->view->prevFocusChain)
|
|
next->view->prevFocusChain->nextFocusChain = NULL;
|
|
|
|
tPtr->view->nextFocusChain = next->view;
|
|
next->view->prevFocusChain = tPtr->view;
|
|
}
|
|
|
|
void WMSetTextFieldPrevTextField(WMTextField * tPtr, WMTextField * prev)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
if (prev == NULL) {
|
|
if (tPtr->view->prevFocusChain)
|
|
tPtr->view->prevFocusChain->nextFocusChain = NULL;
|
|
tPtr->view->prevFocusChain = NULL;
|
|
return;
|
|
}
|
|
|
|
CHECK_CLASS(prev, WC_TextField);
|
|
|
|
if (tPtr->view->prevFocusChain)
|
|
tPtr->view->prevFocusChain->nextFocusChain = NULL;
|
|
if (prev->view->nextFocusChain)
|
|
prev->view->nextFocusChain->prevFocusChain = NULL;
|
|
|
|
tPtr->view->prevFocusChain = prev->view;
|
|
prev->view->nextFocusChain = tPtr->view;
|
|
}
|
|
|
|
void WMSetTextFieldFont(WMTextField * tPtr, WMFont * font)
|
|
{
|
|
CHECK_CLASS(tPtr, WC_TextField);
|
|
|
|
if (tPtr->font)
|
|
WMReleaseFont(tPtr->font);
|
|
tPtr->font = WMRetainFont(font);
|
|
|
|
tPtr->offsetWidth = WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font)) / 2, 1);
|
|
|
|
if (tPtr->view->flags.realized) {
|
|
paintTextField(tPtr);
|
|
}
|
|
}
|
|
|
|
WMFont *WMGetTextFieldFont(WMTextField * tPtr)
|
|
{
|
|
return tPtr->font;
|
|
}
|
|
|
|
static void didResizeTextField(W_ViewDelegate * self, WMView * view)
|
|
{
|
|
WMTextField *tPtr = (WMTextField *) view->self;
|
|
|
|
/* Parameter not used, but tell the compiler that it is ok */
|
|
(void) self;
|
|
|
|
tPtr->offsetWidth = WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font)) / 2, 1);
|
|
|
|
tPtr->usableWidth = tPtr->view->size.width - 2 * tPtr->offsetWidth /*+ 2 */ ;
|
|
}
|
|
|
|
static char *makeHiddenString(int length)
|
|
{
|
|
char *data = wmalloc(length + 1);
|
|
|
|
memset(data, '*', length);
|
|
data[length] = '\0';
|
|
|
|
return data;
|
|
}
|
|
|
|
static void paintCursor(TextField * tPtr)
|
|
{
|
|
int cx;
|
|
WMScreen *screen = tPtr->view->screen;
|
|
int textWidth;
|
|
char *text;
|
|
|
|
if (tPtr->flags.secure)
|
|
text = makeHiddenString(strlen(tPtr->text));
|
|
else
|
|
text = tPtr->text;
|
|
|
|
cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]), tPtr->cursorPosition - tPtr->viewPosition);
|
|
|
|
switch (tPtr->flags.alignment) {
|
|
case WARight:
|
|
textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
|
|
if (textWidth < tPtr->usableWidth)
|
|
cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
|
|
else
|
|
cx += tPtr->offsetWidth + 1;
|
|
break;
|
|
case WALeft:
|
|
cx += tPtr->offsetWidth + 1;
|
|
break;
|
|
case WAJustified:
|
|
/* not supported */
|
|
case WACenter:
|
|
textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
|
|
if (textWidth < tPtr->usableWidth)
|
|
cx += tPtr->offsetWidth + (tPtr->usableWidth - textWidth) / 2;
|
|
else
|
|
cx += tPtr->offsetWidth;
|
|
break;
|
|
}
|
|
/*
|
|
XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
|
|
cx, tPtr->offsetWidth, 1,
|
|
tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
|
|
printf("%d %d\n",cx,tPtr->cursorPosition);
|
|
*/
|
|
|
|
XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
|
|
cx, tPtr->offsetWidth, cx, tPtr->view->size.height - tPtr->offsetWidth - 1);
|
|
|
|
W_SetPreeditPositon(tPtr->view, cx, 0);
|
|
|
|
if (tPtr->flags.secure) {
|
|
wfree(text);
|
|
}
|
|
}
|
|
|
|
static void drawRelief(WMView * view, Bool beveled)
|
|
{
|
|
WMScreen *scr = view->screen;
|
|
Display *dpy = scr->display;
|
|
GC wgc;
|
|
GC lgc;
|
|
GC dgc;
|
|
int width = view->size.width;
|
|
int height = view->size.height;
|
|
|
|
dgc = WMColorGC(scr->darkGray);
|
|
|
|
if (!beveled) {
|
|
XDrawRectangle(dpy, view->window, dgc, 0, 0, width - 1, height - 1);
|
|
|
|
return;
|
|
}
|
|
wgc = WMColorGC(scr->white);
|
|
lgc = WMColorGC(scr->gray);
|
|
|
|
/* top left */
|
|
XDrawLine(dpy, view->window, dgc, 0, 0, width - 1, 0);
|
|
XDrawLine(dpy, view->window, dgc, 0, 1, width - 2, 1);
|
|
|
|
XDrawLine(dpy, view->window, dgc, 0, 0, 0, height - 2);
|
|
XDrawLine(dpy, view->window, dgc, 1, 0, 1, height - 3);
|
|
|
|
/* bottom right */
|
|
XDrawLine(dpy, view->window, wgc, 0, height - 1, width - 1, height - 1);
|
|
XDrawLine(dpy, view->window, lgc, 1, height - 2, width - 2, height - 2);
|
|
|
|
XDrawLine(dpy, view->window, wgc, width - 1, 0, width - 1, height - 1);
|
|
XDrawLine(dpy, view->window, lgc, width - 2, 1, width - 2, height - 3);
|
|
}
|
|
|
|
static void paintTextField(TextField * tPtr)
|
|
{
|
|
W_Screen *screen = tPtr->view->screen;
|
|
W_View *view = tPtr->view;
|
|
W_View viewbuffer;
|
|
int tx, ty, tw;
|
|
int rx;
|
|
int bd;
|
|
int totalWidth;
|
|
char *text;
|
|
Pixmap drawbuffer;
|
|
WMColor *color;
|
|
|
|
if (!view->flags.realized || !view->flags.mapped)
|
|
return;
|
|
|
|
if (!tPtr->flags.bordered) {
|
|
bd = 0;
|
|
} else {
|
|
bd = 2;
|
|
}
|
|
|
|
if (tPtr->flags.secure) {
|
|
text = makeHiddenString(strlen(tPtr->text));
|
|
} else {
|
|
text = tPtr->text;
|
|
}
|
|
|
|
totalWidth = tPtr->view->size.width - 2 * bd;
|
|
|
|
drawbuffer = XCreatePixmap(screen->display, view->window,
|
|
view->size.width, view->size.height, screen->depth);
|
|
XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
|
|
0, 0, view->size.width, view->size.height);
|
|
/* this is quite dirty */
|
|
viewbuffer.screen = view->screen;
|
|
viewbuffer.size = view->size;
|
|
viewbuffer.window = drawbuffer;
|
|
|
|
if (tPtr->textLen > 0) {
|
|
tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]), tPtr->textLen - tPtr->viewPosition);
|
|
|
|
ty = tPtr->offsetWidth;
|
|
switch (tPtr->flags.alignment) {
|
|
case WALeft:
|
|
tx = tPtr->offsetWidth + 1;
|
|
if (tw < tPtr->usableWidth)
|
|
XFillRectangle(screen->display, drawbuffer,
|
|
WMColorGC(screen->white),
|
|
bd + tw, bd, totalWidth - tw, view->size.height - 2 * bd);
|
|
break;
|
|
|
|
case WACenter:
|
|
tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
|
|
if (tw < tPtr->usableWidth)
|
|
XClearArea(screen->display, view->window, bd, bd,
|
|
totalWidth, view->size.height - 2 * bd, False);
|
|
break;
|
|
|
|
default:
|
|
case WARight:
|
|
tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
|
|
if (tw < tPtr->usableWidth)
|
|
XClearArea(screen->display, view->window, bd, bd,
|
|
totalWidth - tw, view->size.height - 2 * bd, False);
|
|
break;
|
|
}
|
|
|
|
color = tPtr->flags.enabled ? screen->black : screen->darkGray;
|
|
|
|
WMDrawImageString(screen, drawbuffer, color, screen->white,
|
|
tPtr->font, tx, ty, &(text[tPtr->viewPosition]),
|
|
tPtr->textLen - tPtr->viewPosition);
|
|
|
|
if (tPtr->selection.count) {
|
|
int count, count2;
|
|
|
|
count = tPtr->selection.count < 0
|
|
? tPtr->selection.position + tPtr->selection.count : tPtr->selection.position;
|
|
count2 = abs(tPtr->selection.count);
|
|
if (count < tPtr->viewPosition) {
|
|
count2 = abs(count2 - abs(tPtr->viewPosition - count));
|
|
count = tPtr->viewPosition;
|
|
}
|
|
|
|
rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font, text, count)
|
|
- WMWidthOfString(tPtr->font, text, tPtr->viewPosition);
|
|
|
|
WMDrawImageString(screen, drawbuffer, color, screen->gray,
|
|
tPtr->font, rx, ty, &(text[count]), count2);
|
|
}
|
|
} else {
|
|
XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
|
|
bd, bd, totalWidth, view->size.height - 2 * bd);
|
|
}
|
|
|
|
/* draw relief */
|
|
if (tPtr->flags.bordered) {
|
|
drawRelief(&viewbuffer, tPtr->flags.beveled);
|
|
}
|
|
|
|
if (tPtr->flags.secure)
|
|
wfree(text);
|
|
XCopyArea(screen->display, drawbuffer, view->window,
|
|
screen->copyGC, 0, 0, view->size.width, view->size.height, 0, 0);
|
|
XFreePixmap(screen->display, drawbuffer);
|
|
|
|
/* draw cursor */
|
|
if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
|
|
paintCursor(tPtr);
|
|
}
|
|
}
|
|
|
|
static void blinkCursor(void *data)
|
|
{
|
|
TextField *tPtr = (TextField *) data;
|
|
|
|
if (tPtr->flags.cursorOn) {
|
|
tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor, data);
|
|
} else {
|
|
tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor, data);
|
|
}
|
|
paintCursor(tPtr);
|
|
tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
|
|
}
|
|
|
|
static void handleEvents(XEvent * event, void *data)
|
|
{
|
|
TextField *tPtr = (TextField *) data;
|
|
|
|
CHECK_CLASS(data, WC_TextField);
|
|
|
|
switch (event->type) {
|
|
case FocusIn:
|
|
W_FocusIC(tPtr->view);
|
|
if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view)) != tPtr->view)
|
|
return;
|
|
tPtr->flags.focused = 1;
|
|
|
|
if (!tPtr->timerID) {
|
|
tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor, tPtr);
|
|
}
|
|
|
|
paintTextField(tPtr);
|
|
|
|
NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);
|
|
|
|
tPtr->flags.notIllegalMovement = 0;
|
|
break;
|
|
|
|
case FocusOut:
|
|
W_UnFocusIC(tPtr->view);
|
|
tPtr->flags.focused = 0;
|
|
|
|
if (tPtr->timerID)
|
|
WMDeleteTimerHandler(tPtr->timerID);
|
|
tPtr->timerID = NULL;
|
|
|
|
paintTextField(tPtr);
|
|
if (!tPtr->flags.notIllegalMovement) {
|
|
NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
|
|
(void *)WMIllegalTextMovement);
|
|
}
|
|
break;
|
|
|
|
case Expose:
|
|
if (event->xexpose.count != 0)
|
|
break;
|
|
paintTextField(tPtr);
|
|
break;
|
|
|
|
case DestroyNotify:
|
|
destroyTextField(tPtr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event)
|
|
{
|
|
char buffer[64];
|
|
KeySym ksym;
|
|
const char *textEvent = NULL;
|
|
void *data = NULL;
|
|
int count, refresh = 0;
|
|
int control_pressed = 0;
|
|
int cancelSelection = 1;
|
|
Bool shifted, controled, modified;
|
|
Bool relay = True;
|
|
|
|
/*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count); */
|
|
if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
|
|
control_pressed = 1;
|
|
|
|
shifted = (event->xkey.state & ShiftMask ? True : False);
|
|
controled = (event->xkey.state & ControlMask ? True : False);
|
|
modified = shifted || controled;
|
|
|
|
count = W_LookupString(tPtr->view, &event->xkey, buffer, 63, &ksym, NULL);
|
|
//count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
|
|
buffer[count] = '\0';
|
|
|
|
switch (ksym) {
|
|
case XK_Tab:
|
|
#ifdef XK_ISO_Left_Tab
|
|
/* FALLTHRU */
|
|
case XK_ISO_Left_Tab:
|
|
#endif
|
|
if (!controled) {
|
|
if (shifted) {
|
|
if (tPtr->view->prevFocusChain) {
|
|
W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
|
|
tPtr->view->prevFocusChain);
|
|
tPtr->flags.notIllegalMovement = 1;
|
|
}
|
|
data = (void *)WMBacktabTextMovement;
|
|
} else {
|
|
if (tPtr->view->nextFocusChain) {
|
|
W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
|
|
tPtr->view->nextFocusChain);
|
|
tPtr->flags.notIllegalMovement = 1;
|
|
}
|
|
data = (void *)WMTabTextMovement;
|
|
}
|
|
textEvent = WMTextDidEndEditingNotification;
|
|
|
|
cancelSelection = 0;
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
case XK_Escape:
|
|
if (!modified) {
|
|
data = (void *)WMEscapeTextMovement;
|
|
textEvent = WMTextDidEndEditingNotification;
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
#ifdef XK_KP_Enter
|
|
/* FALLTHRU */
|
|
case XK_KP_Enter:
|
|
#endif
|
|
/* FALLTHRU */
|
|
case XK_Return:
|
|
if (!modified) {
|
|
data = (void *)WMReturnTextMovement;
|
|
textEvent = WMTextDidEndEditingNotification;
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
case WM_EMACSKEY_LEFT:
|
|
if (!control_pressed)
|
|
goto normal_key;
|
|
else
|
|
controled = False;
|
|
|
|
#ifdef XK_KP_Left
|
|
/* FALLTHRU */
|
|
case XK_KP_Left:
|
|
#endif
|
|
/* FALLTHRU */
|
|
case XK_Left:
|
|
if (tPtr->cursorPosition > 0) {
|
|
int i;
|
|
paintCursor(tPtr);
|
|
|
|
i = tPtr->cursorPosition;
|
|
i += oneUTF8CharBackward(&tPtr->text[i], i);
|
|
if (controled) {
|
|
while (i > 0 && tPtr->text[i] != ' ')
|
|
i--;
|
|
while (i > 0 && tPtr->text[i] == ' ')
|
|
i--;
|
|
|
|
tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
|
|
} else
|
|
tPtr->cursorPosition = i;
|
|
|
|
if (tPtr->cursorPosition < tPtr->viewPosition) {
|
|
tPtr->viewPosition = tPtr->cursorPosition;
|
|
refresh = 1;
|
|
} else
|
|
paintCursor(tPtr);
|
|
}
|
|
if (shifted)
|
|
cancelSelection = 0;
|
|
|
|
relay = False;
|
|
|
|
break;
|
|
|
|
case WM_EMACSKEY_RIGHT:
|
|
if (!control_pressed)
|
|
goto normal_key;
|
|
else
|
|
controled = False;
|
|
|
|
#ifdef XK_KP_Right
|
|
/* FALLTHRU */
|
|
case XK_KP_Right:
|
|
#endif
|
|
/* FALLTHRU */
|
|
case XK_Right:
|
|
if (tPtr->cursorPosition < tPtr->textLen) {
|
|
int i;
|
|
paintCursor(tPtr);
|
|
|
|
i = tPtr->cursorPosition;
|
|
if (controled) {
|
|
while (tPtr->text[i] && tPtr->text[i] != ' ')
|
|
i++;
|
|
while (tPtr->text[i] == ' ')
|
|
i++;
|
|
} else {
|
|
i += oneUTF8CharForward(&tPtr->text[i], tPtr->textLen - i);
|
|
}
|
|
tPtr->cursorPosition = i;
|
|
|
|
refresh = incrToFit2(tPtr);
|
|
|
|
if (!refresh)
|
|
paintCursor(tPtr);
|
|
}
|
|
if (shifted)
|
|
cancelSelection = 0;
|
|
|
|
relay = False;
|
|
|
|
break;
|
|
|
|
case WM_EMACSKEY_HOME:
|
|
if (!control_pressed)
|
|
goto normal_key;
|
|
else
|
|
controled = False;
|
|
|
|
#ifdef XK_KP_Home
|
|
/* FALLTHRU */
|
|
case XK_KP_Home:
|
|
#endif
|
|
/* FALLTHRU */
|
|
case XK_Home:
|
|
if (!controled) {
|
|
if (tPtr->cursorPosition > 0) {
|
|
paintCursor(tPtr);
|
|
tPtr->cursorPosition = 0;
|
|
if (tPtr->viewPosition > 0) {
|
|
tPtr->viewPosition = 0;
|
|
refresh = 1;
|
|
} else
|
|
paintCursor(tPtr);
|
|
}
|
|
if (shifted)
|
|
cancelSelection = 0;
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
case WM_EMACSKEY_END:
|
|
if (!control_pressed)
|
|
goto normal_key;
|
|
else
|
|
controled = False;
|
|
|
|
#ifdef XK_KP_End
|
|
/* FALLTHRU */
|
|
case XK_KP_End:
|
|
#endif
|
|
/* FALLTHRU */
|
|
case XK_End:
|
|
if (!controled) {
|
|
if (tPtr->cursorPosition < tPtr->textLen) {
|
|
paintCursor(tPtr);
|
|
tPtr->cursorPosition = tPtr->textLen;
|
|
tPtr->viewPosition = 0;
|
|
|
|
refresh = incrToFit(tPtr);
|
|
|
|
if (!refresh)
|
|
paintCursor(tPtr);
|
|
}
|
|
if (shifted)
|
|
cancelSelection = 0;
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
case WM_EMACSKEY_BS:
|
|
if (!control_pressed)
|
|
goto normal_key;
|
|
else
|
|
modified = False;
|
|
|
|
/* FALLTHRU */
|
|
case XK_BackSpace:
|
|
if (!modified) {
|
|
if (tPtr->selection.count) {
|
|
WMDeleteTextFieldRange(tPtr, tPtr->selection);
|
|
data = (void *)WMDeleteTextEvent;
|
|
textEvent = WMTextDidChangeNotification;
|
|
} else if (tPtr->cursorPosition > 0) {
|
|
int i = oneUTF8CharBackward(&tPtr->text[tPtr->cursorPosition],
|
|
tPtr->cursorPosition);
|
|
WMRange range;
|
|
range.position = tPtr->cursorPosition + i;
|
|
range.count = -i;
|
|
WMDeleteTextFieldRange(tPtr, range);
|
|
data = (void *)WMDeleteTextEvent;
|
|
textEvent = WMTextDidChangeNotification;
|
|
}
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
case WM_EMACSKEY_DEL:
|
|
if (!control_pressed)
|
|
goto normal_key;
|
|
else
|
|
modified = False;
|
|
|
|
#ifdef XK_KP_Delete
|
|
/* FALLTHRU */
|
|
case XK_KP_Delete:
|
|
#endif
|
|
/* FALLTHRU */
|
|
case XK_Delete:
|
|
if (!modified) {
|
|
if (tPtr->selection.count) {
|
|
WMDeleteTextFieldRange(tPtr, tPtr->selection);
|
|
data = (void *)WMDeleteTextEvent;
|
|
textEvent = WMTextDidChangeNotification;
|
|
} else if (tPtr->cursorPosition < tPtr->textLen) {
|
|
WMRange range;
|
|
range.position = tPtr->cursorPosition;
|
|
range.count = oneUTF8CharForward(&tPtr->text[tPtr->cursorPosition],
|
|
tPtr->textLen - tPtr->cursorPosition);
|
|
WMDeleteTextFieldRange(tPtr, range);
|
|
data = (void *)WMDeleteTextEvent;
|
|
textEvent = WMTextDidChangeNotification;
|
|
}
|
|
|
|
relay = False;
|
|
}
|
|
break;
|
|
|
|
normal_key:
|
|
default:
|
|
if (!controled) {
|
|
if (count > 0 && !iscntrl(buffer[0])) {
|
|
if (tPtr->selection.count)
|
|
WMDeleteTextFieldRange(tPtr, tPtr->selection);
|
|
WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
|
|
data = (void *)WMInsertTextEvent;
|
|
textEvent = WMTextDidChangeNotification;
|
|
|
|
relay = False;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (relay) {
|
|
WMRelayToNextResponder(W_VIEW(tPtr), event);
|
|
return;
|
|
}
|
|
|
|
/* Do not allow text selection in secure text fields */
|
|
if (cancelSelection || tPtr->flags.secure) {
|
|
lostSelection(tPtr->view, XA_PRIMARY, NULL);
|
|
|
|
if (tPtr->selection.count) {
|
|
tPtr->selection.count = 0;
|
|
refresh = 1;
|
|
}
|
|
tPtr->selection.position = tPtr->cursorPosition;
|
|
} else {
|
|
if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {
|
|
|
|
tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
|
|
|
|
refresh = 1;
|
|
}
|
|
}
|
|
|
|
/*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count); */
|
|
|
|
if (textEvent) {
|
|
WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);
|
|
|
|
if (tPtr->delegate) {
|
|
if (textEvent == WMTextDidBeginEditingNotification && tPtr->delegate->didBeginEditing)
|
|
(*tPtr->delegate->didBeginEditing) (tPtr->delegate, notif);
|
|
|
|
else if (textEvent == WMTextDidEndEditingNotification && tPtr->delegate->didEndEditing)
|
|
(*tPtr->delegate->didEndEditing) (tPtr->delegate, notif);
|
|
|
|
else if (textEvent == WMTextDidChangeNotification && tPtr->delegate->didChange)
|
|
(*tPtr->delegate->didChange) (tPtr->delegate, notif);
|
|
}
|
|
|
|
WMPostNotification(notif);
|
|
WMReleaseNotification(notif);
|
|
}
|
|
|
|
if (refresh)
|
|
paintTextField(tPtr);
|
|
|
|
/*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count); */
|
|
}
|
|
|
|
static int pointToCursorPosition(TextField * tPtr, int x)
|
|
{
|
|
int a, b, pos, prev, tw;
|
|
|
|
if (tPtr->flags.bordered)
|
|
x -= 2;
|
|
|
|
if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
|
|
tPtr->textLen - tPtr->viewPosition) <= x)
|
|
return tPtr->textLen;
|
|
|
|
a = tPtr->viewPosition;
|
|
b = tPtr->textLen;
|
|
|
|
/* we halve the text until we get into a 10 byte vicinity of x */
|
|
while (b - a > 10) {
|
|
pos = (a + b) / 2;
|
|
pos += seekUTF8CharStart(&tPtr->text[pos], pos - a);
|
|
tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]), pos - tPtr->viewPosition);
|
|
if (tw > x) {
|
|
b = pos;
|
|
} else if (tw < x) {
|
|
a = pos;
|
|
} else {
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
/* at this point x can be positioned on any glyph between 'a' and 'b-1'
|
|
* inclusive, with the exception of the left border of the 'a' glyph and
|
|
* the right border or the 'b-1' glyph
|
|
*
|
|
* ( <--- range for x's position ---> )
|
|
* a a+1 .......................... b-1 b
|
|
*/
|
|
pos = prev = a;
|
|
while (pos <= b) {
|
|
tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]), pos - tPtr->viewPosition);
|
|
if (tw > x) {
|
|
return prev;
|
|
} else if (pos == b) {
|
|
break;
|
|
}
|
|
prev = pos;
|
|
pos += oneUTF8CharForward(&tPtr->text[pos], b - pos);
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
static void pasteText(WMView * view, Atom selection, Atom target, Time timestamp, void *cdata, WMData * data)
|
|
{
|
|
TextField *tPtr = (TextField *) view->self;
|
|
char *str;
|
|
|
|
/* Parameter not used, but tell the compiler that it is ok */
|
|
(void) selection;
|
|
(void) target;
|
|
(void) timestamp;
|
|
(void) cdata;
|
|
|
|
tPtr->flags.waitingSelection = 0;
|
|
|
|
if (data != NULL) {
|
|
str = (char *)WMDataBytes(data);
|
|
|
|
WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
|
|
NOTIFY(tPtr, didChange, WMTextDidChangeNotification, (void *)WMInsertTextEvent);
|
|
} else {
|
|
int n;
|
|
|
|
str = XFetchBuffer(tPtr->view->screen->display, &n, 0);
|
|
|
|
if (str != NULL) {
|
|
str[n] = 0;
|
|
WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
|
|
XFree(str);
|
|
NOTIFY(tPtr, didChange, WMTextDidChangeNotification, (void *)WMInsertTextEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handleTextFieldActionEvents(XEvent * event, void *data)
|
|
{
|
|
TextField *tPtr = (TextField *) data;
|
|
static Time lastButtonReleasedEvent = 0;
|
|
static Time lastButtonReleasedEvent2 = 0;
|
|
Display *dpy = event->xany.display;
|
|
|
|
CHECK_CLASS(data, WC_TextField);
|
|
|
|
switch (event->type) {
|
|
case KeyPress:
|
|
if (tPtr->flags.waitingSelection) {
|
|
return;
|
|
}
|
|
if (tPtr->flags.enabled && tPtr->flags.focused) {
|
|
handleTextFieldKeyPress(tPtr, event);
|
|
XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->invisibleCursor);
|
|
tPtr->flags.pointerGrabbed = 1;
|
|
}
|
|
break;
|
|
|
|
case MotionNotify:
|
|
|
|
if (tPtr->flags.pointerGrabbed) {
|
|
tPtr->flags.pointerGrabbed = 0;
|
|
XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->textCursor);
|
|
}
|
|
if (tPtr->flags.waitingSelection) {
|
|
return;
|
|
}
|
|
|
|
if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {
|
|
|
|
if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x > tPtr->usableWidth) {
|
|
if (WMWidthOfString(tPtr->font,
|
|
&(tPtr->text[tPtr->viewPosition]),
|
|
tPtr->cursorPosition - tPtr->viewPosition)
|
|
> tPtr->usableWidth) {
|
|
tPtr->viewPosition += oneUTF8CharForward(&tPtr->text[tPtr->viewPosition],
|
|
tPtr->textLen -
|
|
tPtr->viewPosition);
|
|
}
|
|
} else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
|
|
paintCursor(tPtr);
|
|
tPtr->viewPosition += oneUTF8CharBackward(&tPtr->text[tPtr->viewPosition],
|
|
tPtr->viewPosition);
|
|
}
|
|
|
|
tPtr->cursorPosition = pointToCursorPosition(tPtr, event->xmotion.x);
|
|
|
|
/* Do not allow text selection in secure textfields */
|
|
if (tPtr->flags.secure) {
|
|
tPtr->selection.position = tPtr->cursorPosition;
|
|
}
|
|
|
|
tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;
|
|
|
|
paintCursor(tPtr);
|
|
paintTextField(tPtr);
|
|
|
|
}
|
|
break;
|
|
|
|
case ButtonPress:
|
|
if (tPtr->flags.pointerGrabbed) {
|
|
tPtr->flags.pointerGrabbed = 0;
|
|
XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->textCursor);
|
|
break;
|
|
}
|
|
|
|
if (tPtr->flags.waitingSelection) {
|
|
break;
|
|
}
|
|
|
|
switch (tPtr->flags.alignment) {
|
|
int textWidth;
|
|
case WARight:
|
|
textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
|
|
if (tPtr->flags.enabled && !tPtr->flags.focused) {
|
|
WMSetFocusToWidget(tPtr);
|
|
}
|
|
if (tPtr->flags.focused) {
|
|
tPtr->selection.position = tPtr->cursorPosition;
|
|
tPtr->selection.count = 0;
|
|
}
|
|
if (textWidth < tPtr->usableWidth) {
|
|
tPtr->cursorPosition = pointToCursorPosition(tPtr,
|
|
event->xbutton.x - tPtr->usableWidth
|
|
+ textWidth);
|
|
} else
|
|
tPtr->cursorPosition = pointToCursorPosition(tPtr, event->xbutton.x);
|
|
|
|
paintTextField(tPtr);
|
|
break;
|
|
|
|
case WALeft:
|
|
if (tPtr->flags.enabled && !tPtr->flags.focused) {
|
|
WMSetFocusToWidget(tPtr);
|
|
}
|
|
if (tPtr->flags.focused && event->xbutton.button == Button1) {
|
|
tPtr->cursorPosition = pointToCursorPosition(tPtr, event->xbutton.x);
|
|
tPtr->selection.position = tPtr->cursorPosition;
|
|
tPtr->selection.count = 0;
|
|
paintTextField(tPtr);
|
|
}
|
|
if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
|
|
char *text;
|
|
int n;
|
|
|
|
if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
|
|
event->xbutton.time, pasteText, NULL)) {
|
|
text = XFetchBuffer(tPtr->view->screen->display, &n, 0);
|
|
|
|
if (text) {
|
|
text[n] = 0;
|
|
WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
|
|
XFree(text);
|
|
NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
|
|
(void *)WMInsertTextEvent);
|
|
}
|
|
} else {
|
|
tPtr->flags.waitingSelection = 1;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
if (tPtr->flags.pointerGrabbed) {
|
|
tPtr->flags.pointerGrabbed = 0;
|
|
XDefineCursor(dpy, W_VIEW(tPtr)->window, W_VIEW(tPtr)->screen->textCursor);
|
|
}
|
|
if (tPtr->flags.waitingSelection) {
|
|
break;
|
|
}
|
|
|
|
if (!tPtr->flags.secure && tPtr->selection.count != 0) {
|
|
int start, count;
|
|
XRotateBuffers(dpy, 1);
|
|
|
|
count = abs(tPtr->selection.count);
|
|
if (tPtr->selection.count < 0)
|
|
start = tPtr->selection.position - count;
|
|
else
|
|
start = tPtr->selection.position;
|
|
|
|
XStoreBuffer(dpy, &tPtr->text[start], count, 0);
|
|
}
|
|
|
|
if (!tPtr->flags.secure &&
|
|
event->xbutton.time - lastButtonReleasedEvent <= WINGsConfiguration.doubleClickDelay) {
|
|
|
|
if (event->xbutton.time - lastButtonReleasedEvent2 <=
|
|
2 * WINGsConfiguration.doubleClickDelay) {
|
|
tPtr->selection.position = 0;
|
|
tPtr->selection.count = tPtr->textLen;
|
|
} else {
|
|
int pos, cnt;
|
|
char *txt;
|
|
pos = tPtr->selection.position;
|
|
cnt = tPtr->selection.count;
|
|
txt = tPtr->text;
|
|
while (pos >= 0) {
|
|
if (txt[pos] == ' ' || txt[pos] == '\t')
|
|
break;
|
|
pos--;
|
|
}
|
|
pos++;
|
|
|
|
while (pos + cnt < tPtr->textLen) {
|
|
if (txt[pos + cnt] == ' ' || txt[pos + cnt] == '\t')
|
|
break;
|
|
cnt++;
|
|
}
|
|
tPtr->selection.position = pos;
|
|
tPtr->selection.count = cnt;
|
|
}
|
|
paintTextField(tPtr);
|
|
|
|
if (!tPtr->flags.ownsSelection) {
|
|
tPtr->flags.ownsSelection =
|
|
WMCreateSelectionHandler(tPtr->view,
|
|
XA_PRIMARY,
|
|
event->xbutton.time, &selectionHandler, NULL);
|
|
}
|
|
} else if (!tPtr->flags.secure && tPtr->selection.count != 0 && !tPtr->flags.ownsSelection) {
|
|
tPtr->flags.ownsSelection =
|
|
WMCreateSelectionHandler(tPtr->view,
|
|
XA_PRIMARY, event->xbutton.time, &selectionHandler, NULL);
|
|
}
|
|
|
|
lastButtonReleasedEvent2 = lastButtonReleasedEvent;
|
|
lastButtonReleasedEvent = event->xbutton.time;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void destroyTextField(TextField * tPtr)
|
|
{
|
|
if (tPtr->timerID)
|
|
WMDeleteTimerHandler(tPtr->timerID);
|
|
|
|
W_DestroyIC(tPtr->view);
|
|
|
|
WMReleaseFont(tPtr->font);
|
|
/*// use lostSelection() instead of WMDeleteSelectionHandler here? */
|
|
WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
|
|
WMRemoveNotificationObserver(tPtr);
|
|
|
|
if (tPtr->text)
|
|
wfree(tPtr->text);
|
|
|
|
wfree(tPtr);
|
|
}
|