chimera2/mxw/TextField.c

1641 lines
40 KiB
C

/*-----------------------------------------------------------------------------
* TextField A single line text entry widget
*
* Copyright (c) 1995 Robert W. McMullen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
* Author: Rob McMullen <rwmcm@orion.ae.utexas.edu>
* http://www.ae.utexas.edu/~rwmcm
*/
#define _TextField_
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Xmu.h>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include "TextFieldP.h"
#define offset(field) XtOffsetOf(TextFieldRec, text.field)
static XtResource resources[] =
{
{XtNallowSelection, XtCBoolean, XtRBoolean, sizeof(Boolean),
offset(AllowSelection), XtRString, "True"},
{XtNdisplayCaret, XtCBoolean, XtRBoolean, sizeof(Boolean),
offset(DisplayCursor), XtRString, "True"},
{XtNecho, XtCBoolean, XtRBoolean, sizeof(Boolean),
offset(Echo), XtRString, "True"},
{XtNeditable, XtCBoolean, XtRBoolean, sizeof(Boolean),
offset(Editable), XtRString, "True"},
{XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
offset(font), XtRString, XtDefaultFont},
{XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
offset(foreground_pixel), XtRString, XtDefaultForeground},
{XtNinsertPosition, XtCInsertPosition, XtRInt, sizeof(int),
offset(CursorPos), XtRString, "0"},
{XtNlength, XtCLength, XtRInt, sizeof(int),
offset(TextMaxLen), XtRString, "0"},
{XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
offset(Margin), XtRString, "3"},
{XtNpendingDelete, XtCBoolean, XtRBoolean, sizeof(Boolean),
offset(PendingDelete), XtRString, "True"},
{XtNstring, XtCString, XtRString, sizeof(char *),
offset(DefaultString), XtRString, NULL},
{XtNactivateCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(ActivateCallback), XtRCallback, NULL},
};
#undef offset
static void Initialize();
static void Destroy();
static void Redisplay();
static void Resize();
static Boolean SetValues();
static void Draw(), DrawInsert(), MassiveChangeDraw(), DrawTextReposition(),
ClearHighlight(), DrawHighlight(), DrawCursor(), EraseCursor();
static Boolean PositionCursor(), MassiveCursorAdjust();
static void Nothing(), Activate(), InsertChar(), ForwardChar(),
ForwardToEnd(), BackwardChar(), BackwardToStart(), DeleteNext(),
DeleteToEnd(), DeletePrev(), DeleteToStart(), DeleteHighlighted(),
TransposeChars(), SelectStart(), ExtendStart(), ExtendAdjust(),
ExtendEnd(), InsertSelection();
static char defaultTranslations[] =
"<Key>Right: forward-char()\n\
<Key>KP_Right: forward-char()\n\
Ctrl<Key>f: forward-char()\n\
Ctrl<Key>e: forward-to-end()\n\
<Key>Left: backward-char()\n\
<Key>KP_Left: backward-char()\n\
Ctrl<Key>b: backward-char()\n\
Ctrl<Key>a: backward-to-start()\n\
Ctrl<Key>d: delete-next-char()\n\
Ctrl<Key>k: delete-to-end()\n\
<Key>Delete: delete-previous-char()\n\
<Key>BackSpace: delete-previous-char()\n\
Ctrl<Key>h: delete-previous-char()\n\
Ctrl<Key>u: delete-to-start()\n\
Ctrl<Key>w: delete-highlighted()\n\
Ctrl<Key>t: transpose-chars()\n\
<Key>Return: activate()\n\
<Key>: insert-char()\n\
Shift<Btn1Down>: extend-start()\n\
<Btn1Down>: select-start()\n\
<Btn1Motion>: extend-adjust()\n\
<Btn1Up>: extend-end()\n\
<Btn2Down>: insert-selection()\n\
<Btn3Down>: extend-start()\n\
<Btn3Motion>: extend-adjust()\n\
<Btn3Up>: extend-end()\n\
<EnterWindow>: enter-window()\n\
<LeaveWindow>: leave-window()\n\
<FocusIn>: focus-in()\n\
<FocusOut>: focus-out()";
static XtActionsRec actions[] =
{
{"insert-char", InsertChar},
{"forward-char", ForwardChar},
{"forward-to-end", ForwardToEnd},
{"backward-char", BackwardChar},
{"backward-to-start", BackwardToStart},
{"delete-next-char", DeleteNext},
{"delete-to-end", DeleteToEnd},
{"delete-previous-char", DeletePrev},
{"delete-to-start", DeleteToStart},
{"delete-highlighted", DeleteHighlighted},
{"transpose-chars", TransposeChars},
{"activate", Activate},
{"select-start", SelectStart},
{"extend-start", ExtendStart},
{"extend-adjust", ExtendAdjust},
{"extend-end", ExtendEnd},
{"insert-selection", InsertSelection},
{"enter-window", Nothing},
{"leave-window", Nothing},
{"focus-in", Nothing},
{"focus-out", Nothing},
};
TextFieldClassRec textfieldClassRec =
{
{
/* core_class fields */
/* superclass */ (WidgetClass) & widgetClassRec,
/* class_name */ "TextField",
/* widget_size */ sizeof(TextFieldRec),
/* class_initialize */ NULL,
/* class_part_initialize */ NULL,
/* class_inited */ False,
/* initialize */ Initialize,
/* initialize_hook */ NULL,
/* realize */ XtInheritRealize,
/* actions */ actions,
/* num_actions */ XtNumber(actions),
/* resources */ resources,
/* num_resources */ XtNumber(resources),
/* xrm_class */ NULLQUARK,
/* compress_motion */ True,
/* compress_exposure */ XtExposeCompressMultiple,
/* compress_enterleave */ True,
/* visible_interest */ True,
/* destroy */ Destroy,
/* resize */ Resize,
/* expose */ Redisplay,
/* set_values */ SetValues,
/* set_values_hook */ NULL,
/* set_values_almost */ XtInheritSetValuesAlmost,
/* get_values_hook */ NULL,
/* accept_focus */ NULL,
/* version */ XtVersion,
/* callback_private */ NULL,
/* tm_table */ defaultTranslations,
/* query_geometry */ XtInheritQueryGeometry,
/* display_accelerator */ XtInheritDisplayAccelerator,
/* extension */ NULL
},
{
0 /* some stupid compilers barf on empty structures */
}
};
WidgetClass textfieldWidgetClass = (WidgetClass) & textfieldClassRec;
/* Convenience macros */
#define TopMargin(w) (int)(w->text.Margin - 1)
#define BottomMargin(w) (int)(w->text.Margin)
/* Font functions */
#define FontHeight(f) (int)(f->max_bounds.ascent + f->max_bounds.descent)
#define FontDescent(f) (int)(f->max_bounds.descent)
#define FontAscent(f) (int)(f->max_bounds.ascent)
#define FontTextWidth(f,c,l) (int)XTextWidth(f, c, l)
static void
InitializeGC(TextFieldWidget w)
{
static char dots[] =
{2, 1, 1};
XGCValues values;
XtGCMask mask;
values.line_style = LineSolid;
values.line_width = 0;
values.fill_style = FillSolid;
values.font = w->text.font->fid;
values.background = w->core.background_pixel;
values.foreground = w->text.foreground_pixel;
mask = GCLineStyle | GCLineWidth | GCFillStyle | GCForeground | GCBackground | GCFont;
w->text.drawGC = XtGetGC((Widget) w, mask, &values);
values.foreground = w->core.background_pixel;
values.background = w->text.foreground_pixel;
w->text.highlightGC = XtGetGC((Widget) w, mask, &values);
values.line_style = LineSolid;
values.line_width = 0;
values.background = w->core.background_pixel;
values.foreground = w->text.foreground_pixel;
mask = GCLineStyle | GCLineWidth | GCForeground | GCBackground;
w->text.cursorGC = XtGetGC((Widget) w, mask, &values);
values.foreground = w->core.background_pixel;
values.background = w->text.foreground_pixel;
w->text.eraseGC = XtGetGC((Widget) w, mask, &values);
values.line_style = LineOnOffDash;
values.background = w->core.background_pixel;
values.foreground = w->text.foreground_pixel;
w->text.dashGC = XtGetGC((Widget) w, mask, &values);
XSetDashes(XtDisplay(w), w->text.dashGC, 0, &dots[1],
(int) dots[0]);
w->text.YOffset = TopMargin(w) + FontAscent(w->text.font);
}
static void
ClipGC(TextFieldWidget w)
{
XRectangle clip;
clip.x = 0;
clip.y = w->text.YOffset - FontAscent(w->text.font) + 1;
clip.width = w->text.ViewWidth + 1;
clip.height = FontHeight(w->text.font);
XSetClipRectangles(XtDisplay((Widget) w), w->text.drawGC,
w->text.Margin, 0, &clip, 1, Unsorted);
XSetClipRectangles(XtDisplay((Widget) w), w->text.highlightGC,
w->text.Margin, 0, &clip, 1, Unsorted);
}
static void
SetString(TextFieldWidget w, char *s)
{
int len;
if (s) {
len = strlen(s);
if (len > w->text.TextAlloc) {
w->text.TextAlloc += len;
w->text.Text = XtRealloc(w->text.Text, w->text.TextAlloc);
}
strcpy(w->text.Text, s);
w->text.TextLen = len;
w->text.TextWidth = w->text.OldTextWidth =
FontTextWidth(w->text.font, w->text.Text,
w->text.TextLen);
if ((w->text.TextMaxLen > 0) && (w->text.TextLen > w->text.TextMaxLen))
w->text.TextMaxLen = w->text.TextLen;
}
w->text.DefaultString = w->text.Text;
}
static void
Initialize(Widget treq, Widget tnew, ArgList args, Cardinal * num)
{
TextFieldWidget new;
int height;
new = (TextFieldWidget) tnew;
new->text.timer_id = (XtIntervalId) 0;
new->text.multi_click_time = XtGetMultiClickTime(XtDisplay((Widget) new));
new->text.highlight_time = new->text.multi_click_time / 2;
if (new->text.TextMaxLen > 0) {
new->text.TextAlloc = new->text.TextMaxLen + 1;
}
else {
new->text.TextAlloc = TEXTFIELD_ALLOC_SIZE;
}
new->text.Text = (char *) XtMalloc(new->text.TextAlloc);
new->text.TextLen = 0;
new->text.SelectionText = NULL;
new->text.TextWidth = new->text.OldTextWidth = 0;
if (new->text.DefaultString)
SetString(new, new->text.DefaultString);
if (new->text.CursorPos > 0) {
if (new->text.CursorPos > new->text.TextLen) {
new->text.CursorPos = new->text.TextLen;
}
}
else {
new->text.CursorPos = 0;
}
new->text.OldCursorX = -1;
new->text.HighlightStart = new->text.HighlightEnd = -1;
new->text.OldHighlightStart = new->text.OldHighlightEnd = -1;
height = FontHeight(new->text.font);
if (new->core.height == 0)
new->core.height = (Dimension) height + TopMargin(new) + BottomMargin(new);
if (new->core.width == 0) {
new->text.ViewWidth = 200;
new->core.width = new->text.ViewWidth + 2 * new->text.Margin;
}
else {
int width;
width = (int) new->core.width - 2 * new->text.Margin;
if (width < 0)
new->text.ViewWidth = new->core.width;
else
new->text.ViewWidth = width;
}
new->text.XOffset = new->text.OldXOffset = 0;
InitializeGC(new);
ClipGC(new);
}
static void
Destroy(TextFieldWidget w)
{
XtReleaseGC((Widget) w, w->text.drawGC);
XtReleaseGC((Widget) w, w->text.highlightGC);
if (w->text.SelectionText)
XtFree(w->text.SelectionText);
XtFree(w->text.Text);
}
static void
Redisplay(Widget aw, XExposeEvent * event, Region region)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!XtIsRealized(aw))
return;
Draw(w);
}
static Boolean
SetValues(Widget current, Widget request, Widget reply,
ArgList args, Cardinal * nargs)
{
TextFieldWidget w = (TextFieldWidget) current;
TextFieldWidget new = (TextFieldWidget) reply;
Boolean redraw = False;
if ((w->text.foreground_pixel != new->text.foreground_pixel) ||
(w->core.background_pixel != new->core.background_pixel) ||
(w->text.font != new->text.font)) {
XtReleaseGC((Widget) w, w->text.drawGC);
XtReleaseGC((Widget) w, w->text.highlightGC);
XtReleaseGC((Widget) w, w->text.cursorGC);
XtReleaseGC((Widget) w, w->text.eraseGC);
XtReleaseGC((Widget) w, w->text.dashGC);
InitializeGC(new);
redraw = True;
}
if ((w->text.CursorPos != new->text.CursorPos) ||
(w->text.DisplayCursor != new->text.DisplayCursor)) {
redraw = True;
}
if (w->text.DefaultString != new->text.DefaultString) {
redraw = True;
SetString(new, new->text.DefaultString);
new->text.HighlightStart = new->text.HighlightEnd = -1;
new->text.CursorPos = new->text.TextLen;
#ifdef DEBUG_TF
printf("SetValues: %s\n", new->text.DefaultString);
#endif
}
return redraw;
}
static void
Resize(Widget aw)
{
TextFieldWidget w = (TextFieldWidget) aw;
int width, height;
if (!XtIsRealized(aw))
return;
width = w->core.width - 2 * w->text.Margin;
if (width < 0)
w->text.ViewWidth = w->core.width;
else
w->text.ViewWidth = width;
height = (((int) w->core.height - FontHeight(w->text.font)) / 2) +
FontAscent(w->text.font);
w->text.YOffset = height;
ClipGC(w);
MassiveChangeDraw(w);
}
static void
TextDelete(TextFieldWidget w, int start, int len)
{
int i;
if (len > 0) {
for (i = start + len; i < w->text.TextLen; i++)
w->text.Text[i - len] = w->text.Text[i];
w->text.TextLen -= len;
w->text.TextWidth = FontTextWidth(w->text.font, w->text.Text,
w->text.TextLen);
w->text.Text[w->text.TextLen] = 0;
}
}
static void
TextDeleteHighlighted(TextFieldWidget w)
{
if (w->text.HighlightStart >= 0) {
TextDelete(w, w->text.HighlightStart,
w->text.HighlightEnd - w->text.HighlightStart);
w->text.CursorPos = w->text.HighlightStart;
w->text.HighlightStart = w->text.HighlightEnd = -1;
}
}
/* returns value indicating if the text can be redrawn using the fast
* method */
static Boolean
TextInsert(TextFieldWidget w, char *buf, int len)
{
int i;
Boolean regular_copy, fast_insert;
fast_insert = True;
if (len > 0) {
if (w->text.HighlightStart >= 0) {
fast_insert = False;
if (w->text.PendingDelete)
TextDeleteHighlighted(w);
else
ClearHighlight(w);
}
regular_copy = True;
if (w->text.TextMaxLen > 0) {
if (w->text.TextLen + len > w->text.TextMaxLen)
regular_copy = False;
}
else if (w->text.TextLen + len > w->text.TextAlloc) {
i = TEXTFIELD_ALLOC_SIZE;
if (i < len)
i = len;
w->text.TextAlloc += i + 1;
w->text.Text = XtRealloc(w->text.Text, w->text.TextAlloc);
#ifdef DEBUG_TF
printf("TextInsert: Alloced new space: %d bytes\n", w->text.TextAlloc);
#endif
}
if (regular_copy) {
for (i = w->text.TextLen - 1; i >= w->text.CursorPos; i--)
w->text.Text[i + len] = w->text.Text[i];
strncpy(&w->text.Text[w->text.CursorPos], buf, len);
w->text.FastInsertCursorStart = w->text.CursorPos;
w->text.FastInsertTextLen = len;
w->text.TextLen += len;
w->text.CursorPos += len;
}
else {
int i1;
for (i = w->text.TextLen - 1; i >= w->text.CursorPos; i--)
if (i + len < w->text.TextMaxLen)
w->text.Text[i + len] = w->text.Text[i];
w->text.TextLen += len;
if (w->text.TextLen > w->text.TextMaxLen)
w->text.TextLen = w->text.TextMaxLen;
i1 = w->text.CursorPos;
for (i = 0; i < len; i++) {
if (i1 < w->text.TextMaxLen)
w->text.Text[i1] = *buf++;
else
break;
i1++;
}
w->text.FastInsertCursorStart = w->text.CursorPos;
w->text.FastInsertTextLen = i1 - w->text.CursorPos;
w->text.CursorPos = i1;
}
w->text.TextWidth = FontTextWidth(w->text.font,
w->text.Text, w->text.TextLen);
w->text.Text[w->text.TextLen] = 0;
}
return fast_insert;
}
static int
TextPixelToPos(TextFieldWidget w, int x)
{
int i, tot, cur, pos;
pos = 0;
x -= (int) w->text.Margin + w->text.XOffset;
/* check if the cursor is before the 1st character */
if (x <= 0) {
pos = 0;
}
/* OK, how 'bout after the last character */
else if (x > FontTextWidth(w->text.font, w->text.Text, w->text.TextLen)) {
pos = w->text.TextLen;
}
/* must be in between somewhere... */
else {
tot = 0;
pos = -1;
for (i = 0; i < w->text.TextLen; i++) {
cur = FontTextWidth(w->text.font, &w->text.Text[i], 1);
if (x < tot + (cur / 2)) {
pos = i;
break;
}
tot += cur;
}
if (pos < 0)
pos = w->text.TextLen;
}
return pos;
}
/*
* TextField Widget Action procedures
*/
/* ARGSUSED */
static void
Nothing(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
}
/* ARGSUSED */
static void
Activate(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
TextFieldReturnStruct ret;
ret.reason = 0;
ret.event = event;
ret.string = w->text.Text;
if (XtNactivateCallback)
XtCallCallbacks(aw, XtNactivateCallback, &ret);
}
/* ARGSUSED */
static void
ForwardChar(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
ClearHighlight(w);
if (w->text.CursorPos < w->text.TextLen) {
w->text.CursorPos++;
EraseCursor(w);
if (PositionCursor(w))
DrawTextReposition(w);
DrawCursor(w);
}
}
/* ARGSUSED */
static void
ForwardToEnd(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
ClearHighlight(w);
if (w->text.CursorPos < w->text.TextLen) {
w->text.CursorPos = w->text.TextLen;
EraseCursor(w);
if (PositionCursor(w))
DrawTextReposition(w);
DrawCursor(w);
}
}
/* ARGSUSED */
static void
BackwardChar(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
ClearHighlight(w);
if (w->text.CursorPos > 0) {
w->text.CursorPos--;
EraseCursor(w);
if (PositionCursor(w))
DrawTextReposition(w);
DrawCursor(w);
}
}
/* ARGSUSED */
static void
BackwardToStart(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
ClearHighlight(w);
if (w->text.CursorPos > 0) {
w->text.CursorPos=0;
EraseCursor(w);
if (PositionCursor(w))
DrawTextReposition(w);
DrawCursor(w);
}
}
/* ARGSUSED */
static void
InsertChar(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
int len, i, j;
#define INSERTCHARBUFSIZ 32
char buf[INSERTCHARBUFSIZ];
if (!w->text.Editable)
return;
len = XLookupString((XKeyEvent *) event, buf, BUFSIZ, NULL, NULL);
/* Throw away characters with values < Space (32) */
for (i = 0; i < len; i++) {
if (buf[i] < 32) {
for (j = 0; j < len && j < (BUFSIZ - 1); j++) {
buf[j] = buf[j+1];
}
buf[j] = '\0';
len-=1;
}
}
if (len > 0) {
EraseCursor(w);
if (TextInsert(w, buf, len))
DrawInsert(w);
else
Draw(w);
}
}
/* ARGSUSED */
static void
DeleteNext(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
if (w->text.CursorPos < w->text.TextLen) {
ClearHighlight(w);
TextDelete(w, w->text.CursorPos, 1);
Draw(w);
}
}
/* ARGSUSED */
static void
DeleteToEnd(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
if (w->text.CursorPos < w->text.TextLen) {
ClearHighlight(w);
TextDelete(w, w->text.CursorPos, w->text.TextLen-w->text.CursorPos);
Draw(w);
}
}
/* ARGSUSED */
static void
DeletePrev(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
if (w->text.CursorPos > 0) {
ClearHighlight(w);
TextDelete(w, w->text.CursorPos - 1, 1);
w->text.CursorPos--;
Draw(w);
}
}
/* ARGSUSED */
static void
DeleteToStart(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
if (w->text.CursorPos > 0) {
ClearHighlight(w);
TextDelete(w, 0, w->text.CursorPos);
w->text.CursorPos=0;
Draw(w);
}
}
/* ARGSUSED */
static void
DeleteHighlighted(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.Editable)
return;
if (w->text.HighlightStart >= 0 && w->text.PendingDelete) {
TextDeleteHighlighted(w);
MassiveChangeDraw(w);
}
}
/* ARGSUSED */
static void
TransposeChars(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
char c;
if (!w->text.Editable)
return;
ClearHighlight(w);
if (w->text.CursorPos > 0 && w->text.CursorPos < w->text.TextLen) {
c = w->text.Text[w->text.CursorPos -1];
TextDelete(w, w->text.CursorPos - 1, 1);
TextInsert(w, &c, 1);
Draw(w);
}
}
/* ARGSUSED */
static void
SelectStart(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.AllowSelection)
return;
w->text.CursorPos = TextPixelToPos(w, event->xbutton.x);
w->text.HighlightPivotStart =
w->text.HighlightPivotEnd = w->text.CursorPos;
if (w->text.HighlightStart >= 0) {
ClearHighlight(w);
}
else {
EraseCursor(w);
DrawCursor(w);
}
}
/* ARGSUSED */
static void
ExtendStart(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
int pos;
if (!w->text.AllowSelection)
return;
pos = TextPixelToPos(w, event->xbutton.x);
EraseCursor(w);
if (w->text.HighlightStart < 0) {
w->text.HighlightStart =
w->text.HighlightEnd =
w->text.HighlightPivotStart =
w->text.HighlightPivotEnd = w->text.CursorPos;
}
else {
w->text.HighlightPivotStart = w->text.HighlightStart;
w->text.HighlightPivotEnd = w->text.HighlightEnd;
}
if (pos < w->text.HighlightStart) {
w->text.HighlightStart = pos;
}
else {
w->text.HighlightEnd = pos;
}
w->text.CursorPos = pos;
#ifdef DEBUG_TF
printf("ExtendStart: %d - %d\n", w->text.HighlightStart,
w->text.HighlightEnd);
#endif
DrawHighlight(w);
DrawCursor(w);
}
static void
ExtendHighlight(TextFieldWidget w)
{
int x, pos;
if (!w->text.AllowSelection)
return;
x = w->text.timer_x;
pos = TextPixelToPos(w, x);
if (x < (int) w->text.Margin) {
pos = TextPixelToPos(w, (int) 0);
if (pos > 0)
pos--;
else if (pos == w->text.CursorPos)
return;
}
else if (x > (int) (w->text.Margin + w->text.ViewWidth)) {
pos = TextPixelToPos(w, (int) (w->text.Margin + w->text.ViewWidth));
if (pos < w->text.TextLen)
pos++;
else if (pos == w->text.CursorPos)
return;
}
if (pos == w->text.CursorPos)
return;
EraseCursor(w);
if (pos <= w->text.HighlightPivotStart) {
w->text.HighlightStart = pos;
w->text.HighlightEnd = w->text.HighlightPivotEnd;
}
else {
w->text.HighlightStart = w->text.HighlightPivotStart;
w->text.HighlightEnd = pos;
}
w->text.CursorPos = pos;
#ifdef DEBUG_TF
printf("Highlighting: x=%d pos=%d %d - %d\n", x, pos, w->text.HighlightStart,
w->text.HighlightEnd);
#endif
if (PositionCursor(w))
DrawTextReposition(w);
DrawHighlight(w);
DrawCursor(w);
}
static void
ExtendTimer(XtPointer client_data, XtIntervalId * idp)
{
TextFieldWidget w = (TextFieldWidget) client_data;
ExtendHighlight(w);
w->text.timer_id = XtAppAddTimeOut(
XtWidgetToApplicationContext((Widget) w),
(unsigned long) w->text.highlight_time,
ExtendTimer,
(XtPointer) w);
}
/* ARGSUSED */
static void
ExtendAdjust(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!w->text.AllowSelection)
return;
w->text.timer_x = event->xbutton.x;
if (event->xbutton.x < w->text.Margin || event->xbutton.x > w->text.Margin + w->text.ViewWidth) {
if (w->text.timer_id)
ExtendHighlight(w);
else
ExtendTimer((XtPointer) w, (XtIntervalId) 0);
}
else {
if (w->text.timer_id) {
XtRemoveTimeOut(w->text.timer_id);
w->text.timer_id = (XtIntervalId) 0;
}
ExtendHighlight(w);
}
}
/* ARGSUSED */
static Boolean
ConvertSelection(Widget aw, Atom * selection, Atom * target, Atom * type,
XtPointer * value, unsigned long *length, int *format)
{
TextFieldWidget w = (TextFieldWidget) aw;
XSelectionRequestEvent *req = XtGetSelectionRequest(aw, *selection, NULL);
if (*target == XA_TARGETS(XtDisplay(aw))) {
Atom *targetP;
char *std_targets;
unsigned long std_length;
XmuConvertStandardSelection(aw, req->time, selection,
target, type, &std_targets,
&std_length, format);
assert(*format == sizeof(Atom) * 8);
*value = XtMalloc((unsigned) sizeof(Atom) * (std_length + 1));
targetP = *(Atom **) value;
*length = std_length + 1;
*targetP++ = XA_STRING;
memmove((char *) targetP, (char *) std_targets, sizeof(Atom) * std_length);
XtFree((char *) std_targets);
*type = XA_ATOM;
*format = sizeof(Atom) * 8;
return True;
}
else if (*target == XA_STRING) {
*length = (long) w->text.SelectionLen;
*value = w->text.SelectionText;
*type = XA_STRING;
*format = 8;
return True;
}
return False;
}
/* ARGSUSED */
static void
LoseSelection(Widget aw, Atom * selection)
{
TextFieldWidget w = (TextFieldWidget) aw;
ClearHighlight(w);
}
/* ARGSUSED */
static void
ExtendEnd(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
int len;
if (!w->text.AllowSelection)
return;
if (w->text.timer_id) {
XtRemoveTimeOut(w->text.timer_id);
w->text.timer_id = (XtIntervalId) 0;
}
len = w->text.HighlightEnd - w->text.HighlightStart;
if (len > 0) {
w->text.SelectionLen = len;
if (w->text.SelectionText)
XtFree(w->text.SelectionText);
w->text.SelectionText = XtMalloc(len);
strncpy(w->text.SelectionText, &w->text.Text[w->text.HighlightStart], len);
XtOwnSelection(aw, XA_PRIMARY, event->xbutton.time,
ConvertSelection, LoseSelection, NULL);
XChangeProperty(XtDisplay(aw), DefaultRootWindow(XtDisplay(aw)),
XA_CUT_BUFFER0, XA_STRING, 8, PropModeReplace,
(unsigned char *) w->text.SelectionText, len);
}
}
/* ARGSUSED */
static void
RequestSelection(Widget aw, XtPointer client, Atom * selection, Atom * type,
XtPointer value, unsigned long *length, int *format)
{
TextFieldWidget w = (TextFieldWidget) aw;
if ((value == NULL) || (*length == 0)) {
#ifdef DEBUG_TF
printf("RequestSelection: no selection\n");
#endif
}
else {
int savex;
ClearHighlight(w);
savex = w->text.OldCursorX;
// This is safe because we passed this in as an int
w->text.CursorPos = (intptr_t) client;
#ifdef DEBUG_TF
printf("RequestSelection: inserting %s length=%d at pos: %d\n",
(char *) value, (int) (*length), w->text.CursorPos);
#endif
TextInsert(w, (char *) value, (int) (*length));
w->text.OldCursorX = savex;
Draw(w);
}
}
/* ARGSUSED */
static void
InsertSelection(Widget aw, XEvent * event, String * params, Cardinal * num_params)
{
TextFieldWidget w = (TextFieldWidget) aw;
int pos;
if (!w->text.AllowSelection)
return;
pos = TextPixelToPos(w, event->xbutton.x);
#ifdef DEBUG_TF
printf("InsertSelection: event at pos: %d\n", pos);
#endif
// The cast to intptr_t assumes that pointers are at least as wide as
// ints are, which is pretty safe assumption
XtGetSelectionValue(aw, XA_PRIMARY, XA_STRING,
RequestSelection,
(XtPointer) (intptr_t)pos, event->xbutton.time);
}
/*
* TextField private drawing functions
*/
static Boolean
PositionCursor(TextFieldWidget w)
{
int x, start, end;
Boolean moved;
moved = False;
if (w->text.CursorPos < 0)
w->text.CursorPos = 0;
else if (w->text.CursorPos > w->text.TextLen)
w->text.CursorPos = w->text.TextLen;
x = FontTextWidth(w->text.font, w->text.Text, w->text.CursorPos);
start = -w->text.XOffset;
end = start + w->text.ViewWidth;
if (x < start) {
w->text.XOffset = -x;
moved = True;
}
else if (x > end) {
w->text.XOffset = w->text.ViewWidth - x;
moved = True;
}
return moved;
}
static Boolean
MassiveCursorAdjust(TextFieldWidget w)
{
int start, end, last;
Boolean moved;
moved = False;
end = FontTextWidth(w->text.font, w->text.Text, w->text.CursorPos);
if (w->text.HighlightStart >= 0)
start = FontTextWidth(w->text.font, w->text.Text, w->text.HighlightStart);
else
start = end;
if (end < w->text.ViewWidth) {
if (w->text.XOffset < 0) {
w->text.XOffset = 0;
moved = True;
}
}
else if (start >= w->text.XOffset && end < w->text.XOffset + w->text.ViewWidth)
return moved;
else {
last = FontTextWidth(w->text.font, w->text.Text, w->text.TextLen);
if (start - end > w->text.ViewWidth) {
if (last - end > w->text.ViewWidth)
w->text.XOffset = w->text.ViewWidth - last;
else
w->text.XOffset = w->text.ViewWidth - end;
}
else if (end > w->text.ViewWidth)
w->text.XOffset = w->text.ViewWidth - end;
else
w->text.XOffset = 0;
moved = True;
}
return moved;
}
/*
* Actually draw a range of text onto the widget
*/
static void
DrawText(TextFieldWidget w, int start, int end, Boolean highlight)
{
int x;
GC gc;
if (!w->text.Echo)
return;
if (w->text.TextLen > 0) {
if (start < 0)
return;
else if (end < start) {
int temp;
temp = start;
start = end;
end = temp;
}
if (end <= w->text.TextLen) {
x = w->text.Margin + w->text.XOffset +
FontTextWidth(w->text.font, w->text.Text, start);
if (highlight)
gc = w->text.highlightGC;
else
gc = w->text.drawGC;
XDrawImageString(XtDisplay(w), XtWindow(w), gc,
x, w->text.YOffset,
&w->text.Text[start], end - start);
}
}
}
static void
DrawTextRange(TextFieldWidget w, int start, int end)
{
if (!w->text.Echo)
return;
if (w->text.TextLen > 0) {
if (start < 0)
return;
else if (end < start) {
int temp;
temp = start;
start = end;
end = temp;
}
/* If there is no highlighting, or the refresh area doesn't cross the */
/* the highlight borders, just redraw it. */
if (w->text.HighlightStart < 0 ||
start >= w->text.HighlightEnd ||
end <= w->text.HighlightStart) {
DrawText(w, start, end, False);
}
/* OK, the refresh area crosses one or both highlight borders. */
else {
int clip;
while (start < end) {
if (start < w->text.HighlightStart) {
if (end <= w->text.HighlightStart)
clip = end;
else
clip = w->text.HighlightStart;
DrawText(w, start, clip, False);
start = clip;
}
else if (start < w->text.HighlightEnd) {
if (end <= w->text.HighlightEnd)
clip = end;
else
clip = w->text.HighlightEnd;
DrawText(w, start, clip, True);
start = clip;
}
else {
DrawText(w, start, end, False);
start = end;
}
}
}
}
}
static void
DrawTextReposition(TextFieldWidget w)
{
int xsrc, xdest, width, start, end;
if (!w->text.Echo)
return;
if (w->text.XOffset < w->text.OldXOffset) {
xsrc = w->text.OldXOffset - w->text.XOffset;
xdest = 0;
width = w->text.ViewWidth - xsrc + 1;
/* Need to redraw some characters at the end. */
end = TextPixelToPos(w, w->text.Margin + w->text.ViewWidth);
start = TextPixelToPos(w, w->text.Margin + w->text.ViewWidth - xsrc);
}
else if (w->text.XOffset > w->text.OldXOffset) {
xsrc = 0;
xdest = w->text.XOffset - w->text.OldXOffset;
width = w->text.ViewWidth - xdest + 1;
/* Need to redraw some characters at the beginning. */
start = TextPixelToPos(w, w->text.Margin);
end = TextPixelToPos(w, w->text.Margin + xdest);
}
else
return;
if (width > 0) {
#ifdef DEBUG_TF
printf("Reposition: xoff=%d old=%d src=%d dest=%d width=%d refresh %d-%d\n",
w->text.XOffset, w->text.OldXOffset, xsrc, xdest, width, start, end);
#endif
XCopyArea(XtDisplay(w), XtWindow(w), XtWindow(w),
w->text.drawGC,
w->text.Margin + xsrc, 0,
(unsigned int) width, (unsigned int) w->core.height,
w->text.Margin + xdest, 0);
DrawTextRange(w, start, end);
}
w->text.OldXOffset = w->text.XOffset;
}
static void
DrawTextWithCopyArea(TextFieldWidget w)
{
int x, insert_width;
int xsrc, xdest, width;
if (!w->text.Echo)
return;
x = w->text.XOffset;
insert_width = FontTextWidth(w->text.font, &w->text.Text[w->text.FastInsertCursorStart], w->text.FastInsertTextLen);
if (PositionCursor(w)) {
/*
* if the text is scrolled, then:
* 1. the cursor is at the end
* 2. the copy will move to the left.
*/
xsrc = 0;
width = w->text.OldCursorX + x;
xdest = w->text.ViewWidth - (x + w->text.OldCursorX) - insert_width;
XCopyArea(XtDisplay(w), XtWindow(w), XtWindow(w),
w->text.drawGC,
w->text.Margin + xsrc, 0,
(unsigned int) width, (unsigned int) w->core.height,
w->text.Margin + xdest, 0);
#ifdef DEBUG_TF
printf("DrawInsert: x=%d xsrc=%d xdest=%d width=%d\n", x, xsrc, xdest, width);
#endif
}
else {
/*
* the text hasn't been scrolled, so:
* 1. the text left of the cursor won't change
* 2. the stuff after the cursor will be moved right.
*/
xsrc = FontTextWidth(w->text.font, w->text.Text, w->text.FastInsertCursorStart) + x;
width = w->text.ViewWidth - xsrc;
xdest = xsrc + insert_width;
XCopyArea(XtDisplay(w), XtWindow(w), XtWindow(w),
w->text.drawGC,
w->text.Margin + xsrc, 0,
(unsigned int) width, (unsigned int) w->core.height,
w->text.Margin + xdest, 0);
#ifdef DEBUG_TF
printf("DrawInsert: x=%d xsrc=%d xdest=%d width=%d\n", x, xsrc, xdest, width);
#endif
}
DrawTextRange(w, w->text.FastInsertCursorStart,
w->text.FastInsertCursorStart + w->text.FastInsertTextLen);
if (w->text.TextMaxLen > 0) {
/*
* This is pretty much a hack:
* clear everything to end of window if this is a
* fixed length TextField
*/
xsrc = w->text.XOffset + w->text.TextWidth;
width = w->text.ViewWidth - xsrc;
/* printf("DrawInsert: TextWidth=%d Old=%d\n", w->text.TextWidth, w->text.OldTextWidth); */
XClearArea(XtDisplay(w), XtWindow(w),
w->text.Margin + xsrc, 0,
(unsigned int) width, w->core.height, False);
}
else if (w->text.TextWidth < w->text.OldTextWidth) {
XClearArea(XtDisplay(w), XtWindow(w),
w->text.Margin + w->text.XOffset + w->text.TextWidth, 0,
w->text.OldTextWidth - w->text.TextWidth + 1,
w->core.height, False);
}
w->text.OldTextWidth = w->text.TextWidth;
w->text.OldXOffset = w->text.XOffset;
}
static void
DrawAllText(TextFieldWidget w)
{
if (!w->text.Echo)
return;
DrawTextRange(w, 0, w->text.TextLen);
if (w->text.TextWidth < w->text.OldTextWidth) {
XClearArea(XtDisplay(w), XtWindow(w),
w->text.Margin + w->text.XOffset + w->text.TextWidth, 0,
w->text.OldTextWidth - w->text.TextWidth + 1,
w->core.height, False);
}
w->text.OldTextWidth = w->text.TextWidth;
w->text.OldXOffset = w->text.XOffset;
w->text.OldHighlightStart = w->text.HighlightStart;
w->text.OldHighlightEnd = w->text.HighlightEnd;
}
/* Draw an I-beam cursor */
static void
DrawIBeamCursor(TextFieldWidget w, int x, GC gc)
{
XDrawLine(XtDisplay(w), XtWindow(w), gc,
x, w->text.YOffset - FontAscent(w->text.font) - 1,
x, w->text.YOffset + FontDescent(w->text.font) + 1);
XDrawLine(XtDisplay(w), XtWindow(w), gc,
x - 2, w->text.YOffset - FontAscent(w->text.font) - 1,
x + 2, w->text.YOffset - FontAscent(w->text.font) - 1);
XDrawLine(XtDisplay(w), XtWindow(w), gc,
x - 2, w->text.YOffset + FontDescent(w->text.font) + 1,
x + 2, w->text.YOffset + FontDescent(w->text.font) + 1);
}
static void
DrawCursor(TextFieldWidget w)
{
int x;
GC gc;
if (w->text.DisplayCursor) {
x = FontTextWidth(w->text.font, w->text.Text, w->text.CursorPos);
w->text.OldCursorPos = w->text.CursorPos;
w->text.OldCursorX = x;
x += w->text.Margin + w->text.XOffset;
gc = w->text.cursorGC;
DrawIBeamCursor(w, x, gc);
}
}
static void
EraseCursor(TextFieldWidget w)
{
int x;
if (w->text.DisplayCursor && w->text.OldCursorX >= 0) {
x = w->text.OldCursorX + w->text.Margin + w->text.XOffset;
DrawIBeamCursor(w, x, w->text.eraseGC);
/* Little hack to fix up the character that might have been affected by
* erasing the old cursor.
*/
if (w->text.OldCursorPos < w->text.TextLen)
DrawTextRange(w, w->text.OldCursorPos, w->text.OldCursorPos + 1);
}
}
static void
ClearHighlight(TextFieldWidget w)
{
if (!w->text.Echo)
return;
if (w->text.HighlightStart >= 0) {
EraseCursor(w);
DrawText(w, w->text.HighlightStart, w->text.HighlightEnd, False);
DrawCursor(w);
w->text.HighlightStart = w->text.HighlightEnd = -1;
}
w->text.OldHighlightStart = w->text.OldHighlightEnd = -1;
}
static void
DrawHighlight(TextFieldWidget w)
{
if (!w->text.Echo)
return;
if (w->text.OldHighlightStart < 0) {
DrawText(w, w->text.HighlightStart, w->text.HighlightEnd,
True);
}
else {
DrawText(w, w->text.HighlightStart, w->text.OldHighlightStart,
(w->text.HighlightStart < w->text.OldHighlightStart));
DrawText(w, w->text.HighlightEnd, w->text.OldHighlightEnd,
(w->text.HighlightEnd > w->text.OldHighlightEnd));
}
w->text.OldHighlightStart = w->text.HighlightStart;
w->text.OldHighlightEnd = w->text.HighlightEnd;
}
/*
* Special redraw function after a text insertion
*/
static void
DrawInsert(TextFieldWidget w)
{
/* EraseCursor must be called before this */
DrawTextWithCopyArea(w);
DrawCursor(w);
}
/*
* Redraw the entire widget, but don't scroll the window much
*/
static void
Draw(TextFieldWidget w)
{
EraseCursor(w);
PositionCursor(w);
DrawAllText(w);
DrawCursor(w);
}
/*
* Like Draw(), but has different rules about scrolling the window to
* place the cursor in a good place
*/
static void
MassiveChangeDraw(TextFieldWidget w)
{
EraseCursor(w);
MassiveCursorAdjust(w);
DrawAllText(w);
DrawCursor(w);
}
/*
* Motif-like TextField public functions
*
* Note that this set of functions is only a subset of the functions available
* in the real Motif XmTextField.
*/
Boolean
TextFieldGetEditable(Widget aw)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!XtIsTextField(aw))
return 0;
return w->text.Editable;
}
int
TextFieldGetInsertionPosition(Widget aw)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!XtIsTextField(aw))
return 0;
return w->text.CursorPos;
}
char *
TextFieldGetString(Widget aw)
{
TextFieldWidget w = (TextFieldWidget) aw;
char *ret;
if (!XtIsTextField(aw)) {
ret = XtMalloc(1);
*ret = '\0';
return ret;
}
ret = XtMalloc(w->text.TextLen + 1);
strncpy(ret, w->text.Text, w->text.TextLen);
ret[w->text.TextLen] = '\0';
return ret;
}
void
TextFieldInsert(Widget aw, int pos, char *str)
{
TextFieldWidget w = (TextFieldWidget) aw;
int len;
if (!XtIsTextField(aw))
return;
if (str && ((len = strlen(str)) > 0) && pos >= 0 && pos <= w->text.TextLen) {
w->text.HighlightStart = w->text.HighlightEnd = pos;
TextInsert(w, str, len);
MassiveChangeDraw(w);
}
}
void
TextFieldReplace(Widget aw, int first, int last, char *str)
{
TextFieldWidget w = (TextFieldWidget) aw;
int len;
if (!XtIsTextField(aw))
return;
if (str) {
len = strlen(str);
if (last > w->text.TextLen)
last = w->text.TextLen;
if (first <= last) {
w->text.HighlightStart = first;
w->text.HighlightEnd = last;
TextDeleteHighlighted(w);
TextInsert(w, str, len);
MassiveChangeDraw(w);
}
}
}
void
TextFieldSetEditable(Widget aw, Boolean editable)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!XtIsTextField(aw))
return;
w->text.Editable = editable;
}
void
TextFieldSetInsertionPosition(Widget aw, int pos)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!XtIsTextField(aw))
return;
if (pos >= 0 && pos <= w->text.TextLen) {
w->text.CursorPos = pos;
MassiveChangeDraw(w);
}
}
/* ARGSUSED */
void
TextFieldSetSelection(Widget aw, int start, int end, Time time)
{
TextFieldWidget w = (TextFieldWidget) aw;
if (!XtIsTextField(aw))
return;
if (end < start) {
int temp;
temp = start;
start = end;
end = temp;
}
if (start < 0)
start = 0;
if (end > w->text.TextLen)
end = w->text.TextLen;
w->text.HighlightStart = start;
w->text.HighlightEnd = w->text.CursorPos = end;
MassiveChangeDraw(w);
}
void
TextFieldSetString(Widget aw, char *str)
{
TextFieldWidget w = (TextFieldWidget) aw;
int len;
if (!XtIsTextField(aw))
return;
if (str) {
len = strlen(str);
w->text.HighlightStart = 0;
w->text.HighlightEnd = w->text.TextLen;
TextDeleteHighlighted(w);
TextInsert(w, str, len);
MassiveChangeDraw(w);
}
}