/*----------------------------------------------------------------------------- * 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 * http://www.ae.utexas.edu/~rwmcm */ #define _TextField_ #include #include #include #include #include #include #include #ifdef HAVE_STDLIB_H #include #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[] = "Right: forward-char()\n\ KP_Right: forward-char()\n\ Ctrlf: forward-char()\n\ Ctrle: forward-to-end()\n\ Left: backward-char()\n\ KP_Left: backward-char()\n\ Ctrlb: backward-char()\n\ Ctrla: backward-to-start()\n\ Ctrld: delete-next-char()\n\ Ctrlk: delete-to-end()\n\ Delete: delete-previous-char()\n\ BackSpace: delete-previous-char()\n\ Ctrlh: delete-previous-char()\n\ Ctrlu: delete-to-start()\n\ Ctrlw: delete-highlighted()\n\ Ctrlt: transpose-chars()\n\ Return: activate()\n\ : insert-char()\n\ Shift: extend-start()\n\ : select-start()\n\ : extend-adjust()\n\ : extend-end()\n\ : insert-selection()\n\ : extend-start()\n\ : extend-adjust()\n\ : extend-end()\n\ : enter-window()\n\ : leave-window()\n\ : focus-in()\n\ : 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); } }