#include "textfieldP.h"
#include "textfield.h"
#include <Xm/DrawP.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "../source/textBuf.h"
#include "../source/textSel.h"
#include "../source/textDisp.h"
#include "../source/text.h"
#define TF_DEFAULT_FONT_NAME "Monospace:size=10"
#define TF_VPADDING 3
#define TF_HPADDING 2
#define TF_BUF_BLOCK 128
#define TF_TAB_STR " "
static NFont *defaultFont;
static void textfield_class_init(
void);
static void mouse1DownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void mouse1UpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void adjustselectionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void insertAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void actionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void deletePrevCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void deleteNextCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void deletePrevWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void deleteNextWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void moveLeftAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void moveRightAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void moveLeftWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void moveRightWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void focusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void focusOutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void enterAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void leaveAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void cutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void copyAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void pasteAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void endLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void beginLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void selectAllAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void insertPrimaryAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
static void tfCalcCursorPos(TextFieldWidget tf);
static int tfPosToIndex(TextFieldWidget tf,
int pos);
static int tfXToPos(TextFieldWidget tf,
int x);
static int tfIndexToX(TextFieldWidget tf,
int pos);
static void tfSelection(TextFieldWidget tf,
int *start,
int *end,
int *startX,
int *endX);
static void tfSelectionIndex(TextFieldWidget tf,
int *start,
int *end);
static void tfSetSelection(TextFieldWidget tf,
int from,
int to);
static void tfClearSelection(TextFieldWidget tf);
static void tfInsertPrimary(TextFieldWidget tf, XEvent *event);
static void TFInsert(TextFieldWidget tf,
const char *chars,
size_t nchars);
static int TFLeftPos(TextFieldWidget tf);
static int TFRightPos(TextFieldWidget tf);
static void TFDelete(TextFieldWidget tf,
int from,
int to);
static void wordbounds(TextFieldWidget tf,
int index,
int *out_wleft,
int *out_wright);
static Dimension tfCalcHeight(TextFieldWidget tf);
static Atom aTargets;
static Atom aUtf8String;
static Boolean convertSelection(
Widget w,
Atom *seltype,
Atom *target,
Atom *type,
XtPointer *value,
unsigned long *length,
int *format);
static void loseSelection(Widget w, Atom *type);
static XtResource resources[] = {
{XmNtextRenderTable, XmCTextRenderTable, XmRString,
sizeof(XmString),XtOffset(TextFieldWidget, textfield.renderTable), XmRString,
NULL},
{textNXftFont, textCXftFont, textTXftFont,
sizeof(NFont *), XtOffset(TextFieldWidget, textfield.font), textTXftFont, &defaultFont},
{XmNvalueChangedCallback, XmCCallback, XmRCallback,
sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.valueChangedCB), XmRCallback,
NULL},
{XmNfocusCallback, XmCCallback, XmRCallback,
sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.focusCB), XmRCallback,
NULL},
{XmNlosingFocusCallback, XmCCallback, XmRCallback,
sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.losingFocusCB), XmRCallback,
NULL},
{XmNactivateCallback, XmCCallback, XmRCallback,
sizeof(XtCallbackList), XtOffset(TextFieldWidget, textfield.activateCB), XmRCallback,
NULL},
{XmNblinkRate, XmCBlinkRate , XmRInt,
sizeof(
int), XtOffset(TextFieldWidget, textfield.blinkrate), XmRImmediate, (XtPointer)
500}
};
static XtActionsRec actionslist[] = {
{
"mouse1down",mouse1DownAP},
{
"mouse1up",mouse1UpAP},
{
"adjustselection",adjustselectionAP},
{
"insert",insertAP},
{
"action",actionAP},
{
"moveleft",moveLeftAP},
{
"moveright",moveRightAP},
{
"moveleftword",moveLeftWordAP},
{
"moverightword",moveRightWordAP},
{
"deleteprev",deletePrevCharAP},
{
"deletenext",deleteNextCharAP},
{
"deleteprevword",deletePrevWordAP},
{
"deletenextword",deleteNextWordAP},
{
"focusIn",focusInAP},
{
"focusOut",focusOutAP},
{
"enter",enterAP},
{
"leave",leaveAP},
{
"cut-clipboard",cutAP},
{
"copy-clipboard",copyAP},
{
"paste-clipboard",pasteAP},
{
"endLine",endLineAP},
{
"beginLine",beginLineAP},
{
"selectAll",selectAllAP},
{
"insertPrimary",insertPrimaryAP},
{
"NULL",
NULL}
};
static char defaultTranslations[] =
"\
<FocusIn>: focusIn()\n\
<FocusOut>: focusOut()\n\
<EnterWindow>: leave()\n\
<LeaveWindow>: enter()\n\
s ~m ~a <Key>Tab: PrimitivePrevTabGroup()\n\
~m ~a <Key>Tab: PrimitiveNextTabGroup()\n\
Shift<Btn1Down>: mouse1down(\"select\")\n\
<Btn1Down>: mouse1down()\n\
<Btn1Up>: mouse1up()\n\
<Btn2Up>: insertPrimary()\n\
Ctrl<KeyPress>v: paste-clipboard()\n\
Button1<MotionNotify>: adjustselection()\n\
:<Key>KP_7: insert()\n\
:<Key>KP_8: insert()\n\
:<Key>KP_9: insert()\n\
:<Key>KP_4: insert()\n\
:<Key>KP_6: insert()\n\
:<Key>KP_1: insert()\n\
:<Key>KP_2: insert()\n\
:<Key>KP_3: insert()\n\
:<Key>KP_0: insert()\n\
:<Key>KP_Separator: insert()\n\
<KeyPress>Return: PrimitiveParentActivate() action()\n\
<Key>osfActivate: PrimitiveParentActivate() action()\n\
<Key>osfCancel: PrimitiveParentCancel()\n\
Ctrl<KeyPress>osfBackSpace: deleteprevword()\n\
Ctrl<KeyPress>osfDelete: deletenextword()\n\
<KeyPress>osfBackSpace: deleteprev()\n\
<KeyPress>osfDelete: deletenext()\n\
Shift<Key>osfBeginLine: beginLine(l)\n\
<Key>osfBeginLine: beginLine()\n\
Shift<Key>osfEndLine: endLine(r)\n\
<Key>osfEndLine: endLine()\n\
Ctrl Shift<KeyPress>osfLeft: moveleftword(l)\n\
Ctrl Shift<KeyPress>osfRight: moverightword(r)\n\
Ctrl<KeyPress>osfLeft: moveleftword()\n\
Ctrl<KeyPress>osfRight: moverightword()\n\
Ctrl<Key>a: selectAll()\n\
Shift<KeyPress>osfLeft: moveleft(l)\n\
Shift<KeyPress>osfRight: moveright(r)\n\
<KeyPress>osfLeft: moveleft()\n\
<KeyPress>osfRight: moveright()\n\
<KeyPress>: insert()";
TextFieldClassRec tfWidgetClassRec = {
{
(WidgetClass)&xmPrimitiveClassRec,
"XmTextField",
sizeof(TextFieldRec),
textfield_class_init,
NULL,
FALSE,
textfield_init,
NULL,
textfield_realize,
actionslist,
XtNumber(actionslist),
resources,
XtNumber(resources),
NULLQUARK,
True,
True,
True,
False,
textfield_destroy,
textfield_resize,
textfield_expose,
textfield_set_values,
NULL,
XtInheritSetValuesAlmost,
NULL,
textfield_acceptfocus,
XtVersion,
NULL,
defaultTranslations,
XtInheritQueryGeometry,
NULL,
NULL,
},
{
(XtWidgetProc)_XtInherit,
(XtWidgetProc)_XtInherit,
NULL,
NULL,
NULL,
0,
NULL
},
{
0
}
};
WidgetClass textfieldWidgetClass = (WidgetClass)&tfWidgetClassRec;
static Boolean XftFontConvert(
Display* dpy,
XrmValue* args,
Cardinal* num_args,
XrmValue* from,
XrmValue* to,
XtPointer* converter_data)
{
NFont *font = FontFromName(dpy, from->addr);
if(font) {
memcpy(to->addr, &font,
sizeof(NFont*));
return True;
}
else {
to->addr =
0;
to->size =
0;
return True;
}
}
static void textfield_class_init(
void) {
XtSetTypeConverter(XmRString, textTXftFont, XftFontConvert,
NULL,
0, XtCacheNone,
NULL);
}
Widget XNECreateTextField(Widget parent,
char *name, ArgList arglist, Cardinal argcount) {
return XtCreateWidget(name, textfieldWidgetClass, parent, arglist, argcount);
}
void textfield_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) {
TextFieldWidget tf = (TextFieldWidget)neww;
tf->textfield.alloc =
TF_BUF_BLOCK;
tf->textfield.length =
0;
tf->textfield.pos =
0;
tf->textfield.buffer = XtMalloc(
TF_BUF_BLOCK);
tf->textfield.posX =
0;
tf->textfield.posCalc =
0;
tf->textfield.scrollX =
0;
tf->textfield.hasSelection =
0;
tf->textfield.selStart =
0;
tf->textfield.selEnd =
0;
tf->textfield.btn1ClickPrev =
0;
tf->textfield.btn1ClickPrev2 =
0;
tf->textfield.blinkProcId =
0;
tf->textfield.cursorOn =
0;
if(aTargets ==
0) {
aTargets = XInternAtom(XtDisplay(request),
"TARGETS",
0);
}
if(aUtf8String ==
0) {
aUtf8String = XInternAtom(XtDisplay(request),
"UTF8_STRING",
0);
}
if(tf->textfield.font) {
tf->core.height = tfCalcHeight(tf);
}
}
static void tfInitXft(TextFieldWidget w) {
XWindowAttributes attributes;
XGetWindowAttributes(XtDisplay(w), XtWindow(w), &attributes);
Screen *screen = w->core.screen;
Visual *visual = screen->root_visual;
for(
int i=
0;i<screen->ndepths;i++) {
Depth d = screen->depths[i];
if(d.depth == w->core.depth) {
visual = d.visuals;
break;
}
}
Display *dp = XtDisplay(w);
w->textfield.d = XftDrawCreate(
dp,
XtWindow(w),
visual,
w->core.colormap);
}
static void get_default_font(TextFieldWidget tf, Display *dp) {
int ret;
char *resName;
XrmValue value;
char *resourceType =
NULL;
char *rtFontName =
NULL;
char *rtFontSize =
NULL;
char resNameBuf[
256];
resName =
"defaultRT.fontName";
if(tf->textfield.renderTable) {
ret = snprintf(resNameBuf,
256,
"%s.fontName", tf->textfield.renderTable);
if(ret <
256) {
resName = resNameBuf;
}
}
if(XrmGetResource(XtDatabase(dp), resName,
NULL, &resourceType, &value)) {
if(!strcmp(resourceType,
"String")) {
rtFontName = value.addr;
}
}
resName =
"defaultRT.fontSize";
if(tf->textfield.renderTable) {
ret = snprintf(resNameBuf,
256,
"%s.fontSize", tf->textfield.renderTable);
if(ret <
256) {
resName = resNameBuf;
}
}
if(XrmGetResource(XtDatabase(dp),
"fixedRT.fontSize",
NULL, &resourceType, &value)) {
if(!strcmp(resourceType,
"String")) {
rtFontSize = value.addr;
}
}
char buf[
256];
char *fontname =
TF_DEFAULT_FONT_NAME;
if(rtFontName && rtFontSize) {
ret = snprintf(buf,
256,
"%s:size=%s", rtFontName, rtFontSize);
if(ret <
256) {
fontname = buf;
}
}
defaultFont = FontFromName(dp, fontname);
if(!defaultFont) {
defaultFont = FontFromName(dp,
TF_DEFAULT_FONT_NAME);
}
}
void textfield_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) {
Display *dpy = XtDisplay(widget);
TextFieldWidget text = (TextFieldWidget)widget;
if(!defaultFont) {
get_default_font(text, dpy);
}
if(!text->textfield.font) {
text->textfield.font = defaultFont;
}
textfield_recalc_size((TextFieldWidget)widget);
(coreClassRec.core_class.realize)(widget, mask, attributes);
text->textfield.xim = XmImGetXIM(widget);
if(text->textfield.xim) {
Window win = XtWindow(widget);
XIMStyle style = XIMPreeditNothing | XIMStatusNothing;
text->textfield.xic = XCreateIC(
text->textfield.xim,
XNInputStyle,
style,
XNClientWindow,
win,
XNFocusWindow,
win,
NULL);
}
tfInitXft(text);
text->textfield.foregroundColor = PixelToColor(widget, text->primitive.foreground);
text->textfield.backgroundColor = PixelToColor(widget, text->core.background_pixel);
XGCValues gcvals;
gcvals.foreground = text->primitive.foreground;
gcvals.background = text->core.background_pixel;
text->textfield.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground|GCBackground), &gcvals);
gcvals.foreground = text->core.background_pixel;
gcvals.background = text->primitive.foreground;
text->textfield.gcInv = XCreateGC(dpy, XtWindow(widget), (GCForeground|GCBackground), &gcvals);
gcvals.foreground = XtParent(text)->core.background_pixel;
gcvals.background = XtParent(text)->core.background_pixel;
text->textfield.highlightBackground = XCreateGC(dpy, XtWindow(widget), (GCForeground|GCBackground), &gcvals);
text->textfield.textarea_xoff = text->primitive.shadow_thickness + text->primitive.highlight_thickness +
TF_HPADDING;
text->textfield.textarea_yoff = text->primitive.shadow_thickness + text->primitive.highlight_thickness +
TF_VPADDING;
text->textfield.posX = text->textfield.textarea_xoff;
}
void textfield_destroy(Widget widget) {
TextFieldWidget tf = (TextFieldWidget)widget;
XtFree(tf->textfield.buffer);
if(tf->textfield.font != defaultFont) {
FontUnref(tf->textfield.font);
}
if(tf->textfield.gc) {
XFreeGC(XtDisplay(widget), tf->textfield.gc);
}
if(tf->textfield.gcInv) {
XFreeGC(XtDisplay(widget), tf->textfield.gcInv);
}
if(tf->textfield.highlightBackground) {
XFreeGC(XtDisplay(widget), tf->textfield.highlightBackground);
}
if(tf->textfield.blinkProcId !=
0) {
XtRemoveTimeOut(tf->textfield.blinkProcId);
}
}
void textfield_resize(Widget widget) {
}
static int tfDrawString(TextFieldWidget tf, XftFont *font, XftColor *color,
int x,
const char *text,
size_t len) {
NFontList *fl = tf->textfield.font->fonts;
int yoff = tf->textfield.textarea_yoff;
int area = tf->core.height -
2*yoff;
int pad = area - (fl->font->ascent + fl->font->descent);
int hpad = pad/
2;
XftDrawStringUtf8(
tf->textfield.d,
color,
font,
x - tf->textfield.scrollX,
tf->core.height - tf->textfield.textarea_yoff -fl->font->descent - hpad,
(FcChar8*)text,
len);
XGlyphInfo extents;
XftTextExtentsUtf8(XtDisplay(tf), font, (FcChar8*)text, len, &extents);
return extents.xOff;
}
static void tfDrawCursor(TextFieldWidget tf) {
Display *dp = XtDisplay(tf);
Window w = XtWindow(tf);
int x = tf->textfield.posX - tf->textfield.scrollX;
int top = tf->textfield.textarea_yoff;
int bottom = tf->core.height-tf->textfield.textarea_yoff;
if(tf->textfield.hasFocus) {
XDrawLine(
dp,
w,
tf->textfield.cursorOn ? tf->textfield.gc : tf->textfield.gcInv,
x,
top,
x,
bottom);
}
else {
int diff = bottom-top;
int max = (diff/
2)+
1;
XPoint *points = calloc(max,
sizeof(XPoint));
int y = top;
int i;
for(i=
0;i<max && y <= bottom;i++) {
points[i].x = x;
points[i].y = y;
y +=
2;
}
XDrawPoints(dp, w, tf->textfield.gc, points, i, CoordModeOrigin);
free(points);
}
}
static void tfRedrawText(TextFieldWidget tf) {
tfCalcCursorPos(tf);
int border = tf->primitive.shadow_thickness + tf->primitive.highlight_thickness;
XClearArea(XtDisplay(tf), XtWindow(tf), border, border, tf->core.width -
2*border, tf->core.height -
2*border, False);
int selStart, selEnd, selStartX, selEndX;
if(tf->textfield.hasSelection) {
tfSelection(tf, &selStart, &selEnd, &selStartX, &selEndX);
XftDrawRect(
tf->textfield.d,
&tf->textfield.foregroundColor,
selStartX - tf->textfield.scrollX,
tf->textfield.textarea_yoff,
selEndX - selStartX,
tf->core.height -
2*tf->textfield.textarea_yoff);
}
XRectangle rect;
rect.x =
0;
rect.y =
0;
rect.width = tf->core.width -
2 * tf->textfield.textarea_xoff;
rect.height = tf->core.height;
XftDrawSetClipRectangles(tf->textfield.d, tf->textfield.textarea_xoff,
0, &rect,
1);
XftFont *font = tf->textfield.font->fonts->font;
XftColor *color = &tf->textfield.foregroundColor;
const char *buf = tf->textfield.buffer;
size_t length = tf->textfield.length;
size_t start =
0;
int xoff = tf->textfield.textarea_xoff;
int charlen =
1;
int pos =
0;
for(
int i=
0;i<length;i+=charlen) {
FcChar32 c;
charlen = Utf8ToUcs4(buf + i, &c, length - i);
XftFont *cFont = FindFont(tf->textfield.font, c);
XftColor *cColor = &tf->textfield.foregroundColor;
if(tf->textfield.hasSelection) {
if(i >= selStart && i < selEnd) {
cColor = &tf->textfield.backgroundColor;
}
}
if(c ==
'\t' || cFont != font || color != cColor) {
size_t drawLen = i - start;
if(drawLen >
0) {
xoff += tfDrawString(tf, font, color, xoff, buf + start, drawLen);
start = i;
}
font = cFont;
color = cColor;
if(c ==
'\t') {
xoff += tfDrawString(tf, font, color, xoff,
TF_TAB_STR,
sizeof(
TF_TAB_STR)-
1);
start++;
}
}
pos++;
}
int drawLen = length - start;
if(drawLen >
0) {
tfDrawString(tf, font, color, xoff, buf + start, drawLen);
}
tfDrawCursor(tf);
}
static void tfDrawHighlight(TextFieldWidget tf) {
XmeDrawHighlight(
XtDisplay(tf),
XtWindow(tf),
tf->textfield.hasFocus ? tf->primitive.highlight_GC : tf->textfield.highlightBackground,
0,
0,
tf->core.width,
tf->core.height,
tf->primitive.highlight_thickness);
}
void textfield_expose(Widget widget, XEvent* event, Region region) {
TextFieldWidget tf = (TextFieldWidget)widget;
tfDrawHighlight(tf);
XmeDrawShadows(
XtDisplay(tf),
XtWindow(tf),
tf->primitive.bottom_shadow_GC,
tf->primitive.top_shadow_GC,
tf->primitive.highlight_thickness,
tf->primitive.highlight_thickness,
tf->core.width - (
2 * tf->primitive.highlight_thickness),
tf->core.height - (
2 * tf->primitive.highlight_thickness),
tf->primitive.shadow_thickness,
XmSHADOW_OUT);
tfRedrawText((TextFieldWidget)widget);
}
Boolean textfield_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
Boolean redraw = False;
TextFieldWidget cur = (TextFieldWidget)old;
TextFieldWidget new = (TextFieldWidget)neww;
if(!new->textfield.font) {
if(!defaultFont) {
get_default_font(new, XtDisplay(neww));
}
new->textfield.font = defaultFont;
}
if(cur->textfield.font != new->textfield.font) {
textfield_recalc_size(new);
redraw = True;
}
return redraw;
}
Boolean textfield_acceptfocus(Widget widget, Time *time) {
return 0;
}
static Dimension tfCalcHeight(TextFieldWidget tf) {
NFont *font = tf->textfield.font;
Dimension height = font->fonts->font->ascent + font->fonts->font->descent;
height +=
2*(tf->primitive.highlight_thickness + tf->primitive.shadow_thickness) +
2*
TF_VPADDING;
return height;
}
void textfield_recalc_size(TextFieldWidget w) {
int height = tfCalcHeight(w);
int width = w->core.width;
XtMakeResizeRequest((Widget)w, width, height,
NULL,
NULL);
}
static void ownSelection(TextFieldWidget tf) {
if(tf->textfield.selStart != tf->textfield.selEnd && !tf->textfield.hasSelection) {
XtOwnSelection((Widget)tf,
XA_PRIMARY, XtLastTimestampProcessed(XtDisplay((Widget)tf)), convertSelection, loseSelection,
NULL);
tf->textfield.hasSelection =
1;
}
}
static void adjustSelection(TextFieldWidget tf,
int x) {
int pos = tfXToPos(tf, x);
int index = tfPosToIndex(tf, pos);
tf->textfield.selEnd = index;
tf->textfield.pos = index;
tf->textfield.selEndX = tfIndexToX(tf, index);
ownSelection(tf);
}
static void mouse1DownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int select =
0;
if(*nArgs >=
1) {
if(!strcmp(args[
0],
"select")) {
select =
1;
}
}
XmProcessTraversal(w, XmTRAVERSE_CURRENT);
int pos = tfXToPos(tf, event->xbutton.x);
int index = tfPosToIndex(tf, pos);
int prevPos = tf->textfield.pos;
tf->textfield.pos = index;
int selStart, selEnd;
Time t = event->xbutton.time;
int multiclicktime = XtGetMultiClickTime(XtDisplay(w));
if(select) {
if(tf->textfield.hasSelection) {
if(index < tf->textfield.selStart) {
selStart = index;
selEnd = tf->textfield.selEnd;
}
else {
selStart = tf->textfield.selStart;
selEnd = index;
}
}
else {
if(prevPos > index) {
selStart = index;
selEnd = prevPos;
}
else {
selStart = prevPos;
selEnd = index;
}
}
tf->textfield.dontAdjustSel =
1;
}
else if(t - tf->textfield.btn1ClickPrev2 <
2*multiclicktime) {
t =
0;
selStart =
0;
selEnd = tf->textfield.length;
tf->textfield.pos = selEnd;
tf->textfield.dontAdjustSel =
1;
}
else if(t - tf->textfield.btn1ClickPrev < multiclicktime) {
int wleft, wright;
wordbounds(tf, index, &wleft, &wright);
selStart = wleft;
selEnd = wright;
tf->textfield.pos = wright;
tf->textfield.dontAdjustSel =
1;
}
else {
selStart = index;
selEnd = index;
tf->textfield.dontAdjustSel =
0;
}
tf->textfield.btn1ClickPrev2 = tf->textfield.btn1ClickPrev;
tf->textfield.btn1ClickPrev = t;
tfSetSelection(tf, selStart, selEnd);
tfRedrawText(tf);
}
static void mouse1UpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(tf->textfield.dontAdjustSel)
return;
adjustSelection(tf, event->xbutton.x);
if(tf->textfield.selStart == tf->textfield.selEnd) {
tfClearSelection(tf);
}
tfRedrawText(tf);
}
static void adjustselectionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
adjustSelection(tf, event->xbutton.x);
tfRedrawText(tf);
}
static void insertText(TextFieldWidget tf,
char *chars,
int nchars, XEvent *event) {
if(nchars ==
0)
return;
if(tf->textfield.hasSelection) {
int selStart, selEnd;
tfSelectionIndex(tf, &selStart, &selEnd);
TFDelete(tf, selStart, selEnd);
tfClearSelection(tf);
tf->textfield.pos = selStart;
}
TFInsert(tf, chars, nchars);
XmAnyCallbackStruct cb;
cb.reason = XmCR_VALUE_CHANGED;
cb.event = event;
XtCallCallbacks((Widget)tf, XmNvalueChangedCallback, &cb);
tfRedrawText(tf);
}
static void insertAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
char chars[
128];
KeySym keysym;
int nchars;
int status;
static XComposeStatus compose = {
NULL,
0};
if(tf->textfield.xic) {
#ifdef X_HAVE_UTF8_STRING
nchars = Xutf8LookupString(tf->textfield.xic, &event->xkey, chars,
127, &keysym, &status);
#else
nchars = XmbLookupString(tf->textfield.xic, &event->xkey, chars,
127, &keysym, &status);
#endif
}
else {
nchars = XLookupString(&event->xkey, chars,
127, &keysym, &compose);
}
insertText(tf, chars, nchars, event);
}
static void actionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
XmAnyCallbackStruct cb;
cb.reason = XmCR_ACTIVATE;
cb.event = event;
XtCallCallbacks((Widget)tf, XmNactivateCallback, &cb);
}
static void deleteText(TextFieldWidget tf,
int from,
int to, XEvent *event) {
TFDelete(tf, from, to);
XmAnyCallbackStruct cb;
cb.reason = XmCR_VALUE_CHANGED;
cb.event = event;
XtCallCallbacks((Widget)tf, XmNvalueChangedCallback, &cb);
tf->textfield.pos = from;
tfRedrawText(tf);
}
static void deletePrevCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int from;
int to;
if(tf->textfield.hasSelection) {
tfSelectionIndex(tf, &from, &to);
tfClearSelection(tf);
}
else {
from = TFLeftPos(tf);
to = tf->textfield.pos;
}
deleteText(tf, from, to, event);
}
static void deleteNextCharAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int from;
int to;
if(tf->textfield.hasSelection) {
tfSelectionIndex(tf, &from, &to);
tfClearSelection(tf);
}
else {
from = tf->textfield.pos;
to = TFRightPos(tf);
}
deleteText(tf, from, to, event);
}
static void wordbounds(TextFieldWidget tf,
int index,
int *out_wleft,
int *out_wright) {
int spc = index < tf->textfield.length ? isspace(tf->textfield.buffer[index]) :
1;
int wleft, wright;
if(out_wleft) {
for(wleft=index;wleft>=
0;wleft--) {
if(isspace(tf->textfield.buffer[wleft]) != spc) {
wleft++;
break;
}
}
if(wleft <
0) wleft =
0;
*out_wleft = wleft;
}
if(out_wright) {
for(wright=index;wright<tf->textfield.length;wright++) {
if(isspace(tf->textfield.buffer[wright]) != spc) {
break;
}
}
*out_wright = wright;
}
}
static void deletePrevWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(tf->textfield.pos ==
0 || tf->textfield.length ==
0)
return;
int wleft;
int index = tf->textfield.pos >
0 ? tf->textfield.pos -
1 :
0;
for(
int n=
0;n<
2;n++) {
wordbounds(tf, index, &wleft,
NULL);
if(wleft == tf->textfield.length || !isspace(tf->textfield.buffer[wleft])) {
break;
}
index = wleft >
0 ? wleft -
1 :
0;
}
TFDelete(tf, wleft, tf->textfield.pos);
tf->textfield.pos = wleft;
tfRedrawText(tf);
}
static void deleteNextWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(tf->textfield.pos == tf->textfield.length || tf->textfield.length ==
0)
return;
int wright;
int index = tf->textfield.pos;
wordbounds(tf, index,
NULL, &wright);
TFDelete(tf, index, wright);
tfRedrawText(tf);
}
static void moveSelect(TextFieldWidget tf, String *args, Cardinal *nArgs,
int old_pos,
int new_pos) {
if(*nArgs ==
1) {
String d = args[
0];
if(tf->textfield.hasSelection) {
if(new_pos == (d[
0] ==
'l' ? tf->textfield.selStart : tf->textfield.selEnd)) {
tf->textfield.hasSelection =
0;
}
else if(d[
0] ==
'r' && new_pos < tf->textfield.selEnd) {
tfSetSelection(tf, new_pos, tf->textfield.selEnd);
}
else if(new_pos > tf->textfield.selStart) {
tfSetSelection(tf, tf->textfield.selStart, new_pos);
}
else {
tfSetSelection(tf, new_pos, tf->textfield.selEnd);
}
}
else {
tfSetSelection(tf, old_pos, new_pos);
}
}
else {
tf->textfield.hasSelection =
0;
}
}
static void moveLeftAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int old_pos = tf->textfield.pos;
tf->textfield.pos = TFLeftPos(tf);
moveSelect(tf, args, nArgs, old_pos, tf->textfield.pos);
tfRedrawText(tf);
}
static void moveRightAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int old_pos = tf->textfield.pos;
tf->textfield.pos = TFRightPos(tf);
moveSelect(tf, args, nArgs, old_pos, tf->textfield.pos);
tfRedrawText(tf);
}
static void moveLeftWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int pos = tf->textfield.pos;
int old_pos = pos;
int word =
0;
while(pos >
0) {
pos = TFLeftPos(tf);
if(isspace(tf->textfield.buffer[pos])) {
if(word) {
break;
}
}
else {
word =
1;
}
tf->textfield.pos = pos;
}
int new_pos = tf->textfield.pos;
moveSelect(tf, args, nArgs, old_pos, new_pos);
tfRedrawText(tf);
}
static void moveRightWordAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int old_pos = tf->textfield.pos;
int space =
0;
while(tf->textfield.pos < tf->textfield.length) {
tf->textfield.pos = TFRightPos(tf);
if(!isspace(tf->textfield.buffer[tf->textfield.pos])) {
if(space) {
break;
}
}
else {
space =
1;
}
}
int new_pos = tf->textfield.pos;
moveSelect(tf, args, nArgs, old_pos, new_pos);
tfRedrawText(tf);
}
static void blinkCB(XtPointer data, XtIntervalId *id) {
TextFieldWidget tf = data;
tf->textfield.cursorOn = !tf->textfield.cursorOn;
tfDrawCursor(tf);
tf->textfield.blinkProcId = XtAppAddTimeOut(
XtWidgetToApplicationContext((Widget)tf),
tf->textfield.blinkrate,
blinkCB,
tf);
}
static void focusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(!event->xfocus.send_event)
return;
tf->textfield.hasFocus =
1;
if(tf->textfield.xic) {
XSetICFocus(tf->textfield.xic);
}
XmAnyCallbackStruct cb;
cb.reason = XmCR_FOCUS;
cb.event = event;
XtCallCallbackList (w, tf->textfield.focusCB, (XtPointer) &cb);
if(tf->textfield.blinkProcId ==
0) {
tf->textfield.blinkProcId = XtAppAddTimeOut(
XtWidgetToApplicationContext(w),
tf->textfield.blinkrate,
blinkCB,
tf);
}
Region r =
NULL;
textfield_expose(w, event, r);
}
static void focusOutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(tf->textfield.xic) {
XUnsetICFocus(tf->textfield.xic);
}
if(tf->textfield.blinkProcId !=
0) {
XtRemoveTimeOut(tf->textfield.blinkProcId);
tf->textfield.blinkProcId =
0;
}
tf->textfield.cursorOn =
1;
tf->textfield.hasFocus =
0;
XmAnyCallbackStruct cb;
cb.reason = XmCR_LOSING_FOCUS;
cb.event = event;
XtCallCallbackList (w, tf->textfield.losingFocusCB, (XtPointer) &cb);
Region r =
NULL;
textfield_expose(w, event, r);
}
static void enterAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
}
static void leaveAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
}
static void cutAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(!tf->textfield.hasSelection)
return;
int from, to;
tfSelectionIndex(tf, &from, &to);
size_t len = to - from;
CopyStringToClipboard(w, event->xkey.time, tf->textfield.buffer + from, len);
TFDelete(tf, from, to);
tf->textfield.pos = from;
tfRedrawText(tf);
}
static void copyAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
if(!tf->textfield.hasSelection)
return;
int from, to;
tfSelectionIndex(tf, &from, &to);
size_t len = to - from;
CopyStringToClipboard(w, event->xkey.time, tf->textfield.buffer + from, len);
}
static void pasteAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
char *clipboard = GetClipboard(w);
if(!clipboard)
return;
int len = strlen(clipboard);
insertText(tf, clipboard, len, event);
XtFree(clipboard);
}
static void endLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int old_pos = tf->textfield.pos;
tf->textfield.pos = tf->textfield.length;
moveSelect(tf, args, nArgs, old_pos, tf->textfield.pos);
tfRedrawText(tf);
}
static void beginLineAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
int old_pos = tf->textfield.pos;
tf->textfield.pos =
0;
moveSelect(tf, args, nArgs,
0, old_pos);
tfRedrawText(tf);
}
static void selectAllAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
tfSetSelection(tf,
0, tf->textfield.length);
tfRedrawText(tf);
}
static void insertPrimaryAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
TextFieldWidget tf = (TextFieldWidget)w;
tfInsertPrimary(tf, event);
}
static int tfIndexToX(TextFieldWidget tf,
int pos) {
XftFont *font = tf->textfield.font->fonts->font;
const char *buf = tf->textfield.buffer;
size_t length = tf->textfield.length;
size_t start =
0;
XGlyphInfo extents;
int xoff = tf->textfield.textarea_xoff;
int i;
int charlen =
1;
for(i=
0;i<pos;i+=charlen) {
FcChar32 c;
charlen = Utf8ToUcs4(buf + i, &c, length - i);
XftFont *cFont = FindFont(tf->textfield.font, c);
if(c ==
'\t' || cFont != font) {
size_t drawLen = i - start;
if(drawLen >
0) {
XftTextExtentsUtf8(
XtDisplay(tf),
font,
(FcChar8*)buf + start,
drawLen,
&extents
);
xoff += extents.xOff;
start = i;
}
font = cFont;
if(c ==
'\t') {
start++;
XftTextExtentsUtf8(
XtDisplay(tf),
font,
(FcChar8*)
TF_TAB_STR,
sizeof(
TF_TAB_STR)-
1,
&extents
);
xoff += extents.xOff;
}
}
}
int drawLen = i - start;
if(drawLen >
0) {
XftTextExtentsUtf8(
XtDisplay(tf),
font,
(FcChar8*)buf + start,
drawLen,
&extents
);
xoff += extents.xOff;
}
return xoff;
}
static void tfSelection(TextFieldWidget tf,
int *start,
int *end,
int *startX,
int *endX) {
int selStart, selEnd, selStartX, selEndX;
if(tf->textfield.selStart > tf->textfield.selEnd) {
selStart = tf->textfield.selEnd;
selEnd = tf->textfield.selStart;
selStartX = tf->textfield.selEndX;
selEndX = tf->textfield.selStartX;
}
else {
selEnd = tf->textfield.selEnd;
selStart = tf->textfield.selStart;
selEndX = tf->textfield.selEndX;
selStartX = tf->textfield.selStartX;
}
if(start) *start = selStart;
if(end) *end = selEnd;
if(startX) *startX = selStartX;
if(endX) *endX = selEndX;
}
static void tfSelectionIndex(TextFieldWidget tf,
int *start,
int *end) {
tfSelection(tf, start, end,
NULL,
NULL);
}
static void tfSetSelection(TextFieldWidget tf,
int from,
int to) {
if(from > to) {
int f = from;
from = to;
to = f;
}
tf->textfield.selStart = from;
tf->textfield.selEnd = to > tf->textfield.length ? tf->textfield.length : to;
tf->textfield.selStartX = tfIndexToX(tf, tf->textfield.selStart);
tf->textfield.selEndX = tfIndexToX(tf, tf->textfield.selEnd);
ownSelection(tf);
}
static void tfClearSelection(TextFieldWidget tf) {
tf->textfield.hasSelection =
0;
tf->textfield.selStart =
0;
tf->textfield.selEnd =
0;
}
static void tfCalcCursorPos(TextFieldWidget tf) {
if(tf->textfield.pos == tf->textfield.posCalc)
return;
int xoff = tfIndexToX(tf, tf->textfield.pos);
tf->textfield.posX = xoff;
tf->textfield.posCalc = tf->textfield.pos;
int posX = tf->textfield.posX;
int margin = tf->textfield.textarea_xoff;
if(posX > tf->core.width + tf->textfield.scrollX - margin) {
tf->textfield.scrollX = -tf->core.width + posX + margin;
}
else if(posX < tf->textfield.scrollX + margin) {
tf->textfield.scrollX = posX - tf->textfield.textarea_xoff;
}
}
static int tfPosToIndex(TextFieldWidget tf,
int pos) {
const char *buf = tf->textfield.buffer;
size_t length = tf->textfield.length;
int p =
0;
int charlen =
1;
int i;
for(i=
0;i<length;i+=charlen) {
if(p == pos)
break;
FcChar32 c;
charlen = Utf8ToUcs4(buf + i, &c, length - i);
p++;
}
return i;
}
static int tfXToPos(TextFieldWidget tf,
int x) {
const char *buf = tf->textfield.buffer;
size_t length = tf->textfield.length;
x += tf->textfield.scrollX;
int pos =
0;
XGlyphInfo extents;
int xoff = tf->textfield.textarea_xoff;
int charlen =
1;
for(
int i=
0;i<length;i+=charlen) {
FcChar32 c;
charlen = Utf8ToUcs4(buf + i, &c, length - i);
const char *str;
size_t slen;
if(c ==
'\t') {
str =
TF_TAB_STR;
slen =
sizeof(
TF_TAB_STR)-
1;
}
else {
str = buf+i;
slen = charlen;
}
XftFont *font = FindFont(tf->textfield.font, c);
XftTextExtentsUtf8(
XtDisplay(tf),
font,
(FcChar8*)str,
slen,
&extents
);
xoff += extents.xOff;
if(xoff > x + (extents.xOff /
2)) {
break;
}
pos++;
}
return pos;
}
static void TFInsert(TextFieldWidget tf,
const char *chars,
size_t nchars) {
if(tf->textfield.length + nchars >= tf->textfield.alloc) {
tf->textfield.alloc +=
TF_BUF_BLOCK;
tf->textfield.buffer = XtRealloc(tf->textfield.buffer, tf->textfield.alloc);
}
if(tf->textfield.pos == tf->textfield.length) {
memcpy(tf->textfield.buffer + tf->textfield.length, chars, nchars);
}
else {
char *insertpos = tf->textfield.buffer + tf->textfield.pos;
memmove(insertpos + nchars, insertpos, tf->textfield.length - tf->textfield.pos);
memmove(insertpos, chars, nchars);
}
tf->textfield.length += nchars;
tf->textfield.pos += nchars;
tfClearSelection(tf);
}
static int TFLeftPos(TextFieldWidget tf) {
int left =
0;
int cur =
0;
while(cur < tf->textfield.pos) {
left = cur;
cur += Utf8CharLen((
unsigned char*)tf->textfield.buffer + cur);
}
return left;
}
static int TFRightPos(TextFieldWidget tf) {
int pos = tf->textfield.pos;
int right = pos + Utf8CharLen((
unsigned char*)tf->textfield.buffer + pos);
return right > tf->textfield.length ? tf->textfield.length : right;
}
static void TFDelete(TextFieldWidget tf,
int from,
int to) {
if(from >= to)
return;
if(to >= tf->textfield.length) {
tf->textfield.length = from;
return;
}
int len = tf->textfield.length - to;
memmove(tf->textfield.buffer + from, tf->textfield.buffer + to, len);
tf->textfield.length -= to - from;
tfClearSelection(tf);
}
struct PSelection {
TextFieldWidget tf;
XEvent *event;
char *xastring;
char *utf8string;
int target;
};
static void getPrimary(
Widget w,
XtPointer clientData,
Atom *selType,
Atom *type,
XtPointer value,
unsigned long *length,
int *format)
{
struct PSelection *sel = clientData;
sel->target++;
if(value && *format ==
8 && *length >
0 && (*type ==
XA_STRING || *type == aUtf8String)) {
char *str = XtMalloc((*length) +
1);
memcpy(str, value, *length);
str[*length] =
0;
if(*type == aUtf8String) {
sel->utf8string = str;
}
else {
sel->xastring = str;
}
}
if(sel->target ==
2) {
char *insert = sel->utf8string ? sel->utf8string : sel->xastring;
if(insert) {
insertText(sel->tf, insert, strlen(insert), sel->event);
}
if(sel->utf8string) {
XtFree(sel->utf8string);
}
if(sel->xastring) {
XtFree(sel->xastring);
}
XtFree((
void*)sel);
}
}
static void tfInsertPrimary(TextFieldWidget tf, XEvent *event) {
struct PSelection *sel = (
void*)XtMalloc(
sizeof(
struct PSelection));
sel->tf = tf;
sel->event = event;
sel->target =
0;
sel->xastring =
NULL;
sel->utf8string =
NULL;
Atom targets[
2] = {aUtf8String,
XA_STRING};
Time time = XtLastTimestampProcessed(XtDisplay((Widget)tf));
void *data[
2] = { sel, sel };
#ifdef __APPLE__
XtGetSelectionValue((Widget)tf,
XA_PRIMARY, targets[
0], getPrimary, sel, time);
XtGetSelectionValue((Widget)tf,
XA_PRIMARY, targets[
1], getPrimary, sel, time);
#else
XtGetSelectionValues((Widget)tf,
XA_PRIMARY, targets,
2, getPrimary, data, time);
#endif
}
static Boolean convertSelection(
Widget w,
Atom *seltype,
Atom *target,
Atom *type,
XtPointer *value,
unsigned long *length,
int *format)
{
TextFieldWidget tf = (TextFieldWidget)w;
if(*target == aTargets) {
Atom *retTargets = calloc(
3,
sizeof(Atom));
retTargets[
0] =
XA_STRING;
retTargets[
1] = aUtf8String;
retTargets[
2] = aTargets;
*type =
XA_ATOM;
*value = retTargets;
*length =
3;
*format =
32;
return True;
}
if(*target ==
XA_STRING || *target == aUtf8String) {
char *selectedText =
NULL;
size_t len =
0;
if(tf->textfield.hasSelection) {
int from, to;
tfSelectionIndex(tf, &from, &to);
len = to - from;
selectedText = XtMalloc(len +
1);
memcpy(selectedText, tf->textfield.buffer + from, len);
selectedText[len] =
0;
}
else {
selectedText = XtMalloc(
4);
selectedText[
0] =
0;
}
*type = *target == aUtf8String ? aUtf8String :
XA_STRING;
*value = selectedText;
*length = len;
*format =
8;
return True;
}
return False;
}
static void loseSelection(Widget w, Atom *type) {
TextFieldWidget tf = (TextFieldWidget)w;
tfClearSelection(tf);
tfRedrawText(tf);
}
void XNETextFieldSetString(Widget widget,
char *value) {
if(!value) {
value =
"";
}
size_t len = strlen(value);
TextFieldWidget tf = (TextFieldWidget)widget;
if(len > tf->textfield.alloc) {
size_t alloc = len +
TF_BUF_BLOCK - (len %
TF_BUF_BLOCK);
tf->textfield.buffer = XtRealloc(tf->textfield.buffer, alloc);
tf->textfield.alloc = alloc;
}
memcpy(tf->textfield.buffer, value, len);
tf->textfield.length = len;
tf->textfield.pos =
0;
tfClearSelection(tf);
if(XtIsRealized(widget)) {
tfRedrawText(tf);
}
}
char* XNETextFieldGetString(Widget widget) {
TextFieldWidget tf = (TextFieldWidget)widget;
char *r = XtMalloc(tf->textfield.length +
1);
memcpy(r, tf->textfield.buffer, tf->textfield.length);
r[tf->textfield.length] =
'\0';
return r;
}
XmTextPosition XNETextFieldGetLastPosition(Widget widget) {
TextFieldWidget tf = (TextFieldWidget)widget;
return tf->textfield.length;
}
void XNETextFieldSetInsertionPosition(Widget widget, XmTextPosition i) {
TextFieldWidget tf = (TextFieldWidget)widget;
tf->textfield.pos = i <= tf->textfield.length ? i : tf->textfield.length;
if(XtIsRealized(widget)) {
tfRedrawText(tf);
}
}
void XNETextFieldSetSelection(Widget w, XmTextPosition first, XmTextPosition last, Time sel_time) {
TextFieldWidget tf = (TextFieldWidget)w;
tfSetSelection(tf, first, last);
if(XtIsRealized(w)) {
tfRedrawText(tf);
}
}