#include "CCmdHandler.h"


/*
** Generated by X-Designer
*/

/*
** X-Designer-generated prelude.
** Do not edit lines before "End of X-Designer generated prelude"
** Lines beginning ** X-Designer Stub indicate a stub
** which will not be output on re-generation
*/

/*
**LIBS: -lXm -lXt -lX11
*/

#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>

#include <Xm/Xm.h>

#include "CGuru.h"



/* End of X-Designer generated prelude */

#include <Xm/List.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <errno.h>
#include "Commands.h"
#include "CWaitCursor.h"

#include "CText.h"
#include "CTopic.h"
#include "CHistList.h"
#include "help.h"

/*
** Enumeration of naviagtion commands 
*/
enum {
	cmdBack = cmdUser,
	cmdForward,
	cmdFirst
};

/*
** Some used for application-wide dialogs and suchlike
*/
CHistoryDialog *searchResults;
CTopic *currentTopic;
CHelpDb db;
CHistoryList historyList;//FIXME should b member

char *CGuru::helpDir = 0 ;

/*
** Convenience function; take a char * which might include environment
** variables and return a char * which has the variables expanded to
** their values in the current environment
*/
char *
ExpandString( char *string ) {
    char *str;

    /* the method is very simple - use the command-line echo command to 
       print out the string - this whould expand everything quite nicely */
    char *buf = XtMalloc ( strlen ( string ) + 6 ) ;
    FILE *f ;
 
    /* Create a command to print out the string */
    sprintf ( buf, "echo %s", string ) ;

    /* Open a pipe to the command */
    f = popen ( buf, "r" ) ;

    if ( f ) {
	int size = 5 ;
	int i = 0 ;
	char ch = fgetc ( f ) ;
 
	/* Allocate something with a sensible starting size */
	str = XtMalloc( size ) ;

	/* Read in the expanded string, increasing our memory buffer size 
	   when necessary */
	while ( ch != EOF && ch != '\n' ) {

	    str[i] = ch ;
	    i++ ;

	    if ( i >= size ) {
		size = size + 5 ;
		str = XtRealloc ( str, size ) ;
	    }

	    ch = fgetc ( f ) ;
	}

	str[i] = '\0' ;
	pclose ( f ) ;
    }
    else
	str = strdup( string ) ;

    XtFree ( buf ) ;
 
    return str ;
}

/* 
** Get a resource which represents a printf-style formatting string,
** insert /text/ where the %s is, and put it in the status bar
*/
void
CStatusBar::SetTextFromResource( const char *formatResource,
				 const char *text )
{
    char *formatString = (char *) 0 ;
    XrmValue v;

    /* These strings are all stored in the resource file in resources with
       a specific prefix */
    CJString name = "visu_help.statusFormat." ;
    char *s;

    /* ...create a full resource name */
    name += formatResource ;

    /* ...try to get the resource */
    if ( XrmGetResource( XrmGetDatabase( XtDisplay( statusLabel ) ), name, 
			 "visu_help.StatusFormat", &s, &v ) )
	formatString = (char *) v.addr ;

    /* ...if we found the format string, we use it - if not, we
       just do nothing as this may be because the format string
       was removed for some good reason. */
    if ( formatString != (char *) 0 ) {
	char *statusText = new char[ strlen( formatString ) 
				    + strlen( text ) + 1 ] ;
	sprintf( statusText, formatString, text ) ;
	SetText( statusText ) ;
    }
	
}

/*
** Method to set the text in the status bar to a specific char *
*/
void
CStatusBar::SetText( const char *string ) 
{
    XmString labelString = XmStringCreateLocalized( (char *) string ) ;
    SetText( labelString ) ;
    XmStringFree( labelString ) ;
}

/*
** Method to set the text in the status bar to a specific XmString
*/
void
CStatusBar::SetText( XmString text )
{
    Arg al ;
    XtSetArg( al, XmNlabelString, text ) ;
    XtSetValues( statusLabel, &al, 1 ) ;
}

/*
** Method to clear the status bar 
*/
void
CStatusBar::Clear()
{
    SetText( " " ) ;
}


/*
** Method to quit the application
*/
void
CGuru::Exit() 
{
    exit( 0 ) ;
}

/*
** Method to ensure that all of the user interface components in the main
** window of the help system are sensitised of desensitised as appropriate 
*/
void
CGuru::UpdateUI()
{
	Boolean b;

	/* These occur in pairs; the first one sorts out the toolbar button,
	   the second the menu button which does the same thing. */

	XtSetSensitive(backButton, b = DispatchCommand(cmdBack, TRUE));
	XtSetSensitive(navBackItem, b);

	XtSetSensitive(forwardButton, b = DispatchCommand(cmdForward, TRUE));
	XtSetSensitive(navForwardItem, b);

	XtSetSensitive(firstButton, b = DispatchCommand(cmdFirst, TRUE));
	XtSetSensitive(navIndexItem, b);

	/* No toolbar undo button */
	XtSetSensitive(editUndoItem, DispatchCommand(cmdUndo, TRUE));

	XtSetSensitive(cutButton, b = DispatchCommand(cmdCut, TRUE));
	XtSetSensitive(editCutItem, b);

	XtSetSensitive(copyButton, b = DispatchCommand(cmdCopy, TRUE));
	XtSetSensitive(editCopyItem, b);

	XtSetSensitive(pasteButton, b = DispatchCommand(cmdPaste, TRUE));
	XtSetSensitive(editPasteItem, b);

	/* The most of the buttons in the file menu (and their associated
	   toolbar buttons) rely simply on having a topic loaded. */
	XtSetSensitive(fileSaveItem, currentTopic != 0);
	XtSetSensitive(saveButton, currentTopic != 0);
	XtSetSensitive(fileSaveAsItem, currentTopic != 0);
	XtSetSensitive(filePrintItem, currentTopic != 0);
	XtSetSensitive(printButton, currentTopic != 0);
}

/*
** Accessor for the directory in which the help files for the help system
** itself are kept (this information is in the system's resource file).  
** Should only be called after the create() method has been called, as
** it relies on some initialisation being done there.
*/
char *
CGuru::GetHelpDir() {

    /* Check if we've already got this information */
    if ( helpDir == 0 ) {
	XrmValue v;
	char *s;

	/* If not, get it from the resource file */
	if ( XrmGetResource( XrmGetDatabase( XtDisplay(xd_rootwidget())), 
			     "visu_help.helpDir", "visu_help.HelpDir",
			     &s, &v)) {

	    /* If we got the information successfully, expand any environment
	       variables in it */
	    helpDir = ExpandString( v.addr ) ;
	}
    }

    /* Now return whatever we have; 0 if the directory is unknown, a char *
       pointing to it otherwise. */
    return helpDir ;
}

/*
** Method to got to a help page describing the help system itself
*/
void
CGuru::SetHelpTopic(const char *key, Boolean bAddToHistory)
{
    char *dir = GetHelpDir() ;
    
    if ( dir != 0 ) {
	CJString topic( dir ) ;
	topic += '/' ;
	topic += key ;
	topic += ".html" ;
	SetTopic(topic, True) ;
    }
}

/*
** Method to move to a topic whose filename is specified by /key/.  If
** /bAddToHistory/ then we add the title of this topic to the history
** list.
*/
void
CGuru::SetTopic(const char *keyin, Boolean bAddToHistory)
{
    /* If the key is /QUIT_HELP_SYSTEM_MSG/ then we've actually received a 
       message telling us to exit. */
    if ( !strcmp( keyin, QUIT_HELP_SYSTEM_MSG ) )
	Exit() ;

    /* Find any '#' signs in the key */
    int i ;
    for ( i=0 ; keyin[i] && keyin[i] != '#'; i++ )
	;

    /* Copy the bit before the '#' or all of it depending on whether 
       we found a '#'; this is the filename part of the tag */
    char *key = 0 ;
    char *namedA = 0 ;

    /* If there was anything before the '#' */
    if ( i != 0 ) {
	key = new char[i + 1] ;
	strncpy( key, keyin, i ) ;
	key[i] = '\0' ;
    }
    
    /* If there *was* a '#' sign */
    if ( keyin[i] ) {
	/* Copy across the bit after the '#', the 'tag' or named anchor */
	int namedAlen = strlen( keyin ) - i - 1 ;
	namedA = new char [ namedAlen + 1 ] ;
	strncpy( namedA, keyin + i + 1, namedAlen ) ;
	namedA[namedAlen] = '\0' ;
    }

    /* If the key was not within this file, or null anyway... */
    if ( key != 0 ) {
	CTopic *aTopic;

	/* Clear the list */
	XmListDeleteAllItems(linksList);
	
	/* Load the topic; this parses the contents of the file
	   in /key/ and then stores the results in the Topic, a 
	   pointer to which it returns */
	aTopic = db.OpenTopic(key);

	/* Whether the load has succeeded or failed, we need a base name for
	   the key, so that we can give an appropriate (and concise) message 
	   in the status bar */
	const char *fileBaseName = key + strlen( key ) ;
	while ( fileBaseName != key && *fileBaseName != '/' )
	    fileBaseName -- ;

	/* ...strip off the / as well */
	if ( fileBaseName != key && fileBaseName[0] == '/' )
	    fileBaseName ++ ;

	if (aTopic) {

	    /* ...if the load was successful... */

	    CLink *link;

	    /* Set up an iterator to walk the list of links */
	    CLinkListIterator it(&aTopic->links);

	    /* Set the status bar to tell the user the load was 
	       successful... */
	    statusBar->SetTextFromResource( "fileLoaded", fileBaseName ) ;

	    /* Set the title text field... */
	    XmTextFieldSetString(titleText, (char *) aTopic->title);

	    /* Set the body text area */
	    XmTextSetString(helpText, (char *) aTopic->text);

	    /* Walk the list of links, adding them to the links list */
	    while ((link = it()) != 0)
		XmListAddItem( linksList, 
			       XmStringCreateLocalized( link->text ), 0 );

	    /* If we're meant to be adding this topic to the history list...*/
	    if (bAddToHistory) {

		/* then, if we have a history window at all... */
		if (historyShell) {

		    /* Delete all items from the current item downwards;
		       the number of items to delete is the total number 
		       of items minus the index of the current item, minus 1,
		       and the number from which to delete is the index of 
		       the current item plus one - the adjustments are due to 
		       the fact that we number from 0 and Motif numbers from
		       1. */
		    XmListDeleteItemsPos( historyShell->historyList, 
					  historyList.GetNumItems()
					  - historyList.currentItem - 1, 
					  historyList.currentItem+2);
	       
		    /* Add the new item to the history list */
		    XmListAddItem( historyShell->historyList, 
				   XmStringCreateLocalized( aTopic->title ),
				   0 );
		}

		/* Even if we have no history window, we must add a link to the
		   topic to the list of history items we keep internally. 
		   The current pathname will have been sorted out in the call
		   to CHelpDb::OpenTopic earlier on, so all we need to do
		   to get the full pathname is tack the basename onto the 
		   database's current path. */
		CJString fullName( db.pathName ) ;
		fullName += fileBaseName ;

		/* ...then we add a new item to the history list. */
		historyList.AppendItem( new CLink( aTopic->title, fullName ) );
	    }
	} else {
	    /* If the load failed, give the user appropriate feedback in the 
	       body text area and in the status bar. */
	    XmTextSetString(helpText, "<Can't read help file>");
	    statusBar->SetTextFromResource( "loadFailed", fileBaseName ) ;
	}

	/* Forget the old topic and remember the new... */
	delete currentTopic;
	currentTopic = aTopic ;
    }

    /* If namedA is non-zero, there's a named anchor in this document we're 
       meant to be jumping to */
    if ( namedA ) {
	if ( currentTopic->namedAnchors ) {
	    int charPos = currentTopic->namedAnchors->positionOf( namedA ) ;

	    /* If the named anchor was found, jump to it */
	    if ( charPos != -1 )
		XmTextSetTopCharacter( helpText, charPos ) ;
	} else
	    statusBar->SetTextFromResource( "badNamedA", key + i ) ;
	    
    }

    /* Hook for product testing - please ignore. */
    if ( getenv( "_XD_TESTING" ) ) {	
	if ( currentTopic->title.GetLength() != 0 )
	    fprintf( stderr, "visu_help - gone to page titled %s.\n",
		     currentTopic->title.GetData() ) ;
	else
	    fprintf( stderr, "visu_help - go to untitled page, filename %s\n",
		     key ) ;
    }
    
    UpdateUI();
}

/**********************************************************************
** The command dispatching system in the help system is designed to
** allow user actions to send 'commands', represented as long 
** integers, which are passed from UI object to UI object until 
** an object has dealt with it; this is similar to the X event
** handling system, but at a much higher level.  This allows 
** individual callbacks to be much simpler.
*/

Boolean
CGuru::DoCommand(long cmd)
{
    (void) DispatchCommand( cmd, FALSE );
    UpdateUI();
    return TRUE;
}

/*
** The only specific command this class knows how to dispatch is
** an attempt to update the body text.  This is sent off to a
** text object which handles the change appropriately.
*/
Boolean
CGuru::DispatchCommand(long cmd, Boolean bUpdate)
{
    if ( XmGetFocusWidget( helpText ) == helpText ) {
	CText t( helpText );
	if ( t.DispatchCommand( cmd, bUpdate ) ) {	
	    return True;
	}
    }
    return CCmdHandler::DispatchCommand( cmd, bUpdate );
}

Boolean
CGuru::OnCommand(long cmd)
{
	CLink *e;
	switch (cmd) {
	case cmdFirst:
		e = historyList.First();
		if (e)
			SetTopic(e->name);
		break;
	case cmdBack:
		e = historyList.Back();
		if (e)
			SetTopic(e->name);
		break;
	case cmdForward:
		e = historyList.Forward();
		if (e)
			SetTopic(e->name);
		break;
	default:
		return CCmdHandler::OnCommand(cmd);
	}
	return FALSE;
}

Boolean
CGuru::OnUpdateCommand(long cmd)
{
	switch (cmd) {
	case cmdFirst:
		return historyList.GetNumItems() != 0;
	case cmdBack:
		return historyList.CanGoBack();
	case cmdForward:
		return historyList.CanGoForward();
	default:
		return CCmdHandler::OnUpdateCommand(cmd);
	}
	return FALSE;
}

/**********************************************************************
** Callback methods - mostly simply show dialogs or send
** commands off to the dispatcher above.  Many are never 
** called because the relevant buttons are unmanaged.  
** They may be used in future released.
*/

/*
** X-Designer Stub CGuru::OnSelectLink
*/

/*
** Method called when a link in the list of links receives a default action
** - normally a double-click, or the return key being pressed when the link
** is selected.  Our action is to find the CLink object which represents
** the link and go to its topic.
*/
void 
CGuru::OnSelectLink( Widget w, XtPointer xt_call_data )
{
    XmListCallbackStruct *s = (XmListCallbackStruct *) xt_call_data;
    int i;
    CLink *link = currentTopic->links.NthItem(s->item_position);
    if (link)
	SetTopic(link->name, True);
}

/*
** X-Designer Stub CGuru::OnFileQuit
*/

void 
CGuru::OnFileQuit (Widget w, XtPointer xt_call_data )
{
    Exit() ;
}

/*
** X-Designer Stub CGuru::OnFileOpen
*/

void 
CGuru::OnFileOpen (Widget w, XtPointer xt_call_data )
{
    openFileDialog->xd_show();
}

/*
** X-Designer Stub CGuru::OnEditClear
*/

void 
CGuru::OnEditClear (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdClear);
}

/*
** X-Designer Stub CGuru::OnEditPaste
*/

void 
CGuru::OnEditPaste (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdPaste);
}

/*
** X-Designer Stub CGuru::OnEditCopy
*/

void 
CGuru::OnEditCopy (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdCopy);
}

/*
** X-Designer Stub CGuru::OnEditCut
*/

void 
CGuru::OnEditCut (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdCut);
}

/*
** X-Designer Stub CGuru::OnEditUndo
*/

void 
CGuru::OnEditUndo (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdUndo);
}

/*
** X-Designer Stub CGuru::OnFilePrint
*/

void 
CGuru::OnFilePrint (Widget w, XtPointer xt_call_data )
{
    printDialog->xd_show();
}

/*
** X-Designer Stub CGuru::OnFileRevert
*/

void 
CGuru::OnFileRevert (Widget w, XtPointer xt_call_data )
{
}

/*
** X-Designer Stub CGuru::OnFileSaveAs
*/

void 
CGuru::OnFileSaveAs (Widget w, XtPointer xt_call_data )
{
    saveFileDialog->xd_show();
}

/*
** X-Designer Stub CGuru::OnFileSave
*/

void 
CGuru::OnFileSave (Widget w, XtPointer xt_call_data )
{
    saveFileDialog->xd_show();
}

/*
** X-Designer Stub CGuru::OnFileClose
*/

void 
CGuru::OnFileClose (Widget w, XtPointer xt_call_data )
{
}

/*
** X-Designer Stub CGuru::OnFileNew
*/

void 
CGuru::OnFileNew (Widget w, XtPointer xt_call_data )
{
}

/*
** X-Designer Stub CGuru::OnNavIndex
*/

void 
CGuru::OnNavIndex (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdFirst);
}

/*
** X-Designer Stub CGuru::OnNavForward
*/

void 
CGuru::OnNavForward (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdForward);
}

/*
** X-Designer Stub CGuru::OnNavBack
*/

void 
CGuru::OnNavBack (Widget w, XtPointer xt_call_data )
{
    DoCommand(cmdBack);
}

/*
** X-Designer Stub CGuru::OnLinkSelection
*/

/*
** Method for browse (generally single-click) on links list item - 
** simply show the destination of the link in the status bar
*/
void 
CGuru::OnLinkSelection (Widget w, XtPointer xt_call_data )
{
    XmListCallbackStruct *s = (XmListCallbackStruct *)xt_call_data;
    CLink *link = currentTopic->links.NthItem( s->item_position );
    if ( link )
	statusBar->SetTextFromResource( "linkToText", link->name ) ;
    UpdateUI();
}

/*
** X-Designer Stub OnListDrop
*/

void 
OnListDrop (Widget w, XtPointer client_data, XtPointer xt_call_data )
{
    /* Put code here for handling drop into list */
}

/*
** X-Designer Stub CGuru::OnTextSelectionChanged
*/

void 
CGuru::OnTextSelectionChanged (Widget w, XtPointer xt_call_data )
{
    UpdateUI();
}

/*
** X-Designer Stub CHistoryDialog::OnHelp
*/

void 
CHistoryDialog::OnHelp (Widget w, XtPointer xt_call_data )
{
    /* NB. SetHelpTopic sends the borwser to a set of help files specific to 
       the browser itself, unlike SetTopic which goes to a file possibly
       relative to the last or absolute */
    helpShell->SetHelpTopic("xdh_historyDialog");
}

/*
** X-Designer Stub CHistoryDialog::OnCancel
*/

void 
CHistoryDialog::OnCancel (Widget w, XtPointer xt_call_data )
{
    XtUnmanageChild(historyForm);
}

/*
** X-Designer Stub CHistoryDialog::OnOK
*/

void 
CHistoryDialog::OnOK (Widget w, XtPointer xt_call_data )
{
    int *list = 0;
    int count;

    /* find out which history item was selected */
    if (XmListGetSelectedPos(historyList, &list, &count)) {

	/* See if it has an associated link, and if so go to the link */
	CLink *e = itsList->NthItem(list[0]);
	if (e)
	    helpShell->SetTopic(e->name, False);

	XtFree((char *)list);
    }
}

/*
** X-Designer Stub CGuru::OnNavHistory
*/

void 
CGuru::OnNavHistory (Widget w, XtPointer xt_call_data )
{
    historyShell->itsList = &historyList;
    XtManageChild(historyShell->historyForm);
}

/*
** X-Designer Stub CGuru::OnHelpVersion
*/

void 
CGuru::OnHelpVersion (Widget w, XtPointer xt_call_data )
{
    XtManageChild(aboutShell->aboutDT);
}

/*
** X-Designer Stub CAboutBox::OnOK
*/

void 
CAboutBox::OnOK (Widget w, XtPointer xt_call_data )
{
    XtUnmanageChild(aboutDT);
}

/*
** X-Designer Stub COpenFileDialog::OnHelp
*/

void 
COpenFileDialog::OnHelp( Widget w, XtPointer xt_call_data )
{
    helpShell->SetHelpTopic("xdh_openFile");
}

/*
** X-Designer Stub COpenFileDialog::OnOK
*/

void 
COpenFileDialog::OnOK( Widget w, XtPointer xt_call_data )
{
    XmFileSelectionBoxCallbackStruct *s = 
	(XmFileSelectionBoxCallbackStruct *) xt_call_data;
    char *fileName;

    xd_hide();
    if (XmStringGetLtoR(s->value, XmFONTLIST_DEFAULT_TAG, &fileName)) {
	helpShell->SetTopic(fileName, True);
	XtFree(fileName);
    }
}

/*
** X-Designer Stub COpenFileDialog::OnCancel
*/

void 
COpenFileDialog::OnCancel( Widget w, XtPointer xt_call_data )
{
    xd_hide() ;
}

/*
** X-Designer Stub CSaveFileDialog::OnHelp
*/

void 
CSaveFileDialog::OnHelp( Widget w, XtPointer xt_call_data )
{
    helpShell->SetHelpTopic( "xdh_saveFile" ) ;
}

/*
** X-Designer Stub CSaveFileDialog::OnOK
*/

void 
CSaveFileDialog::OnOK( Widget w, XtPointer xt_call_data )
{
    XmFileSelectionBoxCallbackStruct *s = 
	(XmFileSelectionBoxCallbackStruct *) xt_call_data ;
    char *fileName ;

    if (XmStringGetLtoR(s->value, XmFONTLIST_DEFAULT_TAG, &fileName)) {

	CWaitCursor waitCursor( helpShell->xd_rootwidget() ) ;
	/* TODO - code for saving */
	XtFree(fileName);
    }
    xd_hide();
}

/*
** X-Designer Stub CSaveFileDialog::OnCancel
*/

void 
CSaveFileDialog::OnCancel (Widget w, XtPointer xt_call_data )
{
    xd_hide();
}
/*
** X-Designer Stub CFindDialog::OnHelp
*/

void 
CFindDialog::OnHelp (Widget w, XtPointer xt_call_data )
{
    helpShell->SetHelpTopic("xdh_findText");
}

/*
** X-Designer Stub CFindDialog::OnClose
*/

void 
CFindDialog::OnClose (Widget w, XtPointer xt_call_data )
{
    xd_hide();
}

/*
** X-Designer Stub CFindDialog::OnFind
*/

/*
** Method to search the current directory and return a list of links to 
** files containing the phrase specified in the Find dialog's text field 
*/
void 
CFindDialog::OnFind (Widget w, XtPointer xt_call_data )
{
    /* Get the text we're searching for... */
    char *s = XmTextFieldGetString(findText);

    /* Create a new list to hold the results (which is identical to the 
       history list in format */
    CHistoryList *list = new CHistoryList;

    /* Make the cursor a watch */
    CWaitCursor c( helpShell->xd_rootwidget() );

    /* Find the text in the database (which is an abstraction of the current
       database */
    int count = db.FindText( s, strlen(s), *list );

    XtFree(s);

    /* ...if we found anything... */
    if (count) {

	CLink *link;

	/* if the search results dialog exists... */
	if (!searchResults) {

	    /* Create it with an appropriate name; it doesn't matter that
	       it's a history dialog, as it has identical functionality to
	       one and the widget name means that it picks up different
	       resources and thus has a different dialog title */
	    searchResults = new CHistoryDialog;
	    searchResults->create( helpShell->xd_rootwidget(), 
				   "searchResultsDialog" );

	} else {

	    /* the results dialog did exist, so we must clear its list */
	    delete searchResults->itsList;
	    XmListDeleteAllItems(searchResults->historyList);

	}

	/* Set up the results dialog - set its internal record of the 
	   results... */
	searchResults->itsList = list;

	/* ...then walk the list, adding items to the GUI list as we go */
	CLinkListIterator it(list);
	while ((link = it()) != 0)
	    XmListAddItem( searchResults->historyList, 
			   XmStringCreateLocalized(link->text),
			   0 );

	/* ...now hide ourselves, as we are no longer needed */
	xd_hide();

	/* ...and show the results dialog */
	searchResults->xd_show();
	
    } else {

	/* ...nothing found - hide the search dialog, destroy the (empty)
	   list of results, and show a dialog telling the user of the 
	   failure */

	delete list;
	xd_hide();

	findAlert->xd_show();

    }
}

/*
** X-Designer Stub CGuru::OnFind
*/

void 
CGuru::OnFind (Widget w, XtPointer xt_call_data )
{
    findDialog->xd_show();
}

/*
** X-Designer Stub CFindAlert::OnOK
*/

void 
CFindAlert::OnOK (Widget w, XtPointer xt_call_data )
{
    xd_hide();
}

/*
** X-Designer Stub CSysErrorAlert::OnOK
*/

void 
CSysErrorAlert::OnOK (Widget w, XtPointer xt_call_data )
{
}

/*
** X-Designer Stub COpenDbErrorAlert::OnOK
*/

void 
COpenDbErrorAlert::OnOK (Widget w, XtPointer xt_call_data )
{
}

/*
** X-Designer Stub CGuru::OnSetStatus
*/

/*
** Method to set the status in the status bar; called on the arm
** callback of toolbar and menu buttons.  Reads the help string 
** from the resource file and puts it in the status bar
*/
void 
CGuru::OnSetStatus (Widget w, XtPointer xt_call_data )
{
    XrmValue v;
    CJString name = "visu_help.";
    char *s;
    
    name += XtName(w);
    name += ".helpPrompt";
    if ( XrmGetResource( XrmGetDatabase( XtDisplay( w ) ), name, 
			 "visu_help.HelpPrompt", &s, &v ) ) {
	statusBar->SetText( v.addr ) ;
    } else
	statusBar->Clear() ;
}

/*
** X-Designer Stub CGuru::OnUnsetStatus
*/

/*
** Clear the status bar when buttons with status bar help are unarmed
*/
void 
CGuru::OnUnsetStatus (Widget w, XtPointer xt_call_data )
{
    statusBar->Clear() ;
}

/*
** X-Designer Stub CPrintDialog::OnHelp
*/

void 
CPrintDialog::OnHelp (Widget w, XtPointer xt_call_data )
{
    helpShell->SetHelpTopic("xdh_printItem");
}

/*
** X-Designer Stub CPrintDialog::OnCancel
*/

void 
CPrintDialog::OnCancel (Widget w, XtPointer xt_call_data )
{
    xd_hide();
}

/*
** X-Designer Stub CPrintDialog::OnOK
*/

/*
** Method to print the current topic to the command specified in the
** print dialog 
*/
void 
CPrintDialog::OnOK (Widget w, XtPointer xt_call_data )
{
    /* Get the name of the command */
    char *cmd = XmTextGetString(printCommand);

    /* Make the cursor say the application is busy */
    CWaitCursor waitCursor(helpShell->xd_rootwidget());

    /* Open a stream to the command */
    FILE *f = popen(cmd, "w");

    Boolean ok = False;

    /* If the stream open was successful... */
    if (f) {
	char *text;
	ok = True;

	/* ...get the title of the text to print */
	text = XmTextGetString(helpShell->titleText);

	/* and print it... */
	fwrite(text, 1, strlen(text), f);

	/* XmTextGetString allocates memory - free it */
	XtFree(text);

	/* Print a blank line to separate title and body */
	fputc('\n', f);

	/* Get the body text */
	text = XmTextGetString(helpShell->helpText);

	/* Print this, too */
	fwrite(text, 1, strlen(text), f);
	XtFree(text);

	/* If the pipe cannot be closed without problems we have a problem */
	if (pclose(f) != 0)
	    ok = False;

	/* but whatever happens the dialog should disappear */
	xd_hide();
    }

    /* ...if there was an error printing... */
    if (!ok) {
	char *s;
	XrmValue v;
	
	/* Get an appropriate message from the resource file */
	if (XrmGetResource( XrmGetDatabase(XtDisplay(w)), 
			    "visu_help.cantPrintMsg", 
			    "visu_help.CantPrintMessage", &s, &v)) {
	    char buf[1024];
	    
	    /* if we can get an appropriate message, replace the %s in 
	       the string with the text the system would use to describe 
	       the error */
	    sprintf(buf, v.addr, strerror(errno));

	    /* Set the 'system error' dialog box's label to describe this 
	       error */
	    XmString text = XmStringCreateLocalized(buf);
	    XtVaSetValues( sysErrAlert->messageLabel, 
			   XmNlabelString, text, NULL);
	    XmStringFree(text);

	}
	/* ...and show the dialog whether we could get an appropriate message
	   or not, as the user should be told about the problem whatever 
	   happens */
	sysErrAlert->xd_show();
    }

    /* ...tidy up */
    XtFree(cmd);
}

/*
** X-Designer Stub CGuru::OnHelpIndex
*/

void 
CGuru::OnHelpIndex (Widget w, XtPointer xt_call_data )
{
	SetHelpTopic("xdh_entry");
}

/*
** Main event loop for the help system - just a normal Xt main event loop.
*/
void
CGuru::MainLoop(XtAppContext appContext, Display *display)
{
	XtAppMainLoop( appContext ) ;
}

/*********************************************************************** 
** Code for the help system's IPC mechanism.  For comlete documentation
** of the protocol used, please see the header comment in the client-side 
** library's code file
*/

/*
** Static method called when the system is being told to be sent to a 
** new topic - ensures the event which triggered its activation
** is the correct one, a property change on a specific widget
*/
void
CGuru::NewTopic( Widget w, XtPointer client_data, XEvent *event, Boolean *ctd )
{
    CGuru *obj = (CGuru *) client_data ;

    /* Check that this *is* the event we were expecting */
    if ( event->type == PropertyNotify
      && event->xproperty.atom == helpDest ) {

	/* If it is the right event, do not continue to dispatch it... */
	*ctd = False ;

	/* ...and move on to the new topic. */
	obj->NewTopic( w, event, ctd ) ;

    } else {
	/* ...if it is not the right event, continue to dispatch it to see
	   if anyone else is interested in it */	  
	*ctd = True ;
    }
}

/*
** Instance method called by static method of the smae name above; called
** when the correct signal for moving to a new help topic has been received;
** extracts the information from the widget used for IPC and then
** sends the browser there.
*/
void
CGuru::NewTopic( Widget w, XEvent *event, Boolean *ctd ) {
    Atom  actualType ;
    int   actualFormat,
	  numItems,
	  leftOver ;
    char  newTopic[1024] ;
    unsigned char *retData ;

    /* If we can get the information out of the property, and it's of
       the right type... */
    if ( XGetWindowProperty( XtDisplay( w ), 
			     XtWindow( myCommWidget ),
			     helpDest,
			     0L,
			     sizeof( newTopic ) - 1,
			     False,
			     XA_STRING,
			     &actualType, &actualFormat,
			     (unsigned long *)&numItems,
			     (unsigned long *)&leftOver,
			     (unsigned char **)&retData ) == Success 
      && actualType == XA_STRING ) {

	/* Put the new topic's name into local storage for safety's sake... */
	strncpy( newTopic, (char *)retData, sizeof( newTopic ) - 1 ) ;

	/* ...free the topic information we got, as that was malloc'd
	   by the XGetWindowProperty() */
	XtFree( (char *) retData ) ;

	/* Deiconify the viewer (if necessary) */
	XtMapWidget( helpShell ) ;

	/* ...and raise the window */
	XRaiseWindow( XtDisplay( helpShell ), XtWindow( helpShell ) ) ;

	/* ...then send the browser there, adding the topic to the history 
	   list */
	SetTopic( newTopic, True ) ;
	
    }
}

/*
** Code to connect to the client application, if there is one.  The 
** window /w/ passed in is the window passed in on the command line
*/
void
CGuru::IPCConnect( Window w ) {

    /* Designate a widget to communicate through (and to use for getting
       the display, etc.) */
    myCommWidget = helpMenu ;

    /* Prepare to receive messages: */
    XtAddEventHandler( myCommWidget, PropertyChangeMask, False,
		       CGuru::NewTopic, this ) ;

    /* ...then tell our client where we are: find out the window ID */
    static Window myCommWindow = XtWindow( myCommWidget ) ;
    unsigned char *dataAddr = (unsigned char *) &myCommWindow ;
    
    /* Find the names of the atoms to use for the properties */
    XDHelpGetAtoms( helpMenu ) ;


    /* Set the property, notifying our client that we are here, and what
       the ID to communicate to us is */
    XChangeProperty( XtDisplay( helpMenu ), w, helpWin, XA_WINDOW, 
		     sizeof( int ) * 8, PropModeReplace, dataAddr, 1 ) ;

}

/*
** Code to set up the help system's IPC mechanism.  We are passed a window
** ID on the command line and must set one of its properties to the window
** ID of one of our widgets; our widget is then used for IPC - this allows
** the client application to detect when we are closed very easily.
*/
void
CGuru::SetupIPC( int argc, char **argv ) {
    int pos = 0 ;

    unsigned long wid ;

    /* Look for the '-wid' flag which tags the window ID on the command line */
    for ( int i=1; (i < argc) && (pos == 0) ; i++ )
	if ( strcmp( argv[i], "-wid" ) == 0 )
	    pos = i ;

    /* If we found it and it is followed by something else which could 
       concievably be a window ID then connect to the client application
       using that window. */
    if ( pos != 0 && ++pos < argc 
      && sscanf( argv[pos], "%uld", &wid ) == 1 )
	IPCConnect( (Window) wid ) ;
}
