static const char CVSID[] = "$Id: help.c,v 1.102 2004/08/01 10:06:10 yooden Exp $"; /******************************************************************************* * * * help.c -- Nirvana Editor help display * * * * Copyright (C) 1999 Mark Edel * * * * This is free software; you can redistribute it and/or modify it under the * * terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. In addition, you may distribute version of this program linked to * * Motif or Open Motif. See README for details. * * * * This software is distributed in the hope that it will be useful, but WITHOUT * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along with * * software; if not, write to the Free Software Foundation, Inc., 59 Temple * * Place, Suite 330, Boston, MA 02111-1307 USA * * * * Nirvana Text Editor * * September 10, 1991 * * * * Written by Mark Edel, mostly rewritten by Steve Haehn for new help system, * * December, 2001 * * * *******************************************************************************/ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include "help.h" #include "textBuf.h" #include "text.h" #include "textP.h" #include "textDisp.h" #include "textSel.h" #include "nedit.h" #include "search.h" #include "window.h" #include "preferences.h" #include "help_data.h" #include "file.h" #include "highlight.h" #include "../util/misc.h" #include "../util/DialogF.h" #include "../util/system.h" #include #include #include #include #ifdef VMS #include "../util/VMSparam.h" #else #ifndef __MVS__ #include #endif #endif /*VMS*/ #include #include /* These are for applying style info to help text */ #include #include #include #include #include #ifdef EDITRES #include /* extern void _XEditResCheckMessages(); */ #endif /* EDITRES */ #ifdef HAVE_DEBUG_H #include "../debug.h" #endif /*============================================================================*/ /* SYMBOL DEFINITIONS */ /*============================================================================*/ #define EOS '\0' /* end-of-string character */ #define CLICK_THRESHOLD 5 /* number of pixels mouse may move from its */ /* pressed location for mouse-up to be */ /* considered a valid click (as opposed to */ /* a drag or mouse-pick error) */ /*============================================================================*/ /* VARIABLE DECLARATIONS */ /*============================================================================*/ static Widget HelpWindows[NUM_TOPICS] = {NULL}; static Widget HelpTextPanes[NUM_TOPICS] = {NULL}; static textBuffer *HelpStyleBuffers[NUM_TOPICS] = {NULL}; static int navHistForw[NUM_TOPICS]; static int navHistBack[NUM_TOPICS]; /* Information on the last search for search-again */ static char LastSearchString[DF_MAX_PROMPT_LENGTH] = ""; static int LastSearchTopic = -1; static int LastSearchPos = 0; static int LastSearchWasAllTopics = False; /* Fonts for each help text style generated by the help generator (setext). The NEdit text widget uses the first help style, 'A', to calculate window width, so making 'A' a fixed font will yield window widths calibrated to match width-dependent fixed font layouts in the help text. */ static enum helpFonts StyleFonts[] = { /* Fixed fonts, styles: 'A', 'B', 'C', 'D' */ FIXED_HELP_FONT, BOLD_FIXED_HELP_FONT, BOLD_FIXED_HELP_FONT, BOLD_ITALIC_FIXED_HELP_FONT, /* Underlined fixed fonts, styles: 'E', 'F', 'G', 'H' */ FIXED_HELP_FONT, BOLD_FIXED_HELP_FONT, BOLD_FIXED_HELP_FONT, BOLD_ITALIC_FIXED_HELP_FONT, /* Normal (proportional) fonts, styles: 'I', 'J', 'K', 'L' */ HELP_FONT, BOLD_HELP_FONT, ITALIC_HELP_FONT, BOLD_ITALIC_HELP_FONT, /* Underlined fonts, styles: 'M', 'N', 'O', 'P' */ HELP_FONT, BOLD_HELP_FONT, ITALIC_HELP_FONT, BOLD_ITALIC_HELP_FONT, /* Link font, style: 'Q' */ HELP_FONT, /* Heading fonts, styles: 'R', 'S', 'T' */ H1_HELP_FONT, H2_HELP_FONT, H3_HELP_FONT }; static int StyleUnderlines[] = { /* Fixed fonts, styles: 'A', 'B', 'C', 'D' */ False, False, False, False, /* Underlined fixed fonts, styles: 'E', 'F', 'G', 'H' */ True, True, True, True, /* Normal (proportional) fonts, styles: 'I', 'J', 'K', 'L' */ False, False, False, False, /* Underlined fonts, styles: 'M', 'N', 'O', 'P' */ True, True, True, True, /* Link font, style: 'Q' */ True, /* Heading fonts, styles: 'R', 'S', 'T' */ False, False, False }; #define N_STYLES (XtNumber(StyleFonts)) static styleTableEntry HelpStyleInfo[ N_STYLES ]; /* Translation table for style codes (A, B, C, ...) to their ASCII codes. For systems using ASCII, this is just a one-to-one mapping, but the table makes it possible to use the style codes also on an EBCDIC system. */ static unsigned char AlphabetToAsciiTable[256]; /* Macro that calculates the zero-based index for a given style, taking into account that the character set may not use ASCII coding, but EBCDIC. The "style" argument must be one of the characters A - Z. In ASCII, this comes down to "style - STYLE_PLAIN". */ #define STYLE_INDEX(style) \ (AlphabetToAsciiTable[(unsigned char)style] - \ AlphabetToAsciiTable[(unsigned char)STYLE_PLAIN]) /*============================================================================*/ /* PROGRAM PROTOTYPES */ /*============================================================================*/ static Widget createHelpPanel(enum HelpTopic topic); static void closeCB(Widget w, XtPointer clientData, XtPointer callData); static void prevTopicCB(Widget w, XtPointer clientData, XtPointer callData); static void nextTopicCB(Widget w, XtPointer clientData, XtPointer callData); static void bwHistoryCB(Widget w, XtPointer clientData, XtPointer callData); static void fwHistoryCB(Widget w, XtPointer clientData, XtPointer callData); static void searchHelpCB(Widget w, XtPointer clientData, XtPointer callData); static void searchHelpAgainCB(Widget w, XtPointer clientData, XtPointer callData); static void printCB(Widget w, XtPointer clientData, XtPointer callData); static char *stitch(Widget parent, char **string_list,char **styleMap); static void searchHelpText(Widget parent, int parentTopic, const char *searchFor, int allSections, int startPos, int startTopic); static void changeWindowTopic(int existingTopic, enum HelpTopic newTopic); static int findTopicFromShellWidget(Widget shellWidget); static void loadFontsAndColors(Widget parent, int style); static void initNavigationHistory(void); #ifdef HAVE__XMVERSIONSTRING extern char _XmVersionString[]; #else static char _XmVersionString[] = "unknown"; #endif /*============================================================================*/ /*================================= PROGRAMS =================================*/ /*============================================================================*/ /* ** Create a string containing information on the build environment. Returned ** string must NOT be freed by caller. */ static char *bldInfoString = NULL; static void freeBuildInfo(void) { /* This keeps memory leak detectors happy */ XtFree(bldInfoString); } static const char *getBuildInfo(void) { const char * bldFormat = "%s\n" " Built on: %s, %s, %s\n" " Built at: %s, %s\n" " With Motif: %d.%d.%d [%s]" #ifdef BUILD_BROKEN_NEDIT " (KNOWN-BAD)\n" #elif defined BUILD_UNTESTED_NEDIT " (UNTESTED)\n" #else "\n" #endif "Running Motif: %d.%d [%s]\n" " Server: %s %d\n" " Visual: %s\n" " Locale: %s\n" #ifdef BUILD_BROKEN_NEDIT "\nThis NEdit was built with a known-bad version of Motif. Please\n" "do not report any bugs you encounter unless you can reproduce\n" "them with a known-good binary from the www.nedit.org website.\n" "If this binary was supplied with your Linux distribution please\n" "file a bug report with them asking them to build NEdit with a\n" "known-good version of Motif.\n" #endif ; const char * visualClass[] = {"StaticGray", "GrayScale", "StaticColor", "PseudoColor", "TrueColor", "DirectColor"}; const char *locale; if (bldInfoString == NULL) { char visualStr[500] = ""; if (TheDisplay) { Visual *visual; int depth; Colormap map; Boolean usingDefaultVisual = FindBestVisual(TheDisplay, APP_NAME, APP_CLASS, &visual, &depth, &map); sprintf(visualStr,"%d-bit %s (ID %#lx%s)", depth, visualClass[visual->class], visual->visualid, usingDefaultVisual ? ", Default" : ""); } bldInfoString = XtMalloc (strlen (bldFormat) + 1024); locale = setlocale(LC_MESSAGES, ""); sprintf(bldInfoString, bldFormat, NEditVersion, COMPILE_OS, COMPILE_MACHINE, COMPILE_COMPILER, linkdate, linktime, XmVERSION, XmREVISION, XmUPDATE_LEVEL, XmVERSION_STRING, xmUseVersion/1000, xmUseVersion%1000, _XmVersionString, (NULL == TheDisplay ? "" : ServerVendor(TheDisplay)), (NULL == TheDisplay ? 0 : VendorRelease(TheDisplay)), visualStr, locale ? locale : "None"); atexit(freeBuildInfo); } return bldInfoString; } /* ** Initialization for help system data, needs to be done only once. */ static void initHelpStyles (Widget parent) { static int styleTableInitialized = False; if (! styleTableInitialized) { Pixel fg; int styleIndex; char ** line; XtVaGetValues(parent, XtNforeground, &fg, NULL); for (styleIndex = 0; styleIndex < STL_HD + MAX_HEADING; styleIndex++) { HelpStyleInfo[ styleIndex ].color = fg; HelpStyleInfo[ styleIndex ].underline = StyleUnderlines[styleIndex]; HelpStyleInfo[ styleIndex ].font = NULL; } styleTableInitialized = True; /*------------------------------------------------------- * Only attempt to add build information to version text * when string formatting symbols are present in the text. * This special case is needed to incorporate this * dynamically created information into the static help. *-------------------------------------------------------*/ for (line = HelpText[ HELP_VERSION ]; *line != NULL; line++) { /*-------------------------------------------------- * If and when this printf format is found in the * version help text, replace that line with the * build information. Then stitching the help text * will have the final count of characters to use. *--------------------------------------------------*/ if (strstr (*line, "%s") != NULL) { const char * bldInfo = getBuildInfo(); char * text = XtMalloc (strlen (*line) + strlen (bldInfo)); sprintf (text, *line, bldInfo); *line = text; break; } } /*--------------------------------------------------------- * Also initialize the alphabet-to-ASCII-code table (to * make the style mapping also work on EBCDIC). * DON'T use 'A' to initialize the table! 'A' != 65 in EBCDIC. *--------------------------------------------------------*/ AlphabetToAsciiTable[(unsigned char)'A'] = ASCII_A + 0; AlphabetToAsciiTable[(unsigned char)'B'] = ASCII_A + 1; AlphabetToAsciiTable[(unsigned char)'C'] = ASCII_A + 2; AlphabetToAsciiTable[(unsigned char)'D'] = ASCII_A + 3; AlphabetToAsciiTable[(unsigned char)'E'] = ASCII_A + 4; AlphabetToAsciiTable[(unsigned char)'F'] = ASCII_A + 5; AlphabetToAsciiTable[(unsigned char)'G'] = ASCII_A + 6; AlphabetToAsciiTable[(unsigned char)'H'] = ASCII_A + 7; AlphabetToAsciiTable[(unsigned char)'I'] = ASCII_A + 8; AlphabetToAsciiTable[(unsigned char)'J'] = ASCII_A + 9; AlphabetToAsciiTable[(unsigned char)'K'] = ASCII_A + 10; AlphabetToAsciiTable[(unsigned char)'L'] = ASCII_A + 11; AlphabetToAsciiTable[(unsigned char)'M'] = ASCII_A + 12; AlphabetToAsciiTable[(unsigned char)'N'] = ASCII_A + 13; AlphabetToAsciiTable[(unsigned char)'O'] = ASCII_A + 14; AlphabetToAsciiTable[(unsigned char)'P'] = ASCII_A + 15; AlphabetToAsciiTable[(unsigned char)'Q'] = ASCII_A + 16; AlphabetToAsciiTable[(unsigned char)'R'] = ASCII_A + 17; AlphabetToAsciiTable[(unsigned char)'S'] = ASCII_A + 18; AlphabetToAsciiTable[(unsigned char)'T'] = ASCII_A + 19; AlphabetToAsciiTable[(unsigned char)'U'] = ASCII_A + 20; AlphabetToAsciiTable[(unsigned char)'V'] = ASCII_A + 21; AlphabetToAsciiTable[(unsigned char)'W'] = ASCII_A + 22; AlphabetToAsciiTable[(unsigned char)'X'] = ASCII_A + 23; AlphabetToAsciiTable[(unsigned char)'Y'] = ASCII_A + 24; AlphabetToAsciiTable[(unsigned char)'Z'] = ASCII_A + 25; } } /* ** Help fonts are not loaded until they're actually needed. This function ** checks if the style's font is loaded, and loads it if it's not. */ static void loadFontsAndColors(Widget parent, int style) { XFontStruct *font; int r,g,b; if (HelpStyleInfo[STYLE_INDEX(style)].font == NULL) { font = XLoadQueryFont(XtDisplay(parent), GetPrefHelpFontName(StyleFonts[STYLE_INDEX(style)])); if (font == NULL) { fprintf(stderr, "NEdit: help font, %s, not available\n", GetPrefHelpFontName(StyleFonts[STYLE_INDEX(style)])); font = XLoadQueryFont(XtDisplay(parent), "fixed"); if (font == NULL) { fprintf(stderr, "NEdit: fallback help font, \"fixed\", not " "available, cannot continue\n"); exit(EXIT_FAILURE); } } HelpStyleInfo[STYLE_INDEX(style)].font = font; if (style == STL_NM_LINK) HelpStyleInfo[STYLE_INDEX(style)].color = AllocColor(parent, GetPrefHelpLinkColor(), &r, &g, &b); } } static void adaptNavigationButtons(int topic) { Widget btn; if(HelpWindows[topic] == NULL) return; /* Shouldn't happen */ btn=XtNameToWidget(HelpWindows[topic], "helpForm.prevTopic"); if(btn) { if(topic > 0) XtSetSensitive(btn, True); else XtSetSensitive(btn, False); } btn=XtNameToWidget(HelpWindows[topic], "helpForm.nextTopic"); if(btn) { if(topic < (NUM_TOPICS - 1)) XtSetSensitive(btn, True); else XtSetSensitive(btn, False); } btn=XtNameToWidget(HelpWindows[topic], "helpForm.histBack"); if(btn) { if(navHistBack[topic] != -1) XtSetSensitive(btn, True); else XtSetSensitive(btn, False); } btn=XtNameToWidget(HelpWindows[topic], "helpForm.histForw"); if(btn) { if(navHistForw[topic] != -1) XtSetSensitive(btn, True); else XtSetSensitive(btn, False); } } /* ** Put together stored help strings to create the text and optionally the style ** information for a given help topic. */ static char * stitch ( Widget parent, /* used for dynamic font/color allocation */ char ** string_list, /* given help strings to stitch together */ char ** styleMap /* NULL, or a place to store help styles */ ) { char * cp; char * section, * sp; /* resulting help text section */ char * styleData, * sdp; /* resulting style data for text */ char style = STYLE_PLAIN; /* start off each section with this style */ int total_size = 0; /* help text section size */ char ** crnt_line; /*---------------------------------------------------- * How many characters are there going to be displayed? *----------------------------------------------------*/ for (crnt_line = string_list; *crnt_line != NULL; crnt_line++) { for (cp = *crnt_line; *cp != EOS; cp++) { /*--------------------------------------------- * The help text has embedded style information * consisting of the style marker and a single * character style, for a total of 2 characters. * This style information is not to be included * in the character counting below. *---------------------------------------------*/ if (*cp != STYLE_MARKER) { total_size++; } else { cp++; /* skipping style marker, loop will handle style */ } } } /*-------------------------------------------------------- * Get the needed space, one area for the help text being * stitched together, another for the styles to be applied. *--------------------------------------------------------*/ sp = section = XtMalloc (total_size +1); sdp = styleData = (styleMap) ? XtMalloc (total_size +1) : NULL; *sp = EOS; /*-------------------------------------------- * Fill in the newly acquired contiguous space * with help text and style information. *--------------------------------------------*/ for (crnt_line = string_list; *crnt_line != NULL; crnt_line++) { for (cp = *crnt_line; *cp != EOS; cp++) { if (*cp == STYLE_MARKER) { style = *(++cp); loadFontsAndColors(parent, style); } else { *(sp++) = *cp; /* Beware of possible EBCDIC coding! Use the mapping table. */ if (styleMap) *(sdp++) = AlphabetToAsciiTable[(unsigned char)style]; } } } *sp = EOS; /*----------------------------------------- * Only deal with style map, when available. *-----------------------------------------*/ if (styleMap) { *styleMap = styleData; *sdp = EOS; } return section; } /* ** Display help for subject "topic". "parent" is a widget over which the help ** dialog may be posted. Help dialogs are preserved when popped down by the ** user, and may appear posted over a previous parent, regardless of the parent ** argument. */ void Help(enum HelpTopic topic) { if (HelpWindows[topic] != NULL) RaiseShellWindow(HelpWindows[topic]); else HelpWindows[topic] = createHelpPanel(topic); adaptNavigationButtons(topic); } /* Setup Window/Icon title for the help window. */ static void setHelpWinTitle(Widget win, enum HelpTopic topic) { char * buf, *topStr=HelpTitles[topic]; buf=malloc(strlen(topStr) + 24); topic++; sprintf(buf, "NEdit Help (%d)", (int)topic); XtVaSetValues(win, XmNiconName, buf, NULL); sprintf(buf, "NEdit Help: %s (%d)", topStr, (int)topic); XtVaSetValues(win, XmNtitle, buf, NULL); free(buf); } /* ** Create a new help window displaying a given subject, "topic" ** ** Important hint: If this widget is restructured or the name of the text ** subwidget is changed don't forget to adapt the default translations of the ** help text. They are located in nedit.c, look for ** static char *fallbackResources ** (currently: nedit.helpForm.sw.helpText*translations...) */ static Widget createHelpPanel(enum HelpTopic topic) { Arg al[50]; int ac; Widget appShell, btn, closeBtn, form, btnFW; Widget sw, hScrollBar, vScrollBar; XmString st1; char * helpText = NULL; char * styleData = NULL; ac = 0; XtSetArg(al[ac], XmNdeleteResponse, XmDO_NOTHING); ac++; appShell = CreateWidget(TheAppShell, "help", topLevelShellWidgetClass, al, ac); AddSmallIcon(appShell); /* With openmotif 2.1.30, a crash may occur when the text widget of the help window is (slowly) resized to a zero width. By imposing a minimum _window_ width, we can work around this problem. The minimum width should be larger than the width of the scrollbar. 50 is probably a safe value; this leaves room for a few characters */ XtVaSetValues(appShell, XtNminWidth, 50, NULL); form = XtVaCreateManagedWidget("helpForm", xmFormWidgetClass, appShell, NULL); XtVaSetValues(form, XmNshadowThickness, 0, NULL); /* Create the bottom row of buttons */ btn = XtVaCreateManagedWidget("find", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Find..."), XmNmnemonic, 'F', XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 3, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 25, NULL); XtAddCallback(btn, XmNactivateCallback, searchHelpCB, appShell); XmStringFree(st1); btn = XtVaCreateManagedWidget("findAgain", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Find Again"), XmNmnemonic, 'A', XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 27, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 49, NULL); XtAddCallback(btn, XmNactivateCallback, searchHelpAgainCB, appShell); XmStringFree(st1); btn = XtVaCreateManagedWidget("print", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Print..."), XmNmnemonic, 'P', XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 51, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 73, NULL); XtAddCallback(btn, XmNactivateCallback, printCB, appShell); XmStringFree(st1); closeBtn = XtVaCreateManagedWidget("close", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Close"), XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 75, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 97, NULL); XtAddCallback(closeBtn, XmNactivateCallback, closeCB, appShell); XmStringFree(st1); /* Create the next row of buttons (for navigation) */ btn = XtVaCreateManagedWidget("prevTopic", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("<< Browse"), XmNmnemonic, 'o', XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, closeBtn, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 51, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 73, NULL); XtAddCallback(btn, XmNactivateCallback, prevTopicCB, appShell); XmStringFree(st1); btn = XtVaCreateManagedWidget("nextTopic", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Browse >>"), XmNmnemonic, 'e', XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, closeBtn, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 75, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 97, NULL); XtAddCallback(btn, XmNactivateCallback, nextTopicCB, appShell); XmStringFree(st1); btn = XtVaCreateManagedWidget("histBack", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Back"), XmNmnemonic, 'B', XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, closeBtn, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 3, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 25, NULL); XtAddCallback(btn, XmNactivateCallback, bwHistoryCB, appShell); XmStringFree(st1); btnFW = XtVaCreateManagedWidget("histForw", xmPushButtonWidgetClass, form, XmNlabelString, st1=XmStringCreateSimple("Forward"), XmNmnemonic, 'w', XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, closeBtn, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 27, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 49, NULL); XtAddCallback(btnFW, XmNactivateCallback, fwHistoryCB, appShell); XmStringFree(st1); /* Create a text widget inside of a scrolled window widget */ sw = XtVaCreateManagedWidget("sw", xmScrolledWindowWidgetClass, form, XmNshadowThickness, 2, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, btnFW, NULL); hScrollBar = XtVaCreateManagedWidget("hScrollBar", xmScrollBarWidgetClass, sw, XmNorientation, XmHORIZONTAL, XmNrepeatDelay, 10, NULL); vScrollBar = XtVaCreateManagedWidget("vScrollBar", xmScrollBarWidgetClass, sw, XmNorientation, XmVERTICAL, XmNrepeatDelay, 10, NULL); /* Make sure the fixed size help font is loaded, such that we can base our text widget size calculation on it. */ loadFontsAndColors(sw, 'A'); HelpTextPanes[topic] = XtVaCreateManagedWidget("helpText", textWidgetClass, sw, textNfont, HelpStyleInfo[0].font, /* MUST correspond to 'A' above */ textNrows, 30, textNcolumns, 65, textNbacklightCharTypes, NULL, textNhScrollBar, hScrollBar, textNvScrollBar, vScrollBar, textNreadOnly, True, textNcontinuousWrap, True, textNautoShowInsertPos, True, NULL); XtVaSetValues(sw, XmNworkWindow, HelpTextPanes[topic], XmNhorizontalScrollBar, hScrollBar, XmNverticalScrollBar, vScrollBar, NULL); /* Initialize help style information, if it hasn't already been init'd */ initHelpStyles (HelpTextPanes[topic]); /* Put together the text to display and separate it into parallel text and style data for display by the widget */ helpText = stitch (HelpTextPanes[topic], HelpText[topic], &styleData); /* Stuff the text into the widget's text buffer */ BufSetAll (TextGetBuffer (HelpTextPanes[topic]) , helpText); XtFree (helpText); /* Create a style buffer for the text widget and fill it with the style data which was generated along with the text content */ HelpStyleBuffers[topic] = BufCreate(); BufSetAll(HelpStyleBuffers[topic], styleData); XtFree (styleData); TextDAttachHighlightData(((TextWidget)HelpTextPanes[topic])->text.textD, HelpStyleBuffers[topic], HelpStyleInfo, N_STYLES, '\0', NULL, NULL); /* This shouldn't be necessary (what's wrong in text.c?) */ HandleXSelections(HelpTextPanes[topic]); /* Process dialog mnemonic keys */ AddDialogMnemonicHandler(form, FALSE); /* Set the default button */ XtVaSetValues(form, XmNdefaultButton, closeBtn, NULL); XtVaSetValues(form, XmNcancelButton, closeBtn, NULL); /* realize all of the widgets in the new window */ RealizeWithoutForcingPosition(appShell); /* Give the text pane the initial focus */ XmProcessTraversal(HelpTextPanes[topic], XmTRAVERSE_CURRENT); /* Make close command in window menu gracefully prompt for close */ AddMotifCloseCallback(appShell, (XtCallbackProc)closeCB, appShell); /* Initialize navigation information, if it hasn't already been init'd */ initNavigationHistory(); /* Set the window title */ setHelpWinTitle(appShell, topic); #ifdef EDITRES XtAddEventHandler (appShell, (EventMask)0, True, (XtEventHandler)_XEditResCheckMessages, NULL); #endif /* EDITRES */ return appShell; } static void changeTopicOrRaise(int existingTopic, int newTopic) { if(HelpWindows[newTopic] == NULL) { changeWindowTopic(existingTopic, newTopic); adaptNavigationButtons(newTopic); } else { RaiseShellWindow(HelpWindows[newTopic]); adaptNavigationButtons(existingTopic); adaptNavigationButtons(newTopic); } } /* ** Callbacks for window controls */ static void closeCB(Widget w, XtPointer clientData, XtPointer callData) { int topic; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* I don't understand the mechanism by which this can be called with HelpWindows[topic] as NULL, but it has happened */ XtDestroyWidget(HelpWindows[topic]); HelpWindows[topic] = NULL; if (HelpStyleBuffers[topic] != NULL) { BufFree(HelpStyleBuffers[topic]); HelpStyleBuffers[topic] = NULL; } } static void prevTopicCB(Widget w, XtPointer clientData, XtPointer callData) { int topic; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ topic--; if(topic >= 0) changeTopicOrRaise(topic+1, topic); } static void nextTopicCB(Widget w, XtPointer clientData, XtPointer callData) { int topic; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ topic++; if(topic < NUM_TOPICS) changeTopicOrRaise(topic-1, topic); } static void bwHistoryCB(Widget w, XtPointer clientData, XtPointer callData) { int topic, goTo; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ goTo=navHistBack[topic]; if(goTo >= 0 && goTo < NUM_TOPICS) { navHistForw[goTo]=topic; changeTopicOrRaise(topic, goTo); } } static void fwHistoryCB(Widget w, XtPointer clientData, XtPointer callData) { int topic, goTo; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ goTo=navHistForw[topic]; if(goTo >= 0 && goTo < NUM_TOPICS) { navHistBack[goTo]=topic; changeTopicOrRaise(topic, goTo); } } static void searchHelpCB(Widget w, XtPointer clientData, XtPointer callData) { char promptText[DF_MAX_PROMPT_LENGTH]; int response, topic; static char **searchHistory = NULL; static int nHistoryStrings = 0; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ SetDialogFPromptHistory(searchHistory, nHistoryStrings); response = DialogF(DF_PROMPT, HelpWindows[topic], 3, "Find", "Search for: (use up arrow key to recall previous)", promptText, "This Section", "All Sections", "Cancel"); if (response == 3) return; AddToHistoryList(promptText, &searchHistory, &nHistoryStrings); searchHelpText(HelpWindows[topic], topic, promptText, response == 2, 0, 0); } static void searchHelpAgainCB(Widget w, XtPointer clientData, XtPointer callData) { int topic; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ searchHelpText(HelpWindows[topic], topic, LastSearchString, LastSearchWasAllTopics, LastSearchPos, LastSearchTopic); } static void printCB(Widget w, XtPointer clientData, XtPointer callData) { int topic, helpStringLen; char *helpString; if ((topic = findTopicFromShellWidget((Widget)clientData)) == -1) return; /* shouldn't happen */ helpString = TextGetWrapped(HelpTextPanes[topic], 0, TextGetBuffer(HelpTextPanes[topic])->length, &helpStringLen); PrintString(helpString, helpStringLen, HelpWindows[topic], HelpTitles[topic]); XtFree(helpString); } /* ** Find the topic and text position within that topic targeted by a hyperlink ** with name "link_name". Returns true if the link was successfully interpreted. */ static int is_known_link(char *link_name, int *topic, int *textPosition) { Href * hypertext; /*------------------------------ * Direct topic links found here. *------------------------------*/ for (*topic=0; HelpTitles[*topic] != NULL; (*topic)++) { if (strcmp (link_name, HelpTitles[*topic]) == 0) { *textPosition = 0; return 1; } } /*------------------------------------ * Links internal to topics found here. *------------------------------------*/ for (hypertext = &H_R[0]; hypertext != NULL; hypertext = hypertext->next) { if (strcmp (link_name, hypertext->source) == 0) { *topic = hypertext->topic; *textPosition = hypertext->location; return 1; } } return 0; } /* ** Find the text of a hyperlink from a clicked character position somewhere ** within the hyperlink text, and display the help that it links to. */ static void followHyperlink(int topic, int charPosition, int newWindow) { textDisp *textD = ((TextWidget)HelpTextPanes[topic])->text.textD; char * link_text; int link_topic; int link_pos; int end = charPosition; int begin = charPosition; char whatStyle = BufGetCharacter(textD->styleBuffer, end); /*-------------------------------------------------- * Locate beginning and ending of current text style. *--------------------------------------------------*/ while (whatStyle == BufGetCharacter(textD->styleBuffer, ++end)); while (whatStyle == BufGetCharacter(textD->styleBuffer, begin-1)) begin--; link_text = BufGetRange (textD->buffer, begin, end); if (is_known_link (link_text, &link_topic, &link_pos) ) { if (HelpWindows[link_topic] != NULL) { RaiseShellWindow(HelpWindows[link_topic]); } else { if (newWindow) { HelpWindows[link_topic] = createHelpPanel(link_topic); } else { changeWindowTopic(topic, link_topic); } } navHistBack[link_topic] = topic; navHistForw[topic] = link_topic; TextSetCursorPos(HelpTextPanes[link_topic], link_pos); adaptNavigationButtons(link_topic); adaptNavigationButtons(topic); } XtFree (link_text); } static void helpFocusButtonsAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { XmProcessTraversal(w, XmTRAVERSE_NEXT_TAB_GROUP); } /* * handler for help-button-action() * Calls the activate callback for the named button widget of the help text win. */ static void helpButtonActionAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { char buf[80]; int topic; Widget btn; if(*nArgs != 1) { fprintf(stderr, "help-button-action: requires exactly one argument.\n"); return; } /* Find the topic being displayed by this widget */ for (topic = 0; topic < NUM_TOPICS; topic++) if (HelpTextPanes[topic] == w) break; if(topic == NUM_TOPICS || HelpWindows[topic] == NULL) return; /* Shouldn't happen */ /* Compose the button widget name */ strcpy(&buf[0], "helpForm."); if (strlen(args[0]) <= 70) { strcat(&buf[0], args[0]); } else { fprintf(stderr, "help-button-action: argument too long"); return; } btn=XtNameToWidget(HelpWindows[topic], buf); if (btn) { XtCallCallbacks(btn, XmNactivateCallback, HelpWindows[topic]); } else { fprintf(stderr, "help-button-action: invalid argument: %s\n", args[0]); } } /* * Handler for action help-hyperlink() * Arguments: none - init: record event position * "current": if clicked on a link, follow link in same window * "new": if clicked on a link, follow link in new window * * With the 1st argument "current" or "new" this action can have two additional * arguments. These arguments must be valid names of XmText actions. * In this case, the action named in argument #2 is called if the action * help-hyperlink is about to follow the hyperlink. The Action in argument #3 * is called if no hyperlink has been recognized at the current event position. */ static void helpHyperlinkAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { XButtonEvent *e = (XButtonEvent *)event; int topic; textDisp *textD = ((TextWidget)w)->text.textD; int clickedPos, newWin; static int pressX=0, pressY=0; /* called without arguments we just record coordinates */ if (*nArgs == 0) { pressX = e->x; pressY = e->y; return; } newWin = !strcmp(args[0], "new"); if(!newWin && strcmp(args[0], "current")) { fprintf(stderr, "help-hyperlink: Unrecognized argument %s\n", args[0]); return; } /* * If for any reason (pointer moved - drag!, no hyperlink found) * this action can't follow a hyperlink then execute the the action * named in arg #3 (if provided) */ if (abs(pressX - e->x) > CLICK_THRESHOLD || abs(pressY - e->y) > CLICK_THRESHOLD) { if (*nArgs == 3) XtCallActionProc(w, args[2], event, NULL, 0); return; } clickedPos = TextDXYToCharPos(textD, e->x, e->y); /* Beware of possible EBCDIC coding! Use the mapping table. */ if (BufGetCharacter(textD->styleBuffer, clickedPos) != (char)AlphabetToAsciiTable[(unsigned char)STL_NM_LINK]) { if (*nArgs == 3) XtCallActionProc(w, args[2], event, NULL, 0); return; } /* Find the topic being displayed by this widget */ for (topic = 0; topic < NUM_TOPICS; topic++) if (HelpTextPanes[topic] == w) break; if (topic == NUM_TOPICS) { /* If we get here someone must have bound help-hyperlink to a non-help * text widget (!) Or some other really strange thing happened. */ if (*nArgs == 3) XtCallActionProc(w, args[2], event, NULL, 0); return; } /* If the action help-hyperlink had 3 arguments execute the action * named in arg #2 before really following the link. */ if (*nArgs == 3) XtCallActionProc(w, args[1], event, NULL, 0); followHyperlink(topic, clickedPos, newWin); } /* ** Install the action for following hyperlinks in the help window */ void InstallHelpLinkActions(XtAppContext context) { static XtActionsRec Actions[] = { {"help-hyperlink", helpHyperlinkAP}, {"help-focus-buttons", helpFocusButtonsAP}, {"help-button-action", helpButtonActionAP} }; XtAppAddActions(context, Actions, XtNumber(Actions)); } /* ** Search the help text. If allSections is true, searches all of the help ** text, otherwise searches only in parentTopic. */ static void searchHelpText(Widget parent, int parentTopic, const char *searchFor, int allSections, int startPos, int startTopic) { int topic, beginMatch, endMatch; int found = False; char * helpText = NULL; /* Search for the string */ for (topic=startTopic; topictext.textD, NULL, NULL, 0, '\0', NULL, NULL); BufSetAll(TextGetBuffer(HelpTextPanes[newTopic]), helpText); XtFree(helpText); BufSetAll(HelpStyleBuffers[newTopic], styleData); XtFree(styleData); TextDAttachHighlightData(((TextWidget)HelpTextPanes[newTopic])->text.textD, HelpStyleBuffers[newTopic], HelpStyleInfo, N_STYLES, '\0', NULL, NULL); } static int findTopicFromShellWidget(Widget shellWidget) { int i; for (i=0; i in Motif 1.2 only. As for Motif 2.1 we don't need this call anymore. This also holds for the Motif 2.1 version of LessTif releases > 0.93.0. */ extern void XmRegisterConverters(void); #endif /* Print version info to stdout */ void PrintVersion(void) { const char *text; #if XmVersion < 2001 XmRegisterConverters(); /* see comment above */ #endif text = getBuildInfo(); puts (text); }