/***********************************************************************/ /* Open Visualization Data Explorer */ /* (C) Copyright IBM Corp. 1989,1999 */ /* ALL RIGHTS RESERVED */ /* This code licensed under the */ /* "IBM PUBLIC LICENSE - Open Visualization Data Explorer" */ /***********************************************************************/ #include #include #include #ifdef OS2 #include #include #endif #include #include #include #include "MultiTextP.h" #if !defined(ibm6000) && !defined(OS2) && !defined(DXD_WIN) #include #endif #if defined(DXD_WIN) /* winuser.h defines ScrollWindow which conflicts with code in this file */ #define ScrollWindow _ScrollWindow #endif #define CR 13 #define TAB 9 #define BACKSPACE 8 #define SPACE 32 #define CTRL_C 3 #define DEFAULT_WORD_SPACING 1 #define DEFAULT_LINE_SPACING 0 #define DEFAULT_FONT_NAME "fixed" #define MAX_BUFF_SIZE 32767 #define MAX_STR_LEN 1024 #define UP 0 #define DOWN 1 #define LEFT 2 #define RIGHT 3 #define TOP 1 #define BOTTOM 2 #define ON TRUE #define OFF FALSE #ifndef ABS #define ABS(a) (((a) > 0) ? (a) : -(a)) #endif #define STRCMP(a,b) ((a) ? ((b) ? strcmp(a,b) : strcmp(a,"")) : \ ((b) ? strcmp("",b) : 0)) #ifndef MIN #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif #ifndef MAX #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif /* * Forward declaration of methods: */ static void ClassInitialize(); static void Initialize(Widget, Widget, ArgList, Cardinal*); static void Destroy(Widget); static void Redisplay(Widget, XEvent*, Region); static void Resize(Widget); static Boolean SetValues(); /* * Private routines used by the XmMultiTextWidget. */ static char* NewString(char*); static void WarningDialog(Widget, char*); static XtGeometryResult ChangeHeight(XmMultiTextWidget, Dimension); static void GetBoundingBox(XmMultiTextWidget, char*, XFontStruct*, XCharStruct*, int); static void ShiftFollowingLines(LineList, int); static Boolean PtInLine (int x, int y, LineList lp); static Boolean PtInWord (int x, int y, WordList wp, LineList lp); static Boolean SameLinks (WordList wp1, WordList wp2); static void SetCurrentFont (XmMultiTextWidget w, WordList wp, XFontStruct *font); static void SetCurrentColor (XmMultiTextWidget w, WordList wp, unsigned long color); static void DisplayImage (XmMultiTextWidget w, WordList wp); static void DisplayWord (XmMultiTextWidget w, WordList wp); static void DisplaySelectedWord (XmMultiTextWidget w, WordList wp); static void DisplayDeselectedWord (XmMultiTextWidget w, WordList wp); static void DrawLinkMarking (XmMultiTextWidget w, WordList wp); static void CalculateSpacing (Widget w, WordList wp, int *spaceBefore, int *spaceAfter); static void DrawWord (XmMultiTextWidget w, WordList wp, Boolean selected); static void DrawLine (XmMultiTextWidget w, LineList lp); static void ResizeTheLine (XmMultiTextWidget w, LineList lp, WordList theWord); static void FreeCharRecord (XmMultiTextWidget w, CharList cp); static void FreeWordRecord (XmMultiTextWidget w, WordList wp); static void FreeLineRecord (XmMultiTextWidget w, LineList lp); static int NextTab (XmMultiTextWidget w, int *tabWidth, int indent); static unsigned long Color (XmMultiTextWidget w, char *name); static XFontStruct *NamedFont (XmMultiTextWidget w, char *name); static LineList NewLine (XmMultiTextWidget w, int indent, int yposn, int lineSpacing); static Boolean StuffWordOntoCurrentLine (XmMultiTextWidget w, LineList lp, WordList wp, int neededWidth, Boolean firstWordOfLine, Boolean isTab, Boolean prevWordIsTab); static int GetNeededWidth (XmMultiTextWidget w, WordList wPtr, int indent, Boolean *firstWordOfLine, Boolean *isTab, Boolean *prevWordIsTab); static unsigned short StoreWord (XmMultiTextWidget w, WordList wordPtr, int indent); static unsigned int AppendWordToTopLine (XmMultiTextWidget cw, WordList wordPtr, int indent); static void PositionCursor (XmMultiTextWidget cw, int x, int y, LineList *lp, int *actualX); static void UpdateCursor (XmMultiTextWidget cw, LineList lp, int x, int actualX, int actualY); static void MakeCursorPixmaps (XmMultiTextWidget cw); static void BlinkCursor (XmMultiTextWidget cw); static void TurnOnCursor (XmMultiTextWidget cw); static void TurnOffCursor (XmMultiTextWidget cw); /* actions */ static void MoveTo(Widget, XEvent*, String*, Cardinal*); static void BtnMoveTo (Widget, XEvent*, String*, Cardinal*); static void BtnMoveNew (Widget, XEvent*, String*, Cardinal*); static void BtnRelease (Widget, XEvent*, String*, Cardinal*); static void KeyPush (Widget, XEvent*, String*, Cardinal*); static void NewLineCR (Widget, XEvent*, String*, Cardinal*); static void InsertSpace (Widget, XEvent*, String*, Cardinal*); static void MoveUp (Widget, XEvent*, String*, Cardinal*); static void MoveDown (Widget, XEvent*, String*, Cardinal*); static void MoveLeft (Widget, XEvent*, String*, Cardinal*); static void MoveRight (Widget, XEvent*, String*, Cardinal*); static void Enter (Widget, XEvent*, String*, Cardinal*); static void Leave (Widget, XEvent*, String*, Cardinal*); static void InFocus (Widget, XEvent*, String*, Cardinal*); static void OutFocus (Widget, XEvent*, String*, Cardinal*); static void DeleteLine (Widget w, XKeyPressedEvent *event, String *argv, Cardinal *argc); static void OpenLineTop (Widget, XEvent*, String*, Cardinal*); static void AddWordTop (Widget, XEvent*, String*, Cardinal*); static void Dummy (Widget, XEvent*, String*, Cardinal*); #ifndef NODPS extern void ShowScale (Widget, XEvent*, String*, Cardinal*); #endif static void Refresh (Widget, XEvent*, String*, Cardinal*); static void Cut (Widget, XEvent*, String*, Cardinal*); /* * Event handler? */ static void ChangeFocus (Widget, XEvent*, String*, Cardinal*); /* Public routines defined external in public header file. */ void XmMultiTextClearText (Widget w); int XmMultiTextGetPosition (Widget w); int XmMultiTextGetCursorPosition (Widget w); void XmMultiTextQueryCursor (Widget w, int *lineNumber, int *y); void XmMultiTextDeselectAll (Widget w); void XmMultiTextSetTabStops (Widget w, int tabList[], int tabCount); LinkInfoPtr XmMultiTextMakeLinkRecord (Widget w, LinkType linkType, LinkPosition linkPosition, char *linkData); void XmMultiTextAppendNewLine (Widget w, char *theWord, char *fontName, int indent); unsigned short XmMultiTextAppendWord (Widget w, char *theWord, char *fontName, char *colorName, int indent, LinkInfoPtr linkInfo); unsigned short XmMultiTextAppendWordTop (Widget w, char *theWord, char *fontName, char *colorName, int indent, LinkInfoPtr linkInfo); void XmMultiTextOpenLineTop (Widget w, int indent); int XmMultiTextDeleteLineTop (Widget w, int linesToDelete); void XmMultiTextDeleteLineBottom (Widget w); int XmMultiTextLongestLineLength (Widget w); unsigned short XmMultiTextAppendImage (Widget w, char *theWord, char *fontName, char *colorName, int indent, Pixmap image, unsigned int width, unsigned int height, LinkInfoPtr linkInfo); unsigned short XmMultiTextAppendWidget (Widget w, char *theWord, char *fontName, char *colorName, int indent, Widget newW); unsigned short XmMultiTextAppendDPS (Widget w, char *theWord, char *fontName, char *colorName, int indent, char *dpsFileName, unsigned int width, unsigned int height, LinkInfoPtr linkInfo); Boolean XmMultiTextAppendChar (Widget w, char str); Boolean XmMultiTextAppendLine (Widget w, char *str); static XtResource resources[] = { { XmNdpsCapable, XmCDPSCapable, XmRBoolean, sizeof(Boolean), offset(multiText.dpsCapable), XmRImmediate, (caddr_t)TRUE }, { XmNwordWrap, XmCWordWrap, XmRBoolean, sizeof(Boolean), offset(multiText.wordWrap), XmRImmediate, (caddr_t)TRUE }, { XmNmarginWidth, XmCMarginWidth, XmRInt, sizeof(int), offset(multiText.marginWidth), XmRImmediate, (caddr_t)10 }, { XmNmarginHeight, XmCMarginHeight, XmRInt, sizeof(int), offset(multiText.marginHeight), XmRImmediate, (caddr_t)10 }, { XmNwaitCursorCount, XmCWaitCursorCount, XmRInt, sizeof(int), offset(multiText.waitCursorCount), XmRImmediate, (caddr_t)1 }, { XmNblinkRate, XmCBlinkRate, XmRInt, sizeof(int), offset(multiText.blinkRate), XmRImmediate, (caddr_t)250 }, { XmNshowCursor, XmCShowCursor, XmRBoolean, sizeof(Boolean), offset(multiText.showCursor), XmRImmediate, (caddr_t)TRUE }, { XmNfocusSensitive, XmCFocusSensitive, XmRBoolean, sizeof(Boolean), offset(multiText.focusSensitive), XmRImmediate, (caddr_t)FALSE }, { XmNcursorColor, XmCForeground, XmRPixel, sizeof(Pixel), offset(multiText.cursorColor), XmRString, "yellow" }, { XmNscaleDPSpercent, XmCScaleDPSpercent, XmRInt, sizeof(int), offset(multiText.scaleDPSpercent), XmRImmediate, (caddr_t)100 }, { XmNsmartSpacing, XmCSmartSpacing, XmRBoolean, sizeof(Boolean), offset(multiText.smartSpacing), XmRImmediate, (caddr_t)TRUE }, { XmNsmoothScroll, XmCSmoothScroll, XmRBoolean, sizeof(Boolean), offset(multiText.smoothScroll), XmRImmediate, (caddr_t)FALSE }, { XmNexposeOnly, XmCExposeOnly, XmRBoolean, sizeof(Boolean), offset(multiText.exposeOnly), XmRImmediate, (caddr_t)FALSE }, { XmNlinkCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), offset(multiText.linkCallback), XmRImmediate, (caddr_t) NULL }, { XmNselectCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), offset(multiText.selectCallback), XmRImmediate, (caddr_t)NULL } }; /* * Default Translation Table: * This table maps event sequences into action names. */ #ifndef NODPS static char defaultTranslations[] = "Ctrl Shift: ShowScale() \n\ : BtnMoveTo() \n\ : BtnRelease() \n\ : MoveTo() \n\ BackSpace: Cut() \n\ Up: MoveUp() \n\ Down: MoveDown() \n\ Left: MoveLeft() \n\ Right: MoveRight() \n\ Delete: Dummy() \n\ Return: NewLineCR() \n\ : InFocus() \n\ : OutFocus() \n\ F1: DeleteLine(1) \n\ F2: DeleteLine(2) \n\ F3: OpenLineTop() \n\ F5: AddWordTop()"; #else static char defaultTranslations[] = ": BtnMoveTo() \n\ : BtnRelease() \n\ : MoveTo() \n\ BackSpace: Cut() \n\ Up: MoveUp() \n\ Down: MoveDown() \n\ Left: MoveLeft() \n\ Right: MoveRight() \n\ Delete: Dummy() \n\ Return: NewLineCR() \n\ : InFocus() \n\ : OutFocus() \n\ F1: DeleteLine(1) \n\ F2: DeleteLine(2) \n\ F3: OpenLineTop() \n\ F5: AddWordTop()"; #endif /*=======================================================* | Actions Table | | the action table maps string action names into actual | | action functions. | *=======================================================*/ static XtActionsRec actions[] = { {"MoveTo", MoveTo }, {"BtnMoveTo", BtnMoveTo }, {"BtnMoveNew", BtnMoveNew }, {"BtnRelease", BtnRelease }, {"Refresh", Refresh }, {"Cut", Cut }, {"KeyPush", KeyPush }, {"NewLineCR", NewLineCR }, {"InsertSpace", InsertSpace }, #ifndef NODPS {"ShowScale", ShowScale }, #endif {"MoveUp", MoveUp }, {"MoveDown", MoveDown }, {"MoveLeft", MoveLeft }, {"MoveRight", MoveRight }, {"Enter", Enter }, {"Leave", Leave }, {"InFocus", InFocus }, {"OutFocus", OutFocus }, {"DeleteLine", (XtActionProc)DeleteLine }, {"Dummy", Dummy }, {"OpenLineTop", OpenLineTop }, {"AddWordTop", AddWordTop }, }; /* The MultiText class record definition */ XmMultiTextClassRec xmMultiTextClassRec = { /* * Core class part: */ { (WidgetClass)&xmDrawingAreaClassRec, /* superclass */ "XmMultiText", /* class_name */ sizeof(XmMultiTextRec), /* widget_size */ ClassInitialize, /* class_initialize */ NULL, /* class_part_initialize */ FALSE, /* class_inited */ Initialize, /* initialize */ NULL, /* initialize_hook */ XtInheritRealize, /* realize */ actions, /* actions */ XtNumber(actions), /* num_actions */ resources, /* resources */ XtNumber(resources), /* num_resources */ NULLQUARK, /* xrm_class */ TRUE, /* compress_motion */ TRUE, /* compress_exposure */ TRUE, /* compress_enterleave */ FALSE, /* visible_interest */ Destroy, /* destroy */ Resize, /* resize */ Redisplay, /* expose */ (XtSetValuesFunc)SetValues, /* set_values */ NULL, /* set_values_hook */ XtInheritSetValuesAlmost, /* set_values_almost */ NULL, /* get_values_hook */ NULL, /* accept_focus */ XtVersion, /* version */ NULL, /* callback private */ defaultTranslations, /* tm_table */ XtInheritQueryGeometry, /* query_geometry */ NULL, /* display_accelerator */ NULL /* extension */ }, /* * Compositie class part: */ { XtInheritGeometryManager, /* Geometry Manager */ XtInheritChangeManaged, /* Change Managed */ XtInheritInsertChild, /* Insert Child */ XtInheritDeleteChild, /* Delete Child */ NULL /* extension */ }, /* * Constraint class part: */ { NULL, /* resources */ 0, /* num resources */ 0, /* constraint record */ NULL, /* initialize */ NULL, /* destroy */ NULL, /* set values */ NULL /* extension */ }, /* * Manager class part: */ { NULL, /* default_translations */ NULL, /* get_resources */ 0, /* num_get_resources */ NULL, /* get_cont_resources */ 0, /* num_get_cont_resources */ (XmParentProcessProc)NULL, /* parent_process */ NULL /* extension */ }, /* * DrawingArea class part: */ { 0 /* dummy field */ }, { 0 /* extension */ }, }; WidgetClass xmMultiTextWidgetClass = (WidgetClass)&xmMultiTextClassRec; /*======================================================================* | XmMultiText Widget Methods | | -------------------------- | | These are private to XmMultiText and can't be accessed by the | | application programmer directly. | *======================================================================*/ /* * This routine creates an XFontStruct and a GC. Both of these are put * into the instance record of the MultiTextWidget. */ static void GetTextGC(XmMultiTextWidget cw) { XtGCMask mask; XGCValues values; XFontStruct* font; XRectangle recs[1]; mask = GCForeground | GCBackground | GCFont | GCGraphicsExposures | GCSubwindowMode; font = XLoadQueryFont(XtDisplay(cw), "fixed"); values.font = font->fid; values.foreground = cw->manager.foreground; values.background = cw->core.background_pixel; values.graphics_exposures = False; values.subwindow_mode = ClipByChildren; cw->multiText.textGC = XCreateGC(XtDisplay(cw), XRootWindowOfScreen(XtScreen(cw)), mask, &values); values.foreground = cw->multiText.cursorColor; cw->multiText.cursorGC = XCreateGC(XtDisplay(cw), XRootWindowOfScreen(XtScreen(cw)), mask, &values); XFreeFont(XtDisplay(cw), font); } /* * Only draws the lines that are effected by the refreshed area. */ static void DoRedrawText(XmMultiTextWidget cw, int y, int height) { LineList lp = cw->multiText.firstLine; WordList wp; int y1; int y2; int ey1; int ey2; int maxHeight; if (cw->multiText.drawing) return; cw->multiText.drawing = TRUE; ey1 = y; ey2 = y + height; while (lp != NULL) { y1 = lp->y; y2 = lp->y + lp->bbox.ascent + lp->bbox.descent; if (!(((ey1 < y1) && (ey2 < y1)) || ((y1 < ey1) && (y2 < ey1)))) { DrawLine(cw, lp); } lp = lp->next; } cw->multiText.drawing = FALSE; } static void StringToFloatConverter(XrmValue* args, Cardinal* nargs, XrmValue* fromVal, XrmValue* toVal) { static float result; /* * Make sure the number of args is correct. */ if (*nargs != 0) { XtWarning("String to Float conversion needs no arguments"); } /* * Convert the string in the fromVal to a floating pt. */ if (sscanf((char*)fromVal->addr, "%f", &result) == 1) { /* * Make the toVal point to the result. */ toVal->size = sizeof(float); toVal->addr = (caddr_t)&result; } } static void ClassInitialize(WidgetClass wc) { #ifdef DXD_WIN /* Exceed on WINDOWS NT has _XmRegisterConverters() */ _XmRegisterConverters(); #else XmRegisterConverters(); #endif XtAddConverter(XmRString, XmRFloat, StringToFloatConverter, NULL, 0); } /* * The instance record of each widget must be initialized at run time. * This method is invoked when a new widget is created. * 'newWidget' is the real widget, 'request' is a copy. 'request' contains * the original resource values. 'newWidget' may have been modified by other * initialize routines of superclasses. All changes are made to 'newWidget'. */ static void Initialize(Widget reqWidget, Widget newWidgetWidget, ArgList arg, Cardinal* argc) { XmMultiTextWidget req = (XmMultiTextWidget)reqWidget; XmMultiTextWidget newWidget = (XmMultiTextWidget)newWidgetWidget; int i; int majorOpCode; int firstEvent; int firstError; char buf[MAX_STR_LEN]; #ifndef NODPS if (!XQueryExtension(XtDisplay(req), DPSNAME, &majorOpCode, &firstEvent, &firstError)) { WarningDialog(newWidget, MSG7); } #endif newWidget->manager.traversal_on = True; newWidget->multiText.firstLine = NULL; newWidget->multiText.currentLine = NULL; newWidget->multiText.lastLine = NULL; newWidget->multiText.drawing = FALSE; newWidget->multiText.selecting = FALSE; newWidget->multiText.actualX = 0; newWidget->multiText.cursorX = 0; newWidget->multiText.cursorY = 0; newWidget->multiText.oldX = 0; newWidget->multiText.oldY = 0; newWidget->multiText.cursorFg = NULL; newWidget->multiText.cursorBg = NULL; newWidget->multiText.blinkState = FALSE; newWidget->multiText.cursorAvailable = TRUE; newWidget->multiText.blinkTimeOutID = 0; newWidget->multiText.wordSpacing = DEFAULT_WORD_SPACING; newWidget->multiText.currentFontID = 0; newWidget->multiText.currentColor = 0; newWidget->multiText.lastColorName = NewString(""); newWidget->multiText.numFonts = 0; newWidget->multiText.fontCache = NULL; newWidget->multiText.showCursor = req->multiText.showCursor; if (newWidget->multiText.showCursor) { TurnOnCursor(newWidget); } else { TurnOffCursor(newWidget); } for (i = 0; i < MAX_TAB_COUNT; i++) newWidget->multiText.tabs[i] = 0; newWidget->multiText.tabCount = 0; newWidget->multiText.waitCursorIndex = 1; if ((newWidget->multiText.waitCursorCount < 1) || (newWidget->multiText.waitCursorCount > CURSOR_COUNT)) { sprintf (buf, MSG1, CURSOR_COUNT); XtWarning(buf); newWidget->multiText.waitCursorCount = 1; } if (newWidget->multiText.scaleDPSpercent <= 0) { XtWarning(MSG2); newWidget->multiText.scaleDPSpercent = 1; } GetTextGC(newWidget); XtAddEventHandler((Widget)newWidget, FocusChangeMask, FALSE, (XtEventHandler)ChangeFocus, (Opaque)NULL); XtAddEventHandler((Widget)newWidget, EnterWindowMask, FALSE, (XtEventHandler)Enter, (Opaque)NULL); XtAddEventHandler((Widget)newWidget, LeaveWindowMask, FALSE, (XtEventHandler)Leave, (Opaque)NULL); } /*---------------------------================---------------------------* | Redisplay method | | This is where the widget gets redrawn when an Expose event occurs. | | Note that this routine can't be called 'Expose' since X.h defined | | 'Expose' as 12. | | w - defines the widget instance to be redisplayed. | | event - defines a pointer to an Expose event. | | region - is the region to be redrawn. If compress_exposures is true | | then the region is the sum of rectangles reported on all | | expose events, and the event parameter contains the bounding| | box of the region. Otherwise, region is NULL. | *---------------------------================---------------------------*/ static void Redisplay (Widget w, XEvent *event, Region region) { XmMultiTextWidget cw = (XmMultiTextWidget)w; XExposeEvent *expose = (XExposeEvent*)event; if (XtIsRealized(cw)) { DoRedrawText(cw, expose->y, expose->height); if ((cw->multiText.cursorFg == NULL) && cw->multiText.cursorAvailable) { if (cw->multiText.blinkTimeOutID != 0) { XtRemoveTimeOut(cw->multiText.blinkTimeOutID); cw->multiText.blinkTimeOutID = 0; } BlinkCursor(cw); } } } /*---------------------------================---------------------------* | SetValues method | | This method allows a widget to be notified when one of its resources | | is set or changed. This can be called when a widget is initialized | | by the resource manager, or when an application sets a resource with | | XtSetValues(). The SetValues methods are chained and called from | | superclass to subclass order. | | current - the previous, unaltered state of the widget. | | request - the values requested for the widget. | | newWidget - the state of the widget after all superclass's SetValues | | methods were called. All changes must be made to newWidget. | | SetValues returns a Boolean value specifying whether or not the | | widget needs to be redrawn. If TRUE, the Intrinsics causes an Expose | | event to be generated for the entire window. | | Note that SetValues must not perform any graphics operations on the | | widget's window unless the widget is realized. | *---------------------------================---------------------------*/ static Boolean SetValues (XmMultiTextWidget current, XmMultiTextWidget request, XmMultiTextWidget newWidget) { Boolean redraw = FALSE; char buf[MAX_STR_LEN]; if (current->multiText.waitCursorCount != newWidget->multiText.waitCursorCount) if ((newWidget->multiText.waitCursorCount < 1) || (newWidget->multiText.waitCursorCount > CURSOR_COUNT)) { sprintf(buf, MSG1, CURSOR_COUNT); XtWarning(buf); newWidget->multiText.waitCursorCount = 1; } /* there really isn't any way to screw-up the values of the MultiTextWidget, yet... */ /* however, it is possible to cause a redraw by changing the wordwrap value. */ if (current->multiText.wordWrap != newWidget->multiText.wordWrap) redraw = TRUE; /* check that the DPSscaling percentage is correct. */ if (newWidget->multiText.scaleDPSpercent <= 0) { XtWarning(MSG2); newWidget->multiText.scaleDPSpercent = 1; } /* check if there's a change in the showCursor state. */ if (current->multiText.showCursor != newWidget->multiText.showCursor) { if (newWidget->multiText.showCursor) TurnOnCursor(newWidget); else TurnOffCursor(newWidget); } /* should add checking for margins here... */ return redraw; } /*-------------------------====================-------------------------* | ChangeManaged method | | This routine is responsible for making the initial layout of an app | | and changing the layout when any child changes management state. | | Also, when a child becomes managed or unmanaged, any redrawing or | | moving of other widgets is handled here. *-------------------------====================-------------------------*/ static void ChangeManaged (XmMultiTextWidget cw, XExposeEvent *event) { } /*-----------------------=======================------------------------* | GeometryManager method | | This method handles resize requests from its children. | | Technically, this routine should try to accomidate requests to change| | the size of the children my either moving items around or asking its | | parent for more space. This one just tells any child widget 'NO!'. | *-----------------------=======================------------------------*/ static XtGeometryResult GeometeryManager (XmMultiTextWidget cw, XExposeEvent *event) { return XtGeometryNo; } /*-------------------------====================-------------------------* | QueryGeometry method | | This routine supplies a preferred geometry to a widget's parent when | | the parent calls XtQueryGeometry. The parent makes these calls in | | the process of determining a new layout for its children. | *-------------------------====================-------------------------*/ static XtGeometryResult QueryGeometry (XmMultiTextWidget cw, XExposeEvent *event) { return XtGeometryYes; } /*----------------------------==============----------------------------* | Destroy method | | The Destroy method is invoked when a widget is destroyed. Chaining | | is in reverse order from other chaining; the widget's method is | | called first followed by its superclass's Destroy method. | *----------------------------==============----------------------------*/ static void Destroy (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget)w; int i; /* free the font cache. */ for (i=0; imultiText.numFonts; i++ ) { XFreeFont(XtDisplay(cw), cw->multiText.fontCache[i].font); #ifdef COMMENT XtFree(cw->multiText.fontCache[i].font); #endif XtFree(cw->multiText.fontCache[i].name); } XtFree((char*)cw->multiText.fontCache); XtFree(cw->multiText.lastColorName); if (cw->multiText.textGC) XFreeGC(XtDisplay(cw), cw->multiText.textGC); if (cw->multiText.cursorGC) XFreeGC(XtDisplay(cw), cw->multiText.cursorGC); if (cw->multiText.blinkTimeOutID != 0) XtRemoveTimeOut(cw->multiText.blinkTimeOutID); } /*-----------------------------=============----------------------------* | Resize method | | The Resize method is invoked when the widget's window is reconfigured| | in any way. Since the X server generates an Expose event if the | | contents of a window are corrupted, the Resize method only updates | | the data needed to allow the Redisplay method to redraw the widget | | correctly. | *-----------------------------=============----------------------------*/ static void Resize (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget)w; if (cw->multiText.smoothScroll) { XRectangle recs[1]; recs[0].x = cw->multiText.marginWidth; recs[0].y = cw->multiText.marginHeight; recs[0].width = cw->core.width; recs[0].height = cw->core.height; XSetClipRectangles(XtDisplay(cw), cw->multiText.textGC, 0, 0, recs, 1, Unsorted); } } /*======================================================================* | XmMultiText Widget Action Procedures | | ------------------------------------ | | The(se) routine(s) are defined at the beginning of the file in the | | list of actions. | *======================================================================*/ /*----------------------------------------------------------------------* | PtInWord | *----------------------------------------------------------------------*/ static Boolean PtInWord (int x, int y, WordList wp, LineList lp) { if ((x >= lp->x + wp->x) && (x < lp->x + wp->x + wp->bbox.width)) return TRUE; else /* now for a bit more complex - if this is a link then the bounding box */ /* is different depending if there is a link behind or after this word. */ /* in this case, we must account for the spacing between words as well */ /* as the actual word itself. - yuk! */ if (wp->linkInfo != NULL) { int x1 = lp->x + wp->x; int x2 = lp->x + wp->x + wp->bbox.width; if ((wp->prev != NULL) && (wp->prev->linkInfo != NULL)) x1 -= wp->spacing; if ((wp->next != NULL) && (wp->next->linkInfo != NULL)) x2 += wp->spacing; /* now let's see if it's in the new larger bounding box. */ if ((x >= x1) && (x < x2)) return TRUE; else return FALSE; } else return FALSE; } /*----------------------------------------------------------------------* | HighlightWord | *----------------------------------------------------------------------*/ static void HighlightWord (XmMultiTextWidget cw, LineList lp, WordList wp, int x2, int y2) { int x1 = cw->multiText.startX; int y1 = cw->multiText.startY; int wpx1 = wp->x + lp->x; int wpy1 = lp->y; int wpx2 = wp->x + lp->x + wp->bbox.width; int wpy2 = lp->y + lp->bbox.ascent + lp->bbox.descent; Boolean startAfter, startBefore, endAfter, endBefore, startInside, endInside; startAfter = (((x1 > wpx2) && (y1 >= wpy1)) || (y1 > wpy2)); startBefore = (((x1 < wpx1) && (y1 <= wpy2)) || (y1 < wpy1)); endAfter = (((x2 > wpx2) && (y2 > wpy1)) || (y2 > wpy2)); endBefore = (((x2 < wpx1) && (y2 < wpy2)) || (y2 <= wpy1)); startInside = ((wpx1 < x1) && (x1 <= wpx2) && (wpy1 < y1) && (y1 <= wpy2)); endInside = ((wpx1 < x2) && (x2 <= wpx2) && (wpy1 < y2) && (y2 <= wpy2)); if ((startBefore && endAfter) || (startAfter && endBefore) || startInside || endInside) { if (!wp->selected) { DrawWord(cw, wp, TRUE); wp->selected = TRUE; } } else if (wp->selected) { DrawWord(cw, wp, FALSE); wp->selected = FALSE; } } /*----------------------------------------------------------------------* | BtnMoveTo | | This gets call when the mouse button is pressed while the mouse is | | moved. | *----------------------------------------------------------------------*/ static void BtnMoveTo (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp; WordList wp; if (cw->multiText.selecting) /* selecting turns on when button is pressed. Off on release. */ { /* go through each word and highlight if in region and unhighlight if not. */ for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) for (wp = lp->firstWord; wp != NULL; wp = wp->next) HighlightWord(cw, lp, wp, event->xbutton.x, event->xbutton.y); } } /*----------------------------------------------------------------------* | BtnMoveNew | | This gets call when the mouse button is pressed while the mouse is | | moved. | | This routine highlights the lines in the region - first find the line| | that the pointer is currently in, then find what line the selection | | started in. Highlight all lines between these line but not including| | these. Finally highlight the region corresponding to what should be | | selected for each of the lines containing the start or end points of | | the selection region. | *----------------------------------------------------------------------*/ static void BtnMoveNew (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp, lp1, lp2; WordList wp; int y1, y2; /* find the line containing the starting point (the lowest of the y positions). */ y1 = MIN(cw->multiText.startY, event->xbutton.y); y2 = MAX(cw->multiText.startY, event->xbutton.y); for (lp1 = cw->multiText.firstLine; (lp1 != NULL) && !((lp1->y <= y1) && (y1 <= lp1->y + lp1->bbox.ascent + lp1->bbox.descent)); lp1 = lp1->next) /* move lp1 to start of selection region. */ ; /* now lp1 points to the line where the selection region is starting in. */ /* find the line where the selection ends. */ for (lp2 = lp1; (lp2 != NULL) && !((lp2->y <= y2) && (y2 <= lp2->y + lp2->bbox.ascent + lp2->bbox.descent)); lp2 = lp2->next) /* lp2 points to where the selection ends. */; /* highlight each line between lp1 and lp2. */ for (lp = lp1; lp != lp2; lp = lp->next) /* highlight each line... TBA */ ; } /*----------------------------------------------------------------------* | StuffOntoClipboard | *----------------------------------------------------------------------*/ void ToClipboard (XmMultiTextWidget cw, char *data) { long clipID = 0; int status; XmString clipLabel; char buf[32]; static int cnt; sprintf(buf, "%s-%d", data, ++cnt); clipLabel = XmStringCreate(data, XmSTRING_DEFAULT_CHARSET); do status = XmClipboardStartCopy(XtDisplay(cw), XtWindow(cw), clipLabel, CurrentTime, NULL, NULL, &clipID); while (status == ClipboardLocked); XmStringFree(clipLabel); do status = XmClipboardCopy(XtDisplay(cw), XtWindow(cw), clipID, "STRING", buf, (long)strlen(buf)+1, cnt, NULL); while (status == ClipboardLocked); do status = XmClipboardEndCopy(XtDisplay(cw), XtWindow(cw), clipID); while (status == ClipboardLocked); } /*----------------------------------------------------------------------* | BtnRelease | *----------------------------------------------------------------------*/ static void BtnRelease (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; static char mondoBuffer[MAX_BUFF_SIZE], *space = " ", *cr = "\n"; LineList lp; WordList wp; XmMultiTextSelectCallbackStruct callValue; XButtonEvent *btnEvent = (XButtonEvent *)event; long itemid, dataid; int result; Boolean foundSomething, textIsSelected = FALSE; if (cw->multiText.selecting) { cw->multiText.selecting = FALSE; strcpy(mondoBuffer,""); /* grab the selected text. */ foundSomething = FALSE; for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) { foundSomething = FALSE; for (wp = lp->firstWord; wp != NULL; wp = wp->next) if (wp->selected) { foundSomething = textIsSelected = TRUE; strcat(mondoBuffer, wp->chars); if (cw->multiText.smartSpacing) strcat(mondoBuffer, space); } else foundSomething = FALSE; if (foundSomething) strcat(mondoBuffer, cr); } /* Check if the selection callback exists. */ callValue.reason = XmCR_SELECT; callValue.event = event; callValue.data = NewString(mondoBuffer); if (XtHasCallbacks((Widget)cw, XmNselectCallback) == XtCallbackHasSome) { XFlush(XtDisplay(w)); XtCallCallbacks((Widget)cw, XmNselectCallback, &callValue); } XtFree(callValue.data); } /* if (textIsSelected) ToClipboard(cw, mondoBuffer); */ cw->multiText.textIsSelected = textIsSelected; } /*----------------------------------------------------------------------* | Refresh | | causes a complete redraw of the text widget. | *----------------------------------------------------------------------*/ static void Refresh (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp = cw->multiText.firstLine; XmMultiTextClearText((Widget)cw); while (lp != NULL) { DrawLine(cw, lp); lp = lp->next; } } /*----------------------------------------------------------------------* | DeleteLineFromWidget | *----------------------------------------------------------------------*/ void DeleteLineFromWidget (XmMultiTextWidget cw, LineList *lpp) { LineList temp; if (*lpp == NULL) return; if ((*lpp)->prev == NULL) cw->multiText.firstLine = NULL; else (*lpp)->prev->next = (*lpp)->next; if ((*lpp)->next == NULL) cw->multiText.lastLine == (*lpp)->prev; else (*lpp)->next->prev = (*lpp)->prev; if ((*lpp)->prev == NULL) temp = (*lpp)->next; else temp = (*lpp)->prev; FreeLineRecord(cw, *lpp); *lpp = temp; } /*----------------------------------------------------------------------* | LineTooLong | | A line is too long if it has more than one word AND doesn't fit into | | the widget AND the last word is allowed to be wrapped. | *----------------------------------------------------------------------*/ Boolean LineTooLong (XmMultiTextWidget cw, LineList lp) { /* if the line is null or empty, return false. */ if ((lp == NULL) || (lp->lastWord == NULL)) return (FALSE); /* if the line is too long and the last word is 'wrappable', then return true. */ if (((lp->x + lp->bbox.width) > (cw->core.width - cw->multiText.marginWidth)) && (lp->lastWord->wordWrapping && cw->multiText.wordWrap)) return (TRUE); else return (FALSE); } /*----------------------------------------------------------------------* | WrapLastWordToNextLine | | this routine assumes that the last word is allowed to be wrapped. In | | other words - it just performs the wrapping and does what it's told. | *----------------------------------------------------------------------*/ void WrapLastWordToNextLine (XmMultiTextWidget cw, LineList lp) { int dx; WordList wp; /* let's do some simple checking - these errors should never occur... */ if ((lp == NULL) || (lp->lastWord == NULL) || (lp->lastWord == lp->firstWord)) return; wp = lp->lastWord; lp->lastWord = wp->prev; dx = lp->bbox.width - lp->lastWord->x + lp->lastWord->bbox.width; lp->bbox.width -= dx; lp->remaining += dx; lp->wordCount --; /* if this is the last line, then open a new line after this one. */ /********* if (lp->next == NULL) OpenNewLine(); *********/ wp->y = lp->next->bbox.ascent - wp->bbox.ascent; wp->tabCount = 0; wp->spaceCount = 0; wp->linePtr = lp->next; /* now shift horizontally all words in the line. */ /***** if the a word's tabcount is < next word's tabcount, then the next char is a tab. do the same for the number of spaces... *****/ /* now adjust the ascent or descent of the line if necessary */ } /*----------------------------------------------------------------------* | Reflow | | reflows all lines after the lineIndex so each line fits correctly | | into the newly sized window. | *----------------------------------------------------------------------*/ void Reflow (XmMultiTextWidget cw, LineList reflp) { int maxAscent, maxDescent, ascent, descent, width, xcur, ycur, x, y; LineList lp; WordList wp; /* start reflowing after the current line unless lp is set to NULL. */ /* in that case, reflow all lines. */ if (reflp == NULL) lp = cw->multiText.firstLine; else lp = reflp->next; for (; lp != NULL; lp = lp->next) { while (LineTooLong(cw, lp)) WrapLastWordToNextLine(cw, lp); } } /*----------------------------------------------------------------------* | ShiftFollowingLines2 | *----------------------------------------------------------------------*/ void ShiftFollowingLines2 (XmMultiTextWidget cw, LineList indexLine) { LineList lp, startLine; if (indexLine == NULL) startLine = cw->multiText.firstLine; else startLine = indexLine; if (startLine == NULL) return; for (lp = startLine; (lp != NULL) && (lp->next != NULL); lp = lp->next) { lp->next->y = lp->y + lp->bbox.ascent + lp->bbox.descent + lp->lineSpacing; } } /*----------------------------------------------------------------------* | DeleteWordFromLine | *----------------------------------------------------------------------*/ void DeleteWordFromLine (XmMultiTextWidget cw, WordList wp) { LineList lp; if (wp == NULL) return; if (wp->prev == NULL) wp->linePtr->firstWord == wp->next; else wp->prev->next = wp->next; if (wp->next == NULL) wp->linePtr->lastWord == wp->prev; else wp->next->prev = wp->prev; wp->linePtr->wordCount --; wp->linePtr->remaining += wp->bbox.width; FreeWordRecord(cw, wp); } /*----------------------------------------------------------------------* | Cut | *----------------------------------------------------------------------*/ static void Cut (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp; WordList wp; /* go through all text and find the selected text. */ /* this should probably not keep checking after the first chunk of selected text. */ /* however, future versions may allow multiple selections. */ for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) for (wp = lp->firstWord; wp != NULL; wp = wp->next) if (wp->selected) DeleteWordFromLine(cw, wp); Reflow(cw, NULL); } /*----------------------------------------------------------------------* | GetWholeLink | *----------------------------------------------------------------------*/ static char *GetWholeLink (Widget w, WordList wp) { XmMultiTextWidget cw = (XmMultiTextWidget) w; WordList wpStart, wpTemp; char *buf = "\0"; int len = 0;; if (!wp->linkInfo) return(buf); wpStart = wp; while ((wpStart->prev != NULL) && (SameLinks(wpStart->prev, wp))) wpStart = wpStart->prev; /* scan forward in the line till the end of the line or the end of the line. */ /* buf first find out how long this string is going to be. */ wpTemp = wpStart; while ((wpTemp != NULL) && (SameLinks(wpTemp, wp))) { len += strlen(wpTemp->chars) + 1; wpTemp = wpTemp->next; } buf = (char *) XtMalloc(sizeof(char) * len + 1); strcpy(buf, ""); while ((wpStart != NULL) && (SameLinks(wpStart, wp))) { if (strlen(buf)) sprintf(buf,"%s %s", buf, wpStart->chars); else strcpy(buf, wpStart->chars); wpStart = wpStart->next; } return(buf); } /*----------------------------------------------------------------------* | DeselectAll | *----------------------------------------------------------------------*/ static void DeselectAll (XmMultiTextWidget cw) { LineList lp; WordList wp; for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) for (wp = lp->firstWord; wp != NULL; wp = wp->next) if (wp->selected) { DrawWord(cw, wp, !wp->selected); wp->selected = FALSE; } cw->multiText.textIsSelected = FALSE; } /*-----------------------=======================------------------------* | MoveTo action procedure | | This routine is invoked (default case) on a Btn1Down event occurs in | | the widget's window. It then positions the cursor at the new | | location. Well, actually it does all sorts of things - none of which| | is permanent, so I really won't go into a full description here, yet.| *-----------------------=======================------------------------*/ static void MoveTo (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp = cw->multiText.firstLine; LineList clp; int actualX; WordList wp; Boolean foundLine = FALSE; Boolean foundWord = FALSE; XmMultiTextLinkCallbackStruct callValue; char *wholeLink; /* First scan all the lines until correct line is found. */ while ((lp != NULL) && !foundLine) { foundLine = PtInLine(event->xbutton.x, event->xbutton.y, lp); if (!foundLine) lp = lp->next; } /* Now scan the words to find the word in this line */ if (foundLine) { wp = lp->firstWord; while ((wp != NULL) && !foundWord) { foundWord = PtInWord(event->xbutton.x, event->xbutton.y, wp, lp); if (!foundWord) wp = wp->next; } /* Check if this is a Link. If so, setup the callback. */ if ((foundWord) && (wp->linkInfo != NULL) && (wp->linkInfo->linkType != NOOP)) { callValue.reason = XmCR_LINK; callValue.event = event; callValue.type = wp->linkInfo->linkType; callValue.posn = NewString(wp->linkInfo->linkPosn); callValue.data = NewString(wp->linkInfo->linkData); callValue.word = GetWholeLink(w, wp); if (XtHasCallbacks((Widget)cw, XmNlinkCallback) == XtCallbackHasSome) { XFlush(XtDisplay(w)); XtCallCallbacks((Widget)cw, XmNlinkCallback, &callValue); XtFree(callValue.posn); XtFree(callValue.data); XtFree(callValue.word); return; } XtFree(callValue.posn); XtFree(callValue.data); XtFree(callValue.word); } else DeselectAll((XmMultiTextWidget)cw); PositionCursor(cw, event->xbutton.x, event->xbutton.y, &clp, &actualX); UpdateCursor(cw, clp, event->xbutton.x, actualX, clp->y + clp->bbox.ascent); } else DeselectAll((XmMultiTextWidget)cw); /* now check for selections... */ cw->multiText.selecting = TRUE; /* set the widgets selection points. */ cw->multiText.startX = event->xbutton.x; cw->multiText.startY = event->xbutton.y; /* that's all the setup that's necessary - actions take care of the rest. */ } /*----------------------------------------------------------------------* | ChangeFocus | *----------------------------------------------------------------------*/ static void ChangeFocus(Widget w, XEvent* event, String* params, Cardinal* numParams) { } /*----------------------------------------------------------------------* | Enter | *----------------------------------------------------------------------*/ static void Enter (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; } /*----------------------------------------------------------------------* | Leave | *----------------------------------------------------------------------*/ static void Leave (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; } /*----------------------------------------------------------------------* | InFocus | *----------------------------------------------------------------------*/ static void InFocus (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; } /*----------------------------------------------------------------------* | OutFocus | *----------------------------------------------------------------------*/ static void OutFocus (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; } /*----------------------------------------------------------------------* | DeleteLine | *----------------------------------------------------------------------*/ static void DeleteLine (Widget w, XKeyPressedEvent *event, String *argv, Cardinal *argc) { /* argv hold a single argument telling if the line should be deleted */ /* from the TOP or the BOTTOM. */ switch (atoi(*argv)) { case TOP: XmMultiTextDeleteLineTop(w, 1); break; case BOTTOM: XmMultiTextDeleteLineBottom(w); break; default: ; } } /*----------------------------------------------------------------------* | OpenLineTop | *----------------------------------------------------------------------*/ static void OpenLineTop (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextOpenLineTop (w, 0); } /*----------------------------------------------------------------------* | AddWordTop | *----------------------------------------------------------------------*/ static void AddWordTop (Widget w, XEvent* event, String* params, Cardinal* numParams) { static int i=1; unsigned short status; char *str="testing "; sprintf(str, "testing%d", i++); status = XmMultiTextAppendWordTop (w, str, "hodges-24", "red", 0, NULL); } /*======================================================================* | Xmmultitext Widget Public Procedures | | ------------------------------------ | | The(se) routine(s) are defined at the beginning of the file in the | | list of actions. | *======================================================================*/ /*-----------------------====================---------------------------* | XmMultiTextClearText | *-----------------------====================---------------------------*/ void XmMultiTextClearText (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp, lpNext; int defaultHeight; lp = cw->multiText.firstLine; while (lp != NULL) { lpNext = lp->next; FreeLineRecord(cw, lp); XtFree((char*)lp); lp = lpNext; } cw->multiText.firstLine = NULL; cw->multiText.currentLine = NULL; cw->multiText.lastLine = NULL; /* clean up all the cursor stuff... */ if (cw->multiText.cursorFg != NULL) { XFreePixmap(XtDisplay(cw), cw->multiText.cursorFg); XFreePixmap(XtDisplay(cw), cw->multiText.cursorBg); XFreePixmap(XtDisplay(cw), cw->multiText.cursorMask); } if (cw->multiText.blinkTimeOutID != 0) XtRemoveTimeOut(cw->multiText.blinkTimeOutID); cw->multiText.blinkTimeOutID = 0; cw->multiText.selecting = FALSE; cw->multiText.textIsSelected = FALSE; cw->multiText.cursorFg = NULL; cw->multiText.cursorBg = NULL; cw->multiText.cursorMask = NULL; XClearArea(XtDisplay(cw), XtWindow(cw), 0, 0, 0, 0, TRUE); XSync(XtDisplay(cw), False); ChangeHeight((XmMultiTextWidget)w, XtParent(w)->core.height); } /*----------------------======================--------------------------* | XmMultiTextGetPosition | | Returns the last position of the data already entered. This tells | | where new data will be appended. | *----------------------======================--------------------------*/ int XmMultiTextGetPosition (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget) w; if (cw->multiText.lastLine == NULL) return(0); else return(cw->multiText.lastLine->y); } /*-------------------============================-----------------------* | XmMultiTextGetCursorPosition | | Returns the last position of the data already entered. This tells | | where new data will be appended. | *-------------------============================-----------------------*/ int XmMultiTextGetCursorPosition (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget) w; return(cw->multiText.cursorY); } /*-----------------------======================-------------------------* | XmMultiTextQueryCursor | *-----------------------======================-------------------------*/ void XmMultiTextQueryCursor (Widget w, int *lineNumber, int *y) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp; *lineNumber = 0; for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) { if (lp->y + lp->bbox.ascent + lp->bbox.descent > cw->multiText.cursorY) break; *lineNumber++; } if (lp == NULL) if (cw->multiText.lastLine != NULL) *y = cw->multiText.lastLine->y; else { *y = 0; *lineNumber = 0; } else *y = lp->y; } /*-----------------------======================-------------------------* | XmMultiTextDeselectAll | *-----------------------======================-------------------------*/ void XmMultiTextDeselectAll (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget) w; if (XtIsRealized(cw)) DeselectAll(cw); } /*----------------------======================--------------------------* | XmMultiTextSetTabStops | *----------------------======================--------------------------*/ void XmMultiTextSetTabStops (Widget w, int tabList[], int tabCount) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int i; /* enter the tabs into the widget. */ for (i=0; imultiText.tabs[i] = tabList[i]; cw->multiText.tabCount = tabCount; } /*---------------------=========================------------------------* | XmMultiTextMakeLinkRecord | *---------------------=========================------------------------*/ LinkInfoPtr XmMultiTextMakeLinkRecord (Widget w, LinkType linkType, char *linkPosn, char *linkData) { LinkInfoPtr li; li = (LinkInfoPtr) XtMalloc(sizeof(struct LinkRec)); if (li == NULL) XtError(MSG3); else { li->linkType = linkType; li->linkPosn = NewString(linkPosn); li->linkData = NewString(linkData); } return(li); } /*---------------------========================-------------------------* | XmMultiTextAppendNewLine | | (future version...) a new line should be treated as a word and should| | be assigned a font as any other word. This font should be used to | | determine the height of the new line. If a new word is inserted into | | the new line that is shorter, the line shouldn't shrink to match the | | new height until the font of the newline is changed. | *---------------------========================-------------------------*/ void XmMultiTextAppendNewLine (Widget w, char *theWord, char *fontName, int indent) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp; XFontStruct *font; int direction, fontAscent, fontDescent, yposn; XCharStruct boundingBox; /* Create a new line with the height of the current font. */ /* there are two cases, either the line before has some words in it, or it doesn't. */ /* if there are words in the previous line, use its height to figure the new posn. */ /* if there are no words, then calculate the new posn using the current font. */ /* Case 0 - No lines in widget yet. */ if (cw->multiText.firstLine == NULL) { font = NamedFont(cw, fontName); if (!font) { return; } XTextExtents(font, theWord, strlen(theWord), &direction, &fontAscent, &fontDescent, &boundingBox); if (fontAscent != boundingBox.ascent) boundingBox.ascent = fontAscent; if (fontDescent != boundingBox.descent) boundingBox.descent = fontDescent; yposn = cw->multiText.marginHeight; lp = NewLine(cw, indent, yposn, DEFAULT_LINE_SPACING); lp->bbox.ascent = fontAscent; lp->bbox.descent = fontDescent; cw->multiText.firstLine = lp; cw->multiText.lastLine = lp; } else /* Case 1 - Previous line has words in it already. */ /* Use the previous word for height info - sorta logical, I guess. */ if (cw->multiText.lastLine->firstWord != NULL) { yposn = cw->multiText.lastLine->y + cw->multiText.lastLine->bbox.ascent + cw->multiText.lastLine->bbox.descent + cw->multiText.lastLine->lineSpacing; lp = NewLine(cw, indent, yposn, cw->multiText.lastLine->lineSpacing); cw->multiText.lastLine->next = lp; lp->prev = cw->multiText.lastLine; cw->multiText.lastLine = lp; font = NamedFont(cw, fontName); if (!font) { return; } XTextExtents(font, theWord, strlen(theWord), &direction, &fontAscent, &fontDescent, &boundingBox); lp->bbox.ascent = fontAscent; lp->bbox.descent = fontDescent; } /* Case 2 - Previous line has no words in it - probably just a newline. */ else { font = NamedFont(cw, fontName); if (!font) { return; } XTextExtents(font, theWord, strlen(theWord), &direction, &fontAscent, &fontDescent, &boundingBox); if (fontAscent != boundingBox.ascent) boundingBox.ascent = fontAscent; if (fontDescent != boundingBox.descent) boundingBox.descent = fontDescent; yposn = cw->multiText.lastLine->y + fontAscent + fontDescent + cw->multiText.lastLine->lineSpacing; lp = NewLine(cw, indent, yposn, cw->multiText.lastLine->lineSpacing); lp->bbox.ascent = fontAscent; lp->bbox.descent = fontDescent; cw->multiText.lastLine->next = lp; lp->prev = cw->multiText.lastLine; cw->multiText.lastLine = lp; } cw->multiText.currentLine = cw->multiText.lastLine; /* set this as the current line. */ lp->newLine = TRUE; /* tag this newline as a 'hard' newline. */ } /*-----------------------=====================--------------------------* | XmMultiTextAppendWord | | This is called from the outside world. Words are appended to the | | current cursor position and have the following associated traits: | | Color, Font, Link. | | | | From here, the word will be appended as a single word into the data- | | structure of the XmMultiTextWidget. Any justification and other text | | handling is not done here. | | | | This routine calls the internal routine StoreWord which enters the | | new word structure to the current line. StoreWord returns status | | information depending on what occured when the new word was added to | | the line. | | A tab is entered as a complete word. The next word shouldn't add | | any wordspacing. | *-----------------------=====================--------------------------*/ unsigned short XmMultiTextAppendWord (Widget w, char *theWord, char *fontName, char *colorName, int indent, LinkInfoPtr linkInfo) { XmMultiTextWidget cw = (XmMultiTextWidget) w; WordList wordPtr; XFontStruct *font; XCharStruct boundingBox; unsigned short status; font = NamedFont (cw, fontName); if (!font) { return 0; } /* get the bounding box for this word (or tab). */ GetBoundingBox(cw, theWord, font, &boundingBox, indent); /* build the word record to add to the widget. */ wordPtr = (WordList) XtMalloc(sizeof(struct WordRec)); if (wordPtr == NULL) XtError(MSG6); wordPtr->charList = NULL; wordPtr->bbox = boundingBox; wordPtr->x = 0; wordPtr->y = 0; wordPtr->length = strlen(theWord); wordPtr->chars = NewString(theWord); wordPtr->font = font; wordPtr->spacing = 0; wordPtr->color = Color (cw, colorName); wordPtr->linkInfo = linkInfo; wordPtr->bbox.attributes = 0; wordPtr->image = NULL; wordPtr->widget = NULL; wordPtr->selected = FALSE; wordPtr->linePtr = NULL; wordPtr->next = NULL; wordPtr->prev = NULL; /* append the word structure on the widget's current line. */ status = StoreWord(cw, wordPtr, indent); return (status); } /*---------------------========================-------------------------* | XmMultiTextAppendWordTop | | This routine adds a single word to the first line in the MultiText | | widget. It requires that there is a first line. Additionally, word | | wrapping isn't yet supported and a warning is issued if the appended | | word is supposed to wrap. In any case, the word will be appended as | | though word wrapping is off. | | After the word is inserted, all lines after the current line must be | | updated to reflect the new height changes (if necessary). The prev | | pointer of the following line must also be changed. | *---------------------========================-------------------------*/ unsigned short XmMultiTextAppendWordTop (Widget w, char *theWord, char *fontName, char *colorName, int indent, LinkInfoPtr linkInfo) { XmMultiTextWidget cw = (XmMultiTextWidget) w; WordList wordPtr; XFontStruct *font; XCharStruct boundingBox; unsigned short status; /* this routine won't work if there is no top-line to append the new word. */ /* maybe later, I'll automatically create a new line in this case. But for */ /* now, just return and don't append the new word. */ if (cw->multiText.firstLine == NULL) { XtWarning(MSG9); return(APPEND_ERROR); } font = NamedFont (cw, fontName); if (!font) { return 0; } /* get the bounding box for this word (or tab). */ GetBoundingBox(cw, theWord, font, &boundingBox, indent); /* build the word record to add to the widget. */ wordPtr = (WordList) XtMalloc(sizeof(struct WordRec)); if (wordPtr == NULL) XtError(MSG6); wordPtr->charList = NULL; wordPtr->bbox = boundingBox; wordPtr->x = 0; wordPtr->y = 0; wordPtr->length = strlen(theWord); wordPtr->chars = NewString(theWord); wordPtr->font = font; wordPtr->spacing = 0; wordPtr->color = Color (cw, colorName); wordPtr->linkInfo = linkInfo; wordPtr->bbox.attributes = 0; wordPtr->image = NULL; wordPtr->widget = NULL; wordPtr->selected = FALSE; wordPtr->linePtr = NULL; wordPtr->next = NULL; wordPtr->prev = NULL; /* append the word structure on the widget's first line. */ status = AppendWordToTopLine(cw, wordPtr, indent); return (status); } /*-------------------============================-----------------------* | XmMultiTextAppendOpenLineTop | | | | NOTE - THE NEW LINE IS EMPTY AND HAS NO ASCENT OR DESCENT. YOU MUST | | PUT SOMETHING INTO IT OR IT WILL NOT SHOW UP. | | | | (Future...) should handle cases with no lines in the widget yet. | *-------------------============================-----------------------*/ void XmMultiTextOpenLineTop (Widget w, int indent) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp; lp = NewLine(cw, indent, cw->multiText.marginHeight, DEFAULT_LINE_SPACING); if (lp != NULL) { lp->next = cw->multiText.firstLine; if (lp->next != NULL) lp->next->prev = lp; cw->multiText.firstLine = lp; /* NOTE - THE ASCENT AND DESCENT OF THE LINE IS ZERO AT THIS POINT. */ } } /*----------------------------------------------------------------------* | ScrollWindow | | Note that a negative dy value scrolls up and a positive scrolls down.| | This routine assumes that it is completely visible - ie must be a | | child of a scrolled window (its working area). In addition, the | | scrolledWindow must be in "applicationDefined" mode. | *----------------------------------------------------------------------*/ static void ScrollWindow (XmMultiTextWidget cw, int dy) { if (!cw->multiText.exposeOnly) TurnOffCursor(cw); /* XFlush(XtDisplay(cw)); */ /* XSync(XtDisplay(cw), False); */ if (dy > 0) /* SCROLL DOWN - shift the contents of the screen down. */ { XCopyArea(XtDisplay(cw), XtWindow(cw), XtWindow(cw), cw->multiText.textGC, 0, cw->multiText.marginHeight, cw->core.width, cw->core.height - 2*cw->multiText.marginHeight - dy, 0, cw->multiText.marginHeight + dy); /* draw the lines that fit into the uncovered region. */ XClearArea(XtDisplay(cw), XtWindow(cw), 0, 0, cw->core.width, cw->multiText.marginHeight + dy, FALSE); DoRedrawText(cw, cw->multiText.marginHeight-dy, 2*dy); } else if (dy < 0) /* SCROLL UP - shift the contents of the screen up. */ { XCopyArea(XtDisplay(cw), XtWindow(cw), XtWindow(cw), cw->multiText.textGC, 0, cw->multiText.marginHeight + (-dy), cw->core.width, cw->core.height - 2*cw->multiText.marginHeight - (-dy), 0, cw->multiText.marginHeight); XClearArea(XtDisplay(cw), XtWindow(cw), 0, cw->core.height - cw->multiText.marginHeight - (-dy), cw->core.width, (-dy), FALSE); DoRedrawText(cw, cw->core.height - cw->multiText.marginHeight - (-dy), (-dy)); } if (!cw->multiText.exposeOnly) TurnOnCursor(cw); } /*---------------------========================-------------------------* | XmMultiTextDeleteLineTop | *---------------------========================-------------------------*/ int XmMultiTextDeleteLineTop (Widget w, int linesToDelete) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int dy, i; LineList lp = cw->multiText.firstLine, lp2; Arg args[2]; if ((cw->multiText.firstLine == NULL) || (linesToDelete <= 0)) return (0); for (i=0; (imultiText.firstLine; cw->multiText.firstLine = lp->next; if (lp->next != NULL) lp->next->prev = NULL; /* find how much to shift all the lines... note, line's know about the margin height. */ dy = cw->multiText.marginHeight - cw->multiText.firstLine->y; ShiftFollowingLines (lp, dy); if (cw->multiText.currentLine == lp) if (cw->multiText.firstLine->wordCount > 0) UpdateCursor(cw, cw->multiText.firstLine, cw->multiText.firstLine->x + cw->multiText.firstLine->firstWord->x, cw->multiText.firstLine->x + cw->multiText.firstLine->firstWord->x, cw->multiText.firstLine->y + cw->multiText.firstLine->bbox.ascent); else UpdateCursor(cw, cw->multiText.firstLine, cw->multiText.firstLine->x, cw->multiText.firstLine->x, cw->multiText.firstLine->y + cw->multiText.firstLine->bbox.ascent); else cw->multiText.cursorY += dy; if (!cw->multiText.exposeOnly) if (cw->multiText.smoothScroll) ScrollWindow(cw, dy); /* if you want to automatically update the widget's height, add that here... */ /* XtSetArg(args[0], XmNheight, cw->core.height + dy); XtSetValues(cw, args, 1); */ /* now free the memory held by the line (lp). */ FreeLineRecord (cw, lp); } if (!cw->multiText.exposeOnly) if (!cw->multiText.smoothScroll) XClearArea(XtDisplay(cw), XtWindow(cw), 0, 0, 0, 0, TRUE); return(i); } /*-------------------===========================------------------------* | XmMultiTextDeleteLineBottom | *-------------------===========================------------------------*/ void XmMultiTextDeleteLineBottom (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int dy; LineList lp = cw->multiText.firstLine; Arg args[2]; lp = cw->multiText.lastLine; if (lp != NULL) { cw->multiText.lastLine = lp->prev; if (lp->prev != NULL) lp->prev->next = NULL; /* if you want to automatically update the widget's height, add that here... */ /* dy = lp->bbox.ascent + lp->bbox.descent + lp->lineSpacing; XtSetArg(args[0], XmNheight, cw->core.height - dy); XtSetValues(cw, args, 1); */ /* now free the memory held by the line (lp). */ FreeLineRecord (cw, lp); } } /*------------------============================------------------------* | XmMultiTextLongestLineLength | *------------------============================------------------------*/ int XmMultiTextLongestLineLength (Widget w) { XmMultiTextWidget cw = (XmMultiTextWidget) w; LineList lp; int widestWidth = 0; for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) if (widestWidth < (lp->x + lp->bbox.width) ) widestWidth = lp->x + lp->bbox.width; return (widestWidth); } /*-----------------------======================-------------------------* | XmMultiTextAppendImage | *-----------------------======================-------------------------*/ unsigned short XmMultiTextAppendImage (Widget w, char *theWord, char *fontName, char *colorName, int indent, Pixmap image, unsigned int width, unsigned int height, LinkInfoPtr linkInfo) { XmMultiTextWidget cw = (XmMultiTextWidget) w; WordList wordPtr; XFontStruct *font; XCharStruct boundingBox; unsigned short status; font = NamedFont (cw, fontName); if (!font) { return 0; } /* get the bounding box for this word (or tab). */ GetBoundingBox(cw, theWord, font, &boundingBox, indent); /* build the word record to add to the widget. */ wordPtr = (WordList) XtMalloc(sizeof(struct WordRec)); if (wordPtr == NULL) XtError(MSG6); wordPtr->charList = NULL; wordPtr->x = 0; wordPtr->y = 0; wordPtr->length = strlen(theWord); /* doesn't really make sense for images... */ wordPtr->chars = NewString(theWord); /* 'theWord' is value returned if selected. */ wordPtr->font = font; wordPtr->spacing = 0; wordPtr->color = Color (cw, colorName); wordPtr->linkInfo = linkInfo; wordPtr->selected = FALSE; wordPtr->linePtr = NULL; wordPtr->next = NULL; wordPtr->prev = NULL; /* now setup the image information. */ wordPtr->image = image; wordPtr->widget = NULL; wordPtr->bbox.lbearing = 0; wordPtr->bbox.rbearing = width; wordPtr->bbox.width = width; wordPtr->bbox.ascent = (height+1) / 2; wordPtr->bbox.descent = height / 2; wordPtr->bbox.attributes = 0; /* append the word structure on the widget's current line. */ status = StoreWord(cw, wordPtr, indent); return (1); } /*-----------------------=======================------------------------* | XmMultiTextAppendWidget | *-----------------------=======================------------------------*/ unsigned short XmMultiTextAppendWidget (Widget w, char *theWord, char *fontName, char *colorName, int indent, Widget newW) { XmMultiTextWidget cw = (XmMultiTextWidget) w; WordList wordPtr; XFontStruct *font; XCharStruct boundingBox; unsigned short status; int n, width, height; Arg args[5]; int xoffset; /* line's indent + margin width */ int yposn; /* top of current line */ font = NamedFont (cw, fontName); if (!font) { return 0; } /* get the bounding box for this word (or tab). */ GetBoundingBox(cw, theWord, font, &boundingBox, indent); /* build the word record to add to the widget. */ wordPtr = (WordList) XtMalloc(sizeof(struct WordRec)); if (wordPtr == NULL) XtError(MSG6); wordPtr->charList = NULL; wordPtr->x = 0; wordPtr->y = 0; wordPtr->length = strlen(theWord); /* doesn't really make sense for widgets... */ wordPtr->chars = NewString(theWord); /* 'theWord' is value returned if selected. */ wordPtr->font = font; wordPtr->spacing = 0; wordPtr->color = Color (cw, colorName); wordPtr->linkInfo = NULL; /* can't have a widget as a link... */ wordPtr->selected = FALSE; wordPtr->linePtr = NULL; wordPtr->next = NULL; wordPtr->prev = NULL; /* find the size of the widget */ n = 0; XtSetArg(args[n], XmNwidth, &width); n++; XtSetArg(args[n], XmNheight, &height); n++; XtGetValues(newW, args, n); /* now setup the image information. */ wordPtr->image = NULL; wordPtr->widget = newW; wordPtr->bbox.lbearing = 0; wordPtr->bbox.rbearing = width; wordPtr->bbox.width = width; wordPtr->bbox.ascent = (height+1) / 2; wordPtr->bbox.descent = height / 2; wordPtr->bbox.attributes = 0; /* append the word structure on the widget's current line. */ status = StoreWord(cw, wordPtr, indent); xoffset = wordPtr->linePtr->x; yposn = wordPtr->linePtr->y + wordPtr->linePtr->bbox.ascent; XtMoveWidget(newW, xoffset + wordPtr->x, yposn - wordPtr->bbox.ascent); XtManageChild(newW); return (status); } /*-----------------------====================---------------------------* | XmMultiTextAppendDPS | *-----------------------====================---------------------------*/ unsigned short XmMultiTextAppendDPS (Widget w, char *theWord, char *fontName, char *colorName, int indent, char *dpsFileName, unsigned int width, unsigned int height, LinkInfoPtr linkInfo) { XmMultiTextWidget cw = (XmMultiTextWidget) w; Pixmap image; unsigned short status; #ifdef NODPS /* if the systems doesn't support DPS then just replace it with a substitute string. */ status = XmMultiTextAppendWord (w, "Graphics omitted from Online Documentation, please see the manual", fontName, colorName, indent, linkInfo); #else if (cw->multiText.dpsCapable) { image = GetDPSImage(cw, dpsFileName, &width, &height); /* append the word structure on the widget's current line. */ /* Note: 'theWord' is the value returned when an image is selected. */ status = XmMultiTextAppendImage (w, theWord, fontName, colorName, indent, image, width, height, linkInfo); } else { /* if the systems doesn't support DPS then just replace it with a substitute string. */ status = XmMultiTextAppendWord (w, "Graphics omitted from Online Documentation, please see the manual", fontName, colorName, indent, linkInfo); } #endif return (status != APPEND_ERROR); } /*-----------------------=====================--------------------------* | XmMultiTextAppendLine | *-----------------------=====================--------------------------*/ Boolean XmMultiTextAppendLine (Widget w, char *str) { XmMultiTextWidget cw = (XmMultiTextWidget) w; return True; } /*-----------------------=====================--------------------------* | XmMultiTextAppendChar | | add a character to the current word. This will cause the current | | word to redisplay. Perhapse the best method is to just show the | | character unless the character has a different format than the other | | letters. How to manage that is still a mystery... | *-----------------------=====================--------------------------*/ Boolean XmMultiTextAppendChar (Widget w, char ch) { XmMultiTextWidget cw = (XmMultiTextWidget) w; WordList wp; char *newWord; if ((cw->multiText.firstLine == NULL) || (cw->multiText.lastLine->firstWord == NULL)) XtWarning("Sorry - this isn't supported yet."); else { /* stuff this character onto the current word. */ wp = cw->multiText.lastLine->lastWord; wp->length++; newWord = (char *) XtMalloc(sizeof(char) * wp->length + 2); if (newWord == NULL) XtError(MSG6); sprintf(newWord, "%s%c", wp->chars, ch); XtFree(wp->chars); wp->chars = newWord; GetBoundingBox(cw, newWord, wp->font, &wp->bbox, cw->multiText.lastLine->indent); XClearArea(XtDisplay(w), XtWindow(w), wp->x + cw->multiText.lastLine->x, wp->y + cw->multiText.lastLine->y, wp->bbox.width, wp->bbox.ascent + wp->bbox.descent, TRUE); } return True; } /*======================================================================* | XmMultiText Widget Support Procedures | | ------------------------------------- | *======================================================================*/ /*----------------------------------------------------------------------* | NewString | *----------------------------------------------------------------------*/ static char *NewString(char *str) { char *retStr; retStr = (char *) XtMalloc(sizeof(char) * (str ? strlen(str) : 0) + 1); if (retStr == NULL) XtError(MSG6); if (str == NULL) retStr[0] = '\0'; else strcpy(retStr, str); return(retStr); } /*----------------------------------------------------------------------* | WarningDialog | *----------------------------------------------------------------------*/ static void WarningDialog (Widget parent, char *str) { XmString tcs; Arg args[2]; Widget dialog; tcs = XmStringLtoRCreate(MSG7, XmSTRING_DEFAULT_CHARSET); XtSetArg(args[0], XmNmessageString, tcs); dialog = XmCreateWarningDialog(parent, "dialog", args, 1); XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON)); XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); XtManageChild(dialog); XmStringFree(tcs); } /*----------------------------------------------------------------------* | ChangeHeight | *----------------------------------------------------------------------*/ static XtGeometryResult ChangeHeight (XmMultiTextWidget w, Dimension newHeight) { XtWidgetGeometry request, replyReturn; XtGeometryResult result; /* build the request */ request.request_mode = CWHeight; request.height = newHeight; result = XtMakeGeometryRequest((Widget)w, &request, &replyReturn); return (result); } /*----------------------------------------------------------------------* | GetBoundingBox | | sets the bounding box of the word. | *----------------------------------------------------------------------*/ static void GetBoundingBox(XmMultiTextWidget cw, char *theWord, XFontStruct *font, XCharStruct *boundingBox, int indent) { int direction, fontAscent, fontDescent, tabPos, tabWidth; /* if the word is a tab, must build bbox from scratch. */ if (theWord[0] == TAB) { /* calculate the bounding box depending on the current tab settings. */ tabPos = NextTab(cw, &tabWidth, indent); boundingBox->lbearing = 0; boundingBox->rbearing = tabWidth; boundingBox->width = tabWidth; boundingBox->ascent = 0; boundingBox->descent = 0; boundingBox->attributes = 0; } else { XTextExtents(font, theWord, strlen(theWord), &direction, &fontAscent, &fontDescent, boundingBox); boundingBox->ascent = fontAscent; boundingBox->descent = fontDescent; /* make sure bounding box uses font's ascent,descent */ } } /*----------------------------------------------------------------------* | ShiftFollowingLines | *----------------------------------------------------------------------*/ static void ShiftFollowingLines (LineList lines, int dy) { LineList lp; WordList wp; for (lp = lines; lp != NULL; lp = lp->next) { /* it shouldn't be necessary to adjust any of the words in the line */ /* since they are relative to the line's position. */ lp->y += dy; /* if the line has widgets - these may need moving. */ /* though it should probably be handled on redraw, it is faster */ /* if I handle it here. That way the checking is done only once. */ /* Scan though all words in the line and check if it's a widget. */ /* If so, move it vertically by dy. */ for (wp = lp->firstWord; wp != NULL; wp = wp->next) if (wp->widget != NULL) XtMoveWidget(wp->widget, wp->widget->core.x, dy + wp->widget->core.y); } } /*----------------------------------------------------------------------* | PtInLine | *----------------------------------------------------------------------*/ static Boolean PtInLine (int x, int y, LineList lp) { if (lp == NULL) return FALSE; if ((y >= lp->y) && (y < lp->y + lp->bbox.ascent + lp->bbox.descent)) return TRUE; else return FALSE; } /*----------------------------------------------------------------------* | CalculateSpacing | *----------------------------------------------------------------------*/ static void CalculateSpacing (Widget w, WordList wp, int *spaceBefore, int *spaceAfter) { int x1, x2, x3, w1, w2; *spaceBefore = 0; /* problems will occur if these aren't initialized. */ *spaceAfter = 0; if (wp->prev == NULL) *spaceBefore = 0; else *spaceBefore = (int)((wp->x - (wp->prev->x + wp->prev->bbox.width)) / 2); if (wp->next == NULL) *spaceAfter == 0; else *spaceAfter = (int)((wp->next->x - (wp->x + wp->bbox.width)) / 2 + 1); } /*----------------------------------------------------------------------* | SameLinks | | returns true if the links are the same, and false otherwise. | *----------------------------------------------------------------------*/ static Boolean SameLinks (WordList wp1, WordList wp2) { if ((wp1 == NULL) || (wp2 == NULL)) return(FALSE); else if ((wp1->linkInfo == NULL) || ((wp2->linkInfo == NULL))) return (FALSE); else if (wp1->linkInfo->linkType != wp2->linkInfo->linkType) return(FALSE); else if (STRCMP(wp1->linkInfo->linkPosn, wp2->linkInfo->linkPosn)) return(FALSE); else return(!STRCMP(wp1->linkInfo->linkData, wp2->linkInfo->linkData)); } /*----------------------------------------------------------------------* | SetCurrentFont | *----------------------------------------------------------------------*/ static void SetCurrentFont (XmMultiTextWidget w, WordList wp, XFontStruct *font) { /* to speed things up - only set the font if needed. */ if (wp->font->fid != w->multiText.currentFontID) { XSetFont(XtDisplay(w), w->multiText.textGC, wp->font->fid); w->multiText.currentFontID = wp->font->fid; } } /*----------------------------------------------------------------------* | SetCurrentColor | *----------------------------------------------------------------------*/ static void SetCurrentColor (XmMultiTextWidget w, WordList wp, unsigned long color) { /* to speed things up - only set the color if needed. */ if (wp->color != w->multiText.currentColor) { XSetForeground(XtDisplay(w), w->multiText.textGC, wp->color); w->multiText.currentColor = wp->color; } } /*----------------------------------------------------------------------* | DisplayImage | *----------------------------------------------------------------------*/ static void DisplayImage (XmMultiTextWidget w, WordList wp) { int xoffset = wp->linePtr->x; /* line's indent + margine width */ int yposn = wp->linePtr->y + wp->linePtr->bbox.ascent; /* top of the current line. */ XCopyArea(XtDisplay(w), wp->image, XtWindow(w), w->multiText.textGC, 0, 0, wp->bbox.width, wp->bbox.ascent + wp->bbox.descent, xoffset + wp->x, yposn - wp->bbox.ascent); } /*----------------------------------------------------------------------* | DisplayWord | | (future...) to fix the problem of jaged selection, erase the full | | lineheight before drawing the string. | *----------------------------------------------------------------------*/ static void DisplayWord (XmMultiTextWidget w, WordList wp) { int xoffset = wp->linePtr->x; /* line's indent + margine width */ int yposn = wp->linePtr->y + wp->linePtr->bbox.ascent; /* baseline position */ /* X always draws strings at the */ /* baseline, not upperleft corner*/ SetCurrentFont(w, wp, wp->font); SetCurrentColor(w, wp, wp->color); if (wp->image != NULL) DisplayImage (w, wp); else if (wp->widget != NULL) /* do nothing */; else XDrawString(XtDisplay(w), XtWindow(w), w->multiText.textGC, wp->x + xoffset, yposn, wp->chars, wp->length); } /*----------------------------------------------------------------------* | DisplaySelectedWord | | (future...) to fix the problem of jaged selection, fill the full line| | height on selections, not only the word's bounding box. | | The selection region should eventually be the same as the fill rgn. | *----------------------------------------------------------------------*/ static void DisplaySelectedWord (XmMultiTextWidget w, WordList wp) { Pixel fg = wp->color; Pixel bg = w->core.background_pixel; int i, gap, spaceBefore, spaceAfter, x[4], y[4]; int xoffset = wp->linePtr->x; /* line's indent + margine width */ int yposn = wp->linePtr->y + wp->linePtr->bbox.ascent; /* baseline position */ /* X always draws strings at the */ /* baseline, not upperleft corner*/ SetCurrentFont(w, wp, wp->font); SetCurrentColor(w, wp, wp->color); CalculateSpacing((Widget)w, wp, &spaceBefore, &spaceAfter); /* clear the area first. */ XClearArea(XtDisplay(w), XtWindow(w), xoffset + wp->x - spaceBefore, yposn - wp->bbox.ascent, wp->bbox.width + spaceBefore + spaceAfter, wp->bbox.ascent + wp->bbox.descent, FALSE); if (wp->image != NULL) DisplayImage (w, wp); else if (wp->widget != NULL) /* do nothing */; else XDrawString(XtDisplay(w), XtWindow(w), w->multiText.textGC, wp->x + xoffset, yposn, wp->chars, wp->length); /* draw an xor box at the mouse position */ /* should use... ->primitive.foreground ^ ->core.background_pixel */ XSetForeground(XtDisplay(w), w->multiText.textGC, WhitePixelOfScreen(XtScreen(w))); XSetBackground(XtDisplay(w), w->multiText.textGC, BlackPixelOfScreen(XtScreen(w))); XSetFunction (XtDisplay(w), w->multiText.textGC, GXxor); XFillRectangle(XtDisplay(w), XtWindow(w), w->multiText.textGC, xoffset + wp->x - spaceBefore, yposn - wp->bbox.ascent, wp->bbox.width + spaceBefore + spaceAfter, wp->bbox.ascent + wp->bbox.descent); XSetFunction (XtDisplay(w), w->multiText.textGC, GXcopy); XSetForeground(XtDisplay(w), w->multiText.textGC, fg); XSetBackground(XtDisplay(w), w->multiText.textGC, bg); } /*----------------------------------------------------------------------* | DisplayDeselectedWord | | fix for selections goes here too... | *----------------------------------------------------------------------*/ static void DisplayDeselectedWord (XmMultiTextWidget w, WordList wp) { Pixel fg = wp->color; Pixel bg = w->core.background_pixel; int i, gap, spaceBefore, spaceAfter, x[4], y[4]; int xoffset = wp->linePtr->x; /* line's indent + margine width */ int yposn = wp->linePtr->y + wp->linePtr->bbox.ascent; /* baseline position */ /* X always draws strings at the */ /* baseline, not upperleft corner*/ SetCurrentFont(w, wp, wp->font); SetCurrentColor(w, wp, wp->color); CalculateSpacing((Widget)w, wp, &spaceBefore, &spaceAfter); XClearArea(XtDisplay(w), XtWindow(w), xoffset + wp->x - spaceBefore, yposn - wp->bbox.ascent, wp->bbox.width + spaceBefore + spaceAfter, wp->bbox.ascent + wp->bbox.descent, FALSE); if (wp->image != NULL) DisplayImage (w, wp); else if (wp->widget != NULL) { XtUnmapWidget(wp->widget); XtMapWidget(wp->widget); } else XDrawString(XtDisplay(w), XtWindow(w), w->multiText.textGC, wp->x + xoffset, yposn, wp->chars, wp->length); } /*----------------------------------------------------------------------* | DrawLinkMarking | | this routine draws the rectangle around a link. | *----------------------------------------------------------------------*/ static void DrawLinkMarking (XmMultiTextWidget w, WordList wp) { int x[4], y[4]; int xoffset = wp->linePtr->x; /* line's indent + margine width */ int yposn = wp->linePtr->y + wp->linePtr->bbox.ascent; /* baseline position */ /* X always draws strings at the */ /* baseline, not upperleft corner*/ /* don't close the box if the next word is the same link */ if (SameLinks(wp->next, wp)) x[0] = xoffset + wp->next->x; else x[0] = xoffset + wp->x + wp->bbox.width; y[0] = yposn - wp->linePtr->bbox.ascent -1; x[1] = xoffset + wp->x; y[1] = y[0]; x[2] = x[1]; y[2] = yposn + wp->linePtr->bbox.descent; x[3] = x[0]; y[3] = y[2]; XDrawLine(XtDisplay(w), XtWindow(w), w->multiText.textGC, x[0], y[0], x[1], y[1]); XDrawLine(XtDisplay(w), XtWindow(w), w->multiText.textGC, x[2], y[2], x[3], y[3]); if (!SameLinks(wp->next, wp)) XDrawLine(XtDisplay(w), XtWindow(w), w->multiText.textGC, x[3], y[3], x[0], y[0]); if (!SameLinks(wp, wp->prev)) XDrawLine(XtDisplay(w), XtWindow(w), w->multiText.textGC, x[1], y[1], x[2], y[2]); } /*----------------------------------------------------------------------* | DrawWord | | xoffset is the left edge of the line. yposn is the baseline of the | | line for this word. | | the boolean 'selected' tells if the word becomes selected or not. | | if it has the same value as the word's internal value, then don't | | change it; ie: if it is not selected and 'selected' is false, then | | don't bother clearing the word's bounding box before drawing it. | *----------------------------------------------------------------------*/ static void DrawWord (XmMultiTextWidget w, WordList wp, Boolean selected) { /* this routine just calls the appropriate version of the drawing */ /* routine depending on the type and style of the word. */ /* if this is a tab, return */ if (wp->chars[0] == TAB) return; /* if you don't want the image to be highlighted when selected, uncomment these lines. */ /* if (wp->image != NULL) DisplayImage(w, wp); else */ /* if the word isn't selected and isn't becomming selected, draw normally */ if (!wp->selected && !selected) DisplayWord(w, wp); else /* if the word isn't selected and it should become selected, draw it selected */ if (!wp->selected && selected) DisplaySelectedWord(w, wp); else /* if the word is selected and should become deselected, unselect it. */ if (wp->selected && !selected) DisplayDeselectedWord(w, wp); else /* and finally, if the word was selected and still is, draw it selected */ if (wp->selected && selected) DisplaySelectedWord(w, wp); else /* the above cases should cover all possibilities! */ XtError("DrawWord: detected inconsistency"); /* now handle links. Do whatever drawing is required */ /* to show that this word is a link if needed. */ if ((wp->linkInfo != NULL) && (wp->linkInfo->linkType != NOOP)) DrawLinkMarking(w, wp); } /*----------------------------------------------------------------------* | DrawLine | | call DrawWord for each word in the line. | *----------------------------------------------------------------------*/ static void DrawLine (XmMultiTextWidget w, LineList lp) { WordList wp; int i; if (lp != NULL) { wp = lp->firstWord; for (i = 0; i < lp->wordCount; i++) { DrawWord(w, wp, wp->selected); wp = wp->next; } } } /*----------------------------------------------------------------------* | ResizeTheLine | | this routine will move each word in the line to the correct baseline | | position corresponding to its line. | | theWord is can be passed to this routine to enable a line to be re- | | sized before theWord is appended. If you just want to resize the | | line, pass theWord as NULL. | *----------------------------------------------------------------------*/ static void ResizeTheLine (XmMultiTextWidget w, LineList lp, WordList theWord) { WordList wp; Dimension newHeight; int xoffset, yposn, maxAscent, maxDescent; if (lp == NULL) return; if (theWord == NULL) theWord = lp->firstWord; if (theWord == NULL) { lp->bbox.ascent = 0; lp->bbox.descent = 0; return; } /* know that theWord points to a valid word - may be first in line or new word. */ maxAscent = theWord->bbox.ascent; maxDescent = theWord->bbox.descent; for (wp = lp->firstWord; wp != NULL; wp = wp->next) { maxAscent = MAX(maxAscent, wp->bbox.ascent); maxDescent = MAX(maxDescent, wp->bbox.descent); } lp->bbox.ascent = maxAscent; lp->bbox.descent = maxDescent; wp = lp->firstWord; while (wp != NULL) { wp->y = lp->bbox.ascent - wp->bbox.ascent; /* check if this is a widget, then move the widget. */ if (wp->widget != NULL) { xoffset = wp->linePtr->x; yposn = wp->linePtr->y + wp->linePtr->bbox.ascent; XtMoveWidget(wp->widget, xoffset + wp->x, yposn - wp->bbox.ascent); } wp = wp->next; } /* if this line is below the bottom of the widget, then resize the widget to fit. */ newHeight = lp->y + lp->bbox.ascent + lp->bbox.descent; if (w->core.height < newHeight + w->multiText.marginHeight) ChangeHeight(w, newHeight + (XtParent(w)->core.height/2)); } /*----------------------------------------------------------------------* | FreeCharRecord | *----------------------------------------------------------------------*/ static void FreeCharRecord (XmMultiTextWidget w, CharList cp) { XtError("MultiText FreeCharRecord: charlist not initialized correctly"); } /*----------------------------------------------------------------------* | FreeWordRecord | *----------------------------------------------------------------------*/ static void FreeWordRecord (XmMultiTextWidget w, WordList wp) { CharList cp, cpNext; cp = wp->charList; while (cp != NULL) { cpNext = cp->next; FreeCharRecord(w, cp); XtFree((char*)cp); cp = cpNext; } if (wp->image != NULL) XFreePixmap(XtDisplay(w), wp->image); if (wp->widget != NULL) XtDestroyWidget(wp->widget); if (wp->chars != NULL) XtFree(wp->chars); if (wp->linkInfo != NULL) { XtFree(wp->linkInfo->linkPosn); XtFree(wp->linkInfo->linkData); XtFree((char*)wp->linkInfo); } /* if (wp->font != NULL) XtFree(wp->font); - Don't even think of doing this! */ } /*----------------------------------------------------------------------* | FreeLineRecord | *----------------------------------------------------------------------*/ static void FreeLineRecord (XmMultiTextWidget w, LineList lp) { WordList wp, wpNext; wp = lp->firstWord; while (wp != NULL) { wpNext = wp->next; FreeWordRecord(w, wp); XtFree((char*)wp); wp = wpNext; } } /*----------------------------------------------------------------------* | NextTab | *----------------------------------------------------------------------*/ static int NextTab (XmMultiTextWidget w, int *tabWidth, int indent) { int i, nextTabPos, currentPos; /* first find out where we are. */ if ((w->multiText.lastLine == NULL) || (w->multiText.lastLine->lastWord == NULL)) currentPos = indent; else currentPos = w->multiText.lastLine->lastWord->x + w->multiText.lastLine->lastWord->bbox.width; /* there are no tabs in this widget. nextTabPos is current posn. */ if (w->multiText.tabCount == 0) { *tabWidth = 0; nextTabPos = currentPos; } else { /* must hunt for the correct tab... */ for (i=0; ((currentPos > w->multiText.tabs[i]) && (i < w->multiText.tabCount)); i++); /* if there was no tab after the current posn, ignore the tab. */ if ((i == w->multiText.tabCount) && (currentPos > w->multiText.tabs[i])) nextTabPos = currentPos; else nextTabPos = w->multiText.tabs[i]; *tabWidth = w->multiText.tabs[i] - currentPos; } return (nextTabPos); } /*----------------------------------------------------------------------* | Color | | Note this will only lookup a new color if the name is different than | | the last one. | *----------------------------------------------------------------------*/ static unsigned long Color (XmMultiTextWidget w, char *name) { Colormap cmap; int status; if (STRCMP(w->multiText.lastColorName, name) != 0) { cmap = DefaultColormap(XtDisplay(w), XScreenNumberOfScreen(XtScreen(w))); w->multiText.color.pixel = (unsigned long)0; status = XParseColor(XtDisplay(w), cmap, name, &w->multiText.color); XAllocColor(XtDisplay(w), cmap, &w->multiText.color); if (w->multiText.lastColorName != NULL) XtFree(w->multiText.lastColorName); w->multiText.lastColorName = NewString(name); } return (w->multiText.color.pixel); } /*----------------------------------------------------------------------* | NamedFont | | Note this will only lookup a new font if the name is different than | | the last one. | *----------------------------------------------------------------------*/ static XFontStruct *NamedFont (XmMultiTextWidget w, char *name) { int width; char buf[MAX_STR_LEN]; int i=0; for (i = 0; i < w->multiText.numFonts; ++i) { if (STRCMP(w->multiText.fontCache[i].name, name) == 0) { if (w->multiText.smartSpacing) { width = XTextWidth(w->multiText.fontCache[i].font, " ", 1); if (width != 0) w->multiText.wordSpacing = width; } else w->multiText.wordSpacing = 0; return(w->multiText.fontCache[i].font); } } /* We didn't find the font, get the info and add it to our cache */ w->multiText.numFonts++; w->multiText.fontCache = (XmMultiTextFontCache)XtRealloc((char *)w->multiText.fontCache, (i + 1) * sizeof (XmMultiTextFontCacheRec)); w->multiText.fontCache[i].name = NewString(name); w->multiText.fontCache[i].font = XLoadQueryFont(XtDisplay(w), name); if (w->multiText.fontCache[i].font == NULL) { sprintf(buf, MSG4, name, DEFAULT_FONT_NAME); XtWarning(buf); w->multiText.fontCache[i].font = XLoadQueryFont(XtDisplay(w), DEFAULT_FONT_NAME); if (w->multiText.fontCache[i].font == NULL) { sprintf(buf, MSG5, DEFAULT_FONT_NAME); XtWarning(buf); return NULL; } } if (w->multiText.smartSpacing) { width = XTextWidth(w->multiText.fontCache[i].font, " ", 1); if (width != 0) w->multiText.wordSpacing = width; } else w->multiText.wordSpacing = 0; return (w->multiText.fontCache[i].font); } /*----------------------------------------------------------------------* | NewLine | *----------------------------------------------------------------------*/ static LineList NewLine (XmMultiTextWidget cw, int indent, int yposn, int lineSpacing) { LineList lp; Dimension newHeight; lp = (LineList) XtMalloc(sizeof(struct LineRec)); if (lp == NULL) XtError(MSG6); else { lp->firstWord = NULL; lp->lastWord = NULL; lp->bbox.lbearing = 0; lp->bbox.rbearing = 0; lp->bbox.width = 0; lp->bbox.ascent = 0; lp->bbox.descent = 0; lp->bbox.attributes = 0; lp->x = indent + cw->multiText.marginWidth; lp->y = MAX(yposn, cw->multiText.marginHeight); lp->wordCount = 0; lp->indent = indent; lp->remaining = cw->core.width - 2*cw->multiText.marginWidth - indent; lp->lineSpacing = lineSpacing; lp->next = NULL; lp->prev = NULL; /* don't want to force the prev to be the last line just yet. */ /* if this line is below the bottom of the widget, the resize the widget to fit. */ /* this is also done when an existing line is resized. */ newHeight = (Dimension)(lp->y + lp->bbox.ascent + lp->bbox.descent); if (cw->core.height < newHeight + cw->multiText.marginHeight) ChangeHeight(cw, newHeight + (XtParent(cw)->core.height/RESIZE_FRACTION)); } return (lp); } /*----------------------------------------------------------------------* | StuffWordOntoCurrentLine | | each word is stored at an x,y location which is relative to the line | | that contains this word. A word's x,y posn IS NOT the location in | | the multitext window! | | If any resizing of the line is needed, it will be handled here. The | | calling routine will be notified if any resizing was done so that | | any redrawing of the line can be performed. | *----------------------------------------------------------------------*/ static Boolean StuffWordOntoCurrentLine (XmMultiTextWidget w, LineList lp, WordList wp, int neededWidth, Boolean firstWordOfLine, Boolean isTab, Boolean prevWordIsTab) { Boolean resizeLine = FALSE; int currentPos; if (lp->firstWord == NULL) /* zero, not indent since word offset from lp and is already indented correctly. */ wp->x = 0; else /* move current posn to the end of the last word (no spacing included - yet!) */ wp->x = lp->lastWord->x + lp->lastWord->bbox.width; /* now add the spacing that should go before this word. */ /* wordspacing is always the spacing used by the font of the previous word. */ if (!isTab && !prevWordIsTab && (lp->firstWord != NULL)) { wp->x += w->multiText.wordSpacing /* lp->wordSpacing */; wp->spacing = w->multiText.wordSpacing; } /* find out if resize is necessary. If so, resize the line. */ if ((wp->bbox.ascent > lp->bbox.ascent) || (wp->bbox.descent > lp->bbox.descent)) { ResizeTheLine(w, lp, wp); resizeLine = TRUE; } /* append the wordStructure to the line's structure. */ wp->prev = lp->lastWord; /* each word should know about the previous word. */ if (lp->firstWord == NULL) lp->firstWord = wp; else lp->lastWord->next = wp; lp->lastWord = wp; /* update the parameters of the line. */ lp->bbox.width += neededWidth; lp->wordCount ++; lp->remaining -= neededWidth; /* update the parameters of the word. */ wp->y = lp->bbox.ascent - wp->bbox.ascent; wp->linePtr = lp; return (resizeLine); } /*----------------------------------------------------------------------* | GetNeededWidth | *----------------------------------------------------------------------*/ static int GetNeededWidth (XmMultiTextWidget w, WordList wPtr, int indent, Boolean *firstWordOfLine, Boolean *isTab, Boolean *prevWordIsTab) { int neededWidth; /* lets start with the width of the word. */ neededWidth = wPtr->bbox.width; /* special handling for: first word, tabs, and words following tabs. */ *firstWordOfLine = ((w->multiText.lastLine == NULL) || (w->multiText.lastLine->lastWord == NULL)); *isTab = (wPtr->chars[0] == TAB); *prevWordIsTab = ((w->multiText.lastLine != NULL) && (w->multiText.lastLine->lastWord != NULL) && (w->multiText.lastLine->lastWord->chars[0] == TAB)); /* if this isn't a tab, then add the indent to the amount that this word requires. */ /* this is added since indented lines are just shifted - bounding boxes don't keep this info. */ if (!isTab) neededWidth += indent; if (!(*firstWordOfLine) && !(*prevWordIsTab) && !(*isTab)) neededWidth += w->multiText.wordSpacing; /* spacing of current font is held by widget. */ return(neededWidth); } /*----------------------------------------------------------------------* | StoreWord | | This routine stores the new word into the current line. If this is | | the first word in the document, then create a new line before | | inserting it. Before appending the current word to the line, check | | to make sure that it will fit. The word's size is its bounding plus | | the size of the space between it and the previous word. | | Finally, if the word doesn't fit, print the previous full line and | | create a new line for the word. | *----------------------------------------------------------------------*/ static unsigned short StoreWord (XmMultiTextWidget w, WordList wordPtr, int indent) { LineList lp; Boolean resizeLine, firstWordOfLine, isTab, prevWordIsTab; unsigned short returnVal; int neededWidth, yposn; /* find the amount of space this word takes. This includes any indents and wordspacing. */ /* this is used to determine if this word fits onto this line. (~Word width + indent) */ neededWidth = GetNeededWidth(w, wordPtr, indent, &firstWordOfLine, &isTab, &prevWordIsTab); /* case 1: no lines in the widget -> make a new one and add the word. */ if (w->multiText.firstLine == NULL) { lp = NewLine(w, indent, 0, DEFAULT_LINE_SPACING); w->multiText.firstLine = lp; w->multiText.lastLine = lp; resizeLine = StuffWordOntoCurrentLine(w, lp, wordPtr, neededWidth, firstWordOfLine, isTab, prevWordIsTab); returnVal = APPEND_FIRST; } else /* case 2: word doesn't fit on this line and wordwrapping is on. */ if ((neededWidth > w->multiText.lastLine->remaining) && w->multiText.wordWrap) { yposn = w->multiText.lastLine->y + w->multiText.lastLine->bbox.ascent + w->multiText.lastLine->bbox.descent + w->multiText.lastLine->lineSpacing; lp = NewLine(w, indent, yposn, w->multiText.lastLine->lineSpacing); w->multiText.lastLine->next = lp; lp->prev = w->multiText.lastLine; resizeLine = StuffWordOntoCurrentLine(w, lp, wordPtr, neededWidth, firstWordOfLine, isTab, prevWordIsTab); w->multiText.lastLine = lp; returnVal = APPEND_FIRST; } else /* case 3: word doesn't fit and wordwrapping is off. */ if ((neededWidth > w->multiText.lastLine->remaining) && !w->multiText.wordWrap) { resizeLine = StuffWordOntoCurrentLine(w, w->multiText.lastLine, wordPtr, neededWidth, firstWordOfLine, isTab, prevWordIsTab); returnVal = APPEND_OK; if (resizeLine) returnVal = APPEND_RESIZE; /* this should probably be APPEND_CLIP_RESIZE... */ else returnVal = APPEND_OK; /* this should probably be APPEND_CLIP... */ } else /* case 4: the word fits onto the current line. */ if (neededWidth <= w->multiText.lastLine->remaining) { resizeLine = StuffWordOntoCurrentLine(w, w->multiText.lastLine, wordPtr, neededWidth, firstWordOfLine, isTab, prevWordIsTab); if (resizeLine) returnVal = APPEND_RESIZE; else returnVal = APPEND_OK; } /* set the current line to the one that was just appended to. */ UpdateCursor(w, w->multiText.lastLine, w->multiText.lastLine->x + w->multiText.lastLine->lastWord->x + w->multiText.lastLine->lastWord->bbox.width, w->multiText.lastLine->x + w->multiText.lastLine->lastWord->x + w->multiText.lastLine->lastWord->bbox.width, w->multiText.lastLine->y + w->multiText.lastLine->bbox.ascent); /* now draw what's necessary - Compile with -DREALTIME to see each word as it's appended. */ #ifdef REALTIME if (XtIsRealized) DrawWord(w, wordPtr, FALSE); #endif return(returnVal); } /*----------------------------------------------------------------------* | AppendWordToTopLine | | This routine will append the given word to the first line in the | | widget. There must be a line to append to. In addition, the word is | | not allowed to wrap to the next line since this isn't supported. If | | wordWrapping is off - no problem; however, if it is ON then there is | | only a problem if the word is too long for the line. | | All resizing of the top line and all following lines is handled in | | this routine. | | THIS ~ASSUMES THAT THIS IS THE ONLY WORD OF THE LINE!!! SOME CLEANUP | | WILL BE NECESSARY FOR GENERAL CASES. | *----------------------------------------------------------------------*/ static unsigned int AppendWordToTopLine(XmMultiTextWidget cw, WordList wordPtr, int indent) { int maxAscent, maxDescent, dy; int textBottomY; unsigned int status; LineList lp; /* this is redundant since this must be false if we are here... */ if (cw->multiText.firstLine == NULL) { XtWarning(MSG9); return(APPEND_ERROR); } /* find the amount that all lines must be shifted down. */ maxAscent = MAX(wordPtr->bbox.ascent, cw->multiText.firstLine->bbox.ascent); maxDescent = MAX(wordPtr->bbox.descent, cw->multiText.firstLine->bbox.descent); dy = (maxAscent + maxDescent) - (cw->multiText.firstLine->bbox.ascent + cw->multiText.firstLine->bbox.descent); /* if the text is too large for the widget, then resize to fit. (Bottom of last line is below window. */ textBottomY = cw->multiText.lastLine->y + cw->multiText.lastLine->bbox.ascent + cw->multiText.lastLine->bbox.descent; if (cw->core.height - cw->multiText.marginHeight < dy + textBottomY) ChangeHeight(cw, dy + textBottomY + cw->multiText.marginHeight + (XtParent(cw)->core.height/2)); lp = cw->multiText.firstLine; if ((dy > 0) && (lp->next != NULL)) ShiftFollowingLines(lp->next, dy); /* now append the word and adjust the current line - all following lines are now correct. */ /* note that this line isn't really the correct format; it's newline parameter may be wrong. */ if (lp->wordCount == 0) { wordPtr->x = 0; lp->lastWord = lp->firstWord = wordPtr; } else { wordPtr->x = lp->lastWord->x + lp->lastWord->bbox.width; /* NOT GENERAL... */ lp->lastWord->next = wordPtr; lp->lastWord = wordPtr; } wordPtr->linePtr = cw->multiText.firstLine; lp->bbox.ascent = maxAscent; lp->bbox.descent = maxDescent; lp->wordCount++; /* now check if we have a word wrapping problem - in any case, don't wrap! */ if ((lp->remaining < wordPtr->bbox.width) && (wordPtr->wordWrapping)) XtWarning(MSG8); /* currently, multiText isn't consistent with wordwrapping. Sometimes it uses */ /* resource value and sometimes it uses the word's value. THIS WILL HAVE TO BE */ /* FIXED! however, in the mean time - check both values. */ if ((lp->remaining < wordPtr->bbox.width) && (cw->multiText.wordWrap)) XtWarning(MSG8); /* if, or if not wordwrapping, force the new word at the end of the first line. */ lp->remaining -= wordPtr->bbox.width; /* NOT GENERAL... */ lp->bbox.width += wordPtr->bbox.width; /* NOT GENERAL... */ TurnOffCursor(cw); cw->multiText.cursorY += dy; if (!cw->multiText.exposeOnly) if (dy > 0) ScrollWindow(cw, dy); return APPEND_OK; } /*----------------------------------------------------------------------* | PositionCursor | | This routine returns the correct position for a cursor given x, y. | | Since the cursor position must be on a word boundry, the actual X | | position may be different from the cursor X position. The Y posn is | | always correct - or atleast it should be :-) | | Note - this routine DOES NOT change or move the cursor! | *----------------------------------------------------------------------*/ static void PositionCursor (XmMultiTextWidget cw, int x, int y, LineList *currentLine, int *actualX) { LineList lp; WordList wp, closestWord; int dx; if (cw->multiText.currentLine == NULL) { fprintf(stderr, "ERROR - NO-TEXT CASE Should not occur!\n"); return; } /* find the line containing the new position. */ /* for speed, check the immediate neighborhood (1 up or 1 down) first */ if (PtInLine(x, y, cw->multiText.currentLine)) lp = cw->multiText.currentLine; else if (PtInLine(x, y, cw->multiText.currentLine->next)) lp = cw->multiText.currentLine->next; else if (PtInLine(x, y, cw->multiText.currentLine->prev)) lp = cw->multiText.currentLine->prev; else /* else, find line containing the pt. */ for (lp = cw->multiText.firstLine; lp != NULL; lp = lp->next) if (PtInLine(x, y, lp)) break; /* if not in any line, then ring bell - went off screen. */ if (lp == NULL) { XBell(XtDisplay(cw), 0); /* set cursor info to current positions. */ *actualX = cw->multiText.actualX; *currentLine = cw->multiText.currentLine; return; } /* now to find the horizontal location of the cursor. Either before, after, or middle of line. */ if ((x <= lp->x) || (lp->lastWord == NULL)) *actualX = lp->x; else if (x >= (lp->x + lp->lastWord->x + lp->lastWord->bbox.width)) *actualX = lp->x + lp->lastWord->x + lp->lastWord->bbox.width; else { dx = lp->bbox.width; closestWord = lp->firstWord; for (wp = lp->firstWord; wp != NULL; wp = wp->next) if (dx > ABS((x - lp->x) - wp->x)) { dx = ABS((x - lp->x) - wp->x); closestWord = wp; } *actualX = lp->x + closestWord->x; } *currentLine = lp; } /*----------------------------------------------------------------------* | UpdateCursor | *----------------------------------------------------------------------*/ static void UpdateCursor (XmMultiTextWidget cw, LineList lp, int x, int actualX, int actualY) { cw->multiText.cursorX = x; cw->multiText.cursorY = actualY; cw->multiText.actualX = actualX; cw->multiText.currentLine = lp; } /*----------------------------------------------------------------------* | MoveUp | | Moving up dowsn't change the horizontally stored position. That way | | we always scroll back to the same position. | | Note, cursor is always at the baseline of the line. | *----------------------------------------------------------------------*/ static void MoveUp (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int actualX; int currentX = cw->multiText.cursorX; int currentY; LineList lp; if (cw->multiText.textIsSelected) DeselectAll(cw); lp = cw->multiText.currentLine; if (lp->prev == NULL) { currentY = cw->multiText.cursorY; XBell(XtDisplay(cw), 0); } else currentY = lp->prev->y + lp->prev->bbox.ascent; PositionCursor(cw, currentX, currentY, &lp, &actualX); UpdateCursor(cw, lp, currentX, actualX, currentY); } /*----------------------------------------------------------------------* | MoveDown | | Note, cursor is always at the baseline of the line. | *----------------------------------------------------------------------*/ static void MoveDown (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int actualX; int currentX = cw->multiText.cursorX; int currentY; LineList lp; if (cw->multiText.textIsSelected) DeselectAll(cw); lp = cw->multiText.currentLine; if (lp->next == NULL) { currentY = cw->multiText.cursorY; XBell(XtDisplay(cw), 0); } else currentY = lp->next->y + lp->next->bbox.ascent; PositionCursor(cw, currentX, currentY, &lp, &actualX); UpdateCursor(cw, lp, currentX, actualX, currentY);; } /*----------------------------------------------------------------------* | ClosestPosition | | The closest position to a given location in a line can either be the | | left side of a word closest to the given position, or the end of the | | line. This routine is used to find where the cursor whould be placed | | given the reference position. | | ClosestWord is returned as the word containing the reference posi- | | tion. | *----------------------------------------------------------------------*/ int ClosestPosition (XmMultiTextWidget cw, LineList lp, WordList *closestWord, int refPosn) { int dx, x; WordList wp; /* find the current word in this line. It's the word closest to the actual position. */ dx = lp->bbox.width; *closestWord = lp->firstWord; for (wp = lp->firstWord; wp != NULL; wp = wp->next) if (dx > ABS((cw->multiText.actualX - lp->x) - wp->x)) { dx = ABS((cw->multiText.actualX - lp->x) - wp->x); *closestWord = wp; x = wp->x + lp->x; } /* now check if the end of the line is closer than the closest word. */ if (dx > ABS(lp->x + lp->bbox.width - cw->multiText.actualX)) { *closestWord = NULL; x = lp->x + lp->bbox.width; } return(x); } /*----------------------------------------------------------------------* | MoveLeft | | Note, cursor is always at the baseline of the line. | *----------------------------------------------------------------------*/ static void MoveLeft (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int actualX; int dx; int currentX; int currentY = cw->multiText.cursorY; LineList lp; WordList wp, closestWord; if (cw->multiText.textIsSelected) DeselectAll(cw); lp = cw->multiText.currentLine; /* find the current word in this line. It's the word closest to the actual position. */ ClosestPosition(cw, lp, &closestWord, cw->multiText.actualX); /* if closestWord is null, then the cursor is off the right side of the line. */ /* unless there are no words in this line. */ if ((closestWord == NULL) && (lp->wordCount > 0)) /* move to the left side of the right-most word. */ { if (lp->lastWord != NULL) /* go to the beginning of the last word. */ currentX = actualX = lp->x + lp->lastWord->x; else currentX = actualX = lp->x; } else { if ((closestWord != NULL) && (closestWord->prev != NULL)) currentX = actualX = lp->x + closestWord->prev->x; /* should be here if we're in a line that has no words or this is the first line. */ else { if (lp->prev != NULL) { lp = lp->prev; /* go to prev line. */ currentX = actualX = lp->x + lp->bbox.width; currentY = lp->y + lp->bbox.ascent; } else { /* already at first line. */ currentX = actualX = lp->x; XBell(XtDisplay(cw), 0); } } } UpdateCursor(cw, lp, currentX, actualX, currentY); } /*----------------------------------------------------------------------* | MoveRight | | Note, cursor is always at the baseline of the line. | *----------------------------------------------------------------------*/ static void MoveRight (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; int actualX; int dx; int currentX; int currentY = cw->multiText.cursorY; LineList lp; WordList wp, closestWord; if (cw->multiText.textIsSelected) DeselectAll(cw); lp = cw->multiText.currentLine; ClosestPosition(cw, lp, &closestWord, cw->multiText.actualX); if (closestWord != NULL) { if (closestWord->next != NULL) currentX = actualX = closestWord->next->x + lp->x; else currentX = actualX = lp->x + lp->bbox.width; } else { /* scrolled past end of line - move to beginning of next line if there is one. */ if (lp->next == NULL) { currentX = actualX = lp->x + lp->bbox.width; XBell(XtDisplay(w), 0); } else { lp = lp->next; /* go to next line. */ currentX = actualX = lp->x; currentY = lp->y + lp->bbox.ascent; } } UpdateCursor(cw, lp, currentX, actualX, currentY); } #define cursor_width 7 #define cursor_height 5 /*----------------------------------------------------------------------* | MakeCursorPixmaps | | this routine creates (or updates) the cursor's fg and bg pixmaps. | *----------------------------------------------------------------------*/ static void MakeCursorPixmaps (XmMultiTextWidget cw) { if (cw->multiText.cursorFg == NULL) { static char cursor_bits[] = {0x08, 0x1c, 0x3e, 0x7f, 0x77}; cw->multiText.cursorFg = XCreatePixmapFromBitmapData(XtDisplay(cw), XtWindow(cw), cursor_bits, cursor_width, cursor_height, cw->multiText.cursorColor, None, DefaultDepthOfScreen(XtScreen(cw))); cw->multiText.cursorMask = XCreatePixmapFromBitmapData(XtDisplay(cw), XtWindow(cw), cursor_bits, cursor_width, cursor_height, WhitePixelOfScreen(XtScreen(cw)), None, 1); } if (cw->multiText.cursorBg == NULL) cw->multiText.cursorBg = XCreatePixmap(XtDisplay(cw), XtWindow(cw), cursor_width, cursor_height, DefaultDepthOfScreen(XtScreen(cw))); /* grab the region under the cursor and store it into the cursorBg field. */ XCopyArea(XtDisplay(cw), XtWindow(cw), cw->multiText.cursorBg, cw->multiText.cursorGC, cw->multiText.actualX, cw->multiText.cursorY, cursor_width, cursor_height, 0, 0); } /*----------------------------------------------------------------------* | BlinkCursor | *----------------------------------------------------------------------*/ static void BlinkCursor (XmMultiTextWidget cw) { cw->multiText.blinkTimeOutID = 0; if ((cw->multiText.showCursor) && (XtIsRealized(cw) && !cw->multiText.textIsSelected && !cw->multiText.selecting)) { /* this only gets called once. */ if (cw->multiText.cursorFg == NULL) { MakeCursorPixmaps(cw); cw->multiText.oldX = cw->multiText.actualX; cw->multiText.oldY = cw->multiText.cursorY; } XCopyArea(XtDisplay(cw), cw->multiText.cursorBg, XtWindow(cw), cw->multiText.cursorGC, 0, 0, cursor_width, cursor_height, cw->multiText.oldX, cw->multiText.oldY); if ((cw->multiText.oldX != cw->multiText.actualX) || (cw->multiText.oldY != cw->multiText.cursorY)) { cw->multiText.blinkState = FALSE; MakeCursorPixmaps(cw); } if (!cw->multiText.blinkState) { XSetClipMask (XtDisplay(cw), cw->multiText.cursorGC, cw->multiText.cursorMask); XSetClipOrigin(XtDisplay(cw), cw->multiText.cursorGC, cw->multiText.actualX, cw->multiText.cursorY); XCopyArea(XtDisplay(cw), cw->multiText.cursorFg, XtWindow(cw), cw->multiText.cursorGC, 0, 0, cursor_width, cursor_height, cw->multiText.actualX, cw->multiText.cursorY); XSetClipMask (XtDisplay(cw), cw->multiText.cursorGC, None); cw->multiText.oldX = cw->multiText.actualX; cw->multiText.oldY = cw->multiText.cursorY; } cw->multiText.blinkState = !cw->multiText.blinkState; } cw->multiText.blinkTimeOutID = XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)cw), cw->multiText.blinkRate, (XtTimerCallbackProc)BlinkCursor, cw); } /*----------------------------------------------------------------------* | TurnOffCursor | *----------------------------------------------------------------------*/ static void TurnOffCursor (XmMultiTextWidget cw) { cw->multiText.cursorAvailable = FALSE; if (cw->multiText.blinkTimeOutID != 0) { XtRemoveTimeOut(cw->multiText.blinkTimeOutID); cw->multiText.blinkTimeOutID = 0; } if (cw->multiText.blinkState == ON) { BlinkCursor(cw); XtRemoveTimeOut(cw->multiText.blinkTimeOutID); cw->multiText.blinkTimeOutID = 0; } /* clearout the pixmaps. */ if (cw->multiText.cursorFg != NULL) { XFreePixmap(XtDisplay(cw), cw->multiText.cursorFg); XFreePixmap(XtDisplay(cw), cw->multiText.cursorBg); XFreePixmap(XtDisplay(cw), cw->multiText.cursorMask); } cw->multiText.cursorFg = NULL; cw->multiText.cursorBg = NULL; cw->multiText.cursorMask = NULL; } /*----------------------------------------------------------------------* | TurnOnCursor | *----------------------------------------------------------------------*/ static void TurnOnCursor (XmMultiTextWidget cw) { cw->multiText.cursorAvailable = TRUE; if (cw->multiText.blinkTimeOutID != 0) { XtRemoveTimeOut(cw->multiText.blinkTimeOutID); cw->multiText.blinkTimeOutID = 0; } BlinkCursor(cw); } /*##########################################################################################* # O U T P U T R O U T I N E S F O R U S E R I N P U T # # ... future work... # *##########################################################################################*/ /*-----------------------========================-----------------------* | KeyPush action procedure | | This routine is active if the MultiText widget is editable. There | | is currently no checking for this case. | | | *-----------------------========================-----------------------*/ static void KeyPush (Widget w, XEvent* event, String* params, Cardinal* numParams) { XmMultiTextWidget cw = (XmMultiTextWidget) w; KeySym ks; char *str = " "; XLookupString(&event->xkey, str, 1, &ks, NULL); switch (*str) { case CR: printf("\n"); break; case TAB: printf(""); break; case CTRL_C: printf("\n"); break; case SPACE: printf(""); break; case BACKSPACE: printf(""); break; default: if (isgraph(*str)) printf("%c", *str); } /* XmMultiTextAppendChar(w, str[0]); */ } /*-----------------------------=========--------------------------------* | NewLineCR | | | *-----------------------------=========--------------------------------*/ static void NewLineCR (Widget w, XEvent* event, String* params, Cardinal* numParams) { } /*----------------------------===========-------------------------------* | InsertSpace | | | *----------------------------===========-------------------------------*/ static void InsertSpace (Widget w, XEvent* event, String* params, Cardinal* numParams) { } /*------------------------------=====-----------------------------------* | Dummy | | | *------------------------------=====-----------------------------------*/ static void Dummy (Widget w, XEvent* event, String* params, Cardinal* numParams) { }