/*** DTB_USER_CODE_START vvv Add file header below vvv ***/
/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/*** DTB_USER_CODE_END   ^^^ Add file header above ^^^ ***/

/*
 * File: help_ed_stubs.c
 * Contains: Module callbacks and connection functions
 *
 * This file was generated by dtcodegen, from module help_ed
 *
 * Any text may be added between the DTB_USER_CODE_START and
 * DTB_USER_CODE_END comments (even non-C code). Descriptive comments
 * are provided only as an aid.
 *
 *  ** EDIT ONLY WITHIN SECTIONS MARKED WITH DTB_USER_CODE COMMENTS.  **
 *  ** ALL OTHER MODIFICATIONS WILL BE OVERWRITTEN. DO NOT MODIFY OR  **
 *  ** DELETE THE GENERATED COMMENTS!                                 **
 */

#include <stdint.h>
#include <stdio.h>
#include <Xm/Xm.h>
#include "dtb_utils.h"
#include "dtbuilder.h"
#include "help_ed_ui.h"


/**************************************************************************
 *** DTB_USER_CODE_START
 ***
 *** All necessary header files have been included.
 ***
 *** Add include files, types, macros, externs, and user functions here.
 ***/

#include <Xm/List.h>
#include "dtbuilder.h"
#include "help_ed_ui.h"

#include <Xm/Text.h>
#include <Xm/List.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/Separator.h>

#include <ab_private/abobj.h>
#include <ab_private/ab.h>
#include <ab_private/objxm.h>
#include <ab_private/ui_util.h>
#include <ab_private/trav.h>
#include <ab_private/obj_notify.h>
#include <ab_private/proj.h>
#include <ab_private/abobj_list.h>
#include <ab_private/abobj_set.h>
#include <ab_private/pal.h>
#include <ab_private/prop.h>
#include <ab_private/help.h>

#include <Dt/Help.h>
#include <Dt/HelpQuickD.h>
#include <Dt/HelpDialog.h>
#include "dtb_utils.h"

/*
 * Declarations of global widgets used by callbacks.
 */

static Widget	HelpObjList    = (Widget) NULL;
static Widget   HelpText       = (Widget) NULL;
static Widget   HelpVolume     = (Widget) NULL;
static Widget   HelpLocID      = (Widget) NULL;
static Widget	HelpObjectMenu = (Widget) NULL;

/*
 * End declarations of global widgets
 */

static ABObj		HelpObj	       = (ABObj) NULL;

typedef struct HELP_OBJ_TYPE_INFO_REC {
	AB_OBJECT_TYPE	type;
	int		subtype;
} HelpObjTypeInfoRec;
static HelpObjTypeInfoRec HelpObjTypeInfo = {
	((AB_OBJECT_TYPE)NULL), 0,
};

typedef struct HELP_EDITOR_SETTINGS {
	Widget			prop_sheet;
	PropFieldSettingRec	text;
	PropFieldSettingRec	volume;
	PropFieldSettingRec	locID;
} HelpEditorSettingsRec, *HelpEditorSettings;

HelpEditorSettingsRec Help_Editor_Settings_Rec;

/***********************************************************************
**
**  Private Function Declarations
**
************************************************************************/

static BOOL	editable_obj_test(
    		    PalEditableObjInfo  *ed_obj_info
		);

static void	help_dispatchCB(
		    Widget      widget,
		    XtPointer   client_data,
		    XtPointer   call_data
		);

static void     more_help_dispatch(
                    Widget widget,
                    XtPointer clientData,
                    XtPointer callData
                );

static void     help_back_hdlr(
                    Widget widget,
                    XtPointer clientData,
                    XtPointer callData
                );

static void	help_attr_chgCB(
		    Widget      widget,
		    XtPointer   client_data,
		    XtPointer   call_data
		);

static void	select_instanceCB(
		    Widget      widget,
		    XtPointer   client_data,
		    XmListCallbackStruct *listdata
		);

static int	object_was_created(
		    ObjEvCreateInfo	info
		);

static int	object_was_deleted(
		    ObjEvDestroyInfo	info
		);

static int	object_was_renamed(
		    ObjEvAttChangeInfo	info
		);

static int	object_was_updated(
		    ObjEvUpdateInfo	info
		);

static void	clear_editor_fields(
		);

static void	fetch_help_attrs(
		    ABObj obj
		);

static void	update_help_attrs(
		    ABObj obj
		);

static DTB_MODAL_ANSWER	do_auto_apply(
		    Widget w,
		    ABObj old_obj,
		    ABObj new_obj
		);

static BOOL	help_list_test(
		    ABObj obj
		);

static BOOL	help_obj_is_target_type(
		    ABObj obj
		);

static void	help_test_onitem_help(
		);

static void	verify_closeCB(
		    Widget	w,
		    XtPointer	client_data,
		    XtPointer	call_data
		);

static void	change_objecttype_palCB(
		    Widget	w,
		    XtPointer	client_data,
		    XtPointer	call_data
		);

static void	change_objtype(
		    Widget	w,
		    AB_OBJECT_TYPE	type,
		    int			subtype
		);

static void	update_object_menu_from_obj(
		    Widget	menu,
		    ABObj	obj
		);

static ABObj	find_toplevel_obj(
		);

static BOOL
editable_obj_test(
    PalEditableObjInfo  *ed_obj_info
)
{
    BOOL        needed = True;

    switch(ed_obj_info->type)
    {
        case AB_TYPE_PROJECT:
	case AB_TYPE_LAYERS:
            needed = False;
            break;
	case AB_TYPE_ITEM:
	    if (ed_obj_info->subtype != AB_ITEM_FOR_CHOICE)
		needed = False;
            break;
    }
 
    return needed;
}

/* Bring up the Help Editor dialog and indicate the current object (if any) */
extern void
ab_popup_help(
    Widget	widget,
    XtPointer	client_data,
    XtPointer	call_data
)
{
    DtbHelpEdHelpEditorInfo	help_ed = &dtb_help_ed_help_editor;
    extern Widget		AB_toplevel;
    Widget			dialog_parent;
    int				top_pos, bottom_pos;
    char 			*objname;
    STRING			modname;
    ABSelectedRec		sel;
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    /* If there is no help editor widget, create it */
    if (AB_help_dialog == (Widget)NULL)
    {
	/* Create help editor dialog */
	dtbHelpEdHelpEditorInfo_clear(help_ed);
	dtb_help_ed_help_editor_initialize(help_ed,AB_toplevel);
	AB_help_dialog = help_ed->help_editor_shellform;

	/* Set up local handles for important widgets */
	HelpObjList    = help_ed->obj_list;
	HelpText       = help_ed->help_text;
	HelpVolume     = help_ed->help_volume;
	HelpLocID      = help_ed->help_location;
	HelpObjectMenu = help_ed->obj_type_menu;

	/* Build object type menu (but first must destroy dummy option item) */
	XtDestroyWidget(help_ed->obj_type_menu_items.object_type_item);
	pal_add_editable_obj_menu_items(help_ed->obj_type_menu_menu,
                change_objecttype_palCB, editable_obj_test);
	/* Initialize to First type in list */
/*
    	HelpObjTypeInfo.type    = AB_TYPE_UNDEF;
    	HelpObjTypeInfo.subtype = AB_NO_SUBTYPE;
*/

        /* 
         * Setup dialog to participate in dtbuilder window protocol
         */  
        ab_register_window(AB_help_dialog, AB_WIN_DIALOG, 
		WindowHidden, AB_toplevel, AB_WPOS_TILE_HORIZONTAL,
		verify_closeCB, NULL);

	/* Register Help Editor callbacks */
	XtAddCallback(HelpObjList,XmNsingleSelectionCallback,
		(XtCallbackProc)select_instanceCB, (XtPointer)NULL);

	/* Initialize settings data structure */
	hes->prop_sheet = help_ed->text_panel;
	prop_field_init(&(hes->text),help_ed->hlptxt_label,
		help_ed->help_text,help_ed->txt_change_bar);
	prop_field_init(&(hes->volume),help_ed->help_volume_label,
		help_ed->help_volume,help_ed->vol_change_bar);
	prop_field_init(&(hes->locID),help_ed->help_location_label,
		help_ed->help_location,help_ed->loc_change_bar);
    }

    /* 
    ** If we got here via an editor we have some particular object
    ** in mind (as set via ab_set_help_obj(), probably), so we should make 
    ** sure it shows as selected in the scrolling list and its attributes 
    ** appear.  If not, we may still have an object in mind if there is a 
    ** currently seleted object.  In some cases there isn't even a currently 
    ** selected object, so we just show what happens to be displayed in 
    ** the object type menu.
    */
    if(HelpObj == NULL) {
        PalEditableObjInfo *ed_obj_info;

	/* See if there is a currently-selected object */
	abobj_get_selected(proj_get_project(),FALSE,FALSE,&sel);
	if(sel.count > 0) {
	    /* Yep - found one, so set it as the current object */
	    HelpObj = sel.list[0];
	}
	else {
	    /*
	    ** Nope, nothing selected, so look for something usable.
	    ** If this fails, there must really not be any salient UI objects
	    */
	    HelpObj = find_toplevel_obj();
	}

        /* Check to see if this an object that allows Help Text...
         */
        if (HelpObj && (ed_obj_info = pal_get_editable_obj_info(HelpObj)))
        {
            if (editable_obj_test(ed_obj_info))
            {
		HelpObjTypeInfo.type = obj_get_type(HelpObj);
		HelpObjTypeInfo.subtype = obj_get_subtype(HelpObj);
	    }
	}
    }

    if(HelpObj != NULL) {
    	/* 
    	 ** Having figured out what (if any) object type we should be targeting,
    	 ** we can initialize the list of objects.
    	 */
    	if(HelpObjList != (Widget) NULL) {
            (void) abobj_list_load(HelpObjList,proj_get_project(),
                help_list_test);
    	}
	update_object_menu_from_obj(HelpObjectMenu,HelpObj);
	modname = abobj_get_moduled_name(HelpObj);
	util_dprintf(2,"Setting <%s> as the current item\n",modname);
	ui_list_select_item(HelpObjList,modname,FALSE);
	fetch_help_attrs(HelpObj);
	XtFree(modname);
    }
    else if (HelpObjTypeInfo.type == AB_TYPE_UNDEF)
	/* Just load first Object type in List */
	change_objtype(NULL, AB_TYPE_BUTTON, AB_NO_SUBTYPE);

    /* Now we can show what we've got */
    ab_show_window(AB_help_dialog);

    /*
    ** Hook up callbacks so we're notified when an object is modified.
    ** an update callback occurs when the object has been completely
    ** modified (e.g., when an object is loaded from a file).
    **
    ** The second parameter is our "name" for debugging messages. We'll Just
    ** use the file name.
    **
    ** We don't use the create_callback() to detect the creation of objects
    ** because at the time they are created their name is "(nil)".  However,
    ** a later part of the creation process is to change their name from
    ** "(nil)" to their proper name, and at that time the rename callback is
    ** called.  We therefore use the rename callback to handle both object
    ** creation and renaming.
    */
    obj_add_destroy_callback(object_was_deleted, __FILE__);
    obj_add_rename_callback(object_was_renamed, __FILE__);
    obj_add_update_callback(object_was_updated, __FILE__);
}

void
ab_set_help_obj(
	ABObj	obj
)
{
	HelpObj = obj;
	if(HelpObj != (ABObj) NULL) {
	    HelpObjTypeInfo.type    = obj_get_type(HelpObj);
	    HelpObjTypeInfo.subtype = obj_get_subtype(HelpObj);
	}
	else {
	    HelpObjTypeInfo.type = (AB_OBJECT_TYPE) NULL;
	    HelpObjTypeInfo.subtype = AB_NO_SUBTYPE;
	}
}

/*
** This callback is invoked every time a change is made to one of the
** text fields in the Help Editor.  This lets us keep track of whether 
** the user has made any changes, and therefore makes it possible to
** control the appearance of change bars and handle auto_apply.
*/
static void
help_attr_chgCB(
    Widget      w,
    XtPointer   client_data,
    XtPointer   call_data
)
{
    /* Increment change counter */
    /* Change_count++; */
}

/*
** Callback: object has been selected from list of objects
*/
static void
select_instanceCB(
    Widget widget,
    XtPointer client_data,
    XmListCallbackStruct *listdata)
{
    ABObj        module;
    ABObj        selected_obj;
    STRING       name;
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    name = objxm_xmstr_to_str(listdata->item);
    if (name)
    {
	util_dprintf(2,"You selected <%s>\n",name);
        abobj_moduled_name_extract(name, &module, &selected_obj);
        XtFree(name);
        if (selected_obj)
        {
	    /*
	    ** If there are pending changes, process the requested
	    ** load in a special way that allows the user to abort it
	    ** if they so choose.  
	    */
	    if(HelpObj != NULL && 
		prop_changebars_pending(hes->prop_sheet) == True) {
		(void) do_auto_apply(widget, HelpObj, selected_obj);
	    }
	    /* No pending changes, so just load the new object */
	    else {
		HelpObj = selected_obj;
		HelpObjTypeInfo.type    = obj_get_type(HelpObj);
		HelpObjTypeInfo.subtype = obj_get_subtype(HelpObj);
		fetch_help_attrs(HelpObj);
	    }
            return;
        }
    }
    if (util_get_verbosity() > 0)
        fprintf(stderr,"Help Editor::select_instanceCB: could not get object in list\n");
 
}

/*
** Handle auto apply.  This condition can occur in any of the following ways:
**  - The user selects another object to be edited and hasn't yet saved changes 
**     made to the current object  (old_obj = current object, new_obj = new
**     object they've chosen from the list)
**  - The user selects "Close" from the Motif window menu and hasn't yet saved
**     changes made to the current object (old_obj and new_obj are both = 
**     current object)
**  - The user selects another object type from the object type menu and hasn't
**     yet saved changes made to the current object  (old_obj = current object,
**     new_obj = NULL)
** The assumption here is that the user wants to be given the opportunity
** to save the changes or discard them, and a modal dialog is an o.k.
** way to handle the situation.
*/
static DTB_MODAL_ANSWER
do_auto_apply(
	Widget list,
	ABObj old_obj,
	ABObj new_obj
)
{
    DTB_MODAL_ANSWER	answer;
    char		buffer[256];
    char		*old_name = NULL;
    char		*new_name = NULL;
    BOOL		changing_objects = FALSE;
    XmString		xm_buf = (XmString) NULL;
    DtbObjectHelpData	help_data = NULL;

    if(old_obj == (ABObj) NULL)
	return(DTB_ANSWER_CANCEL);
    else
	old_name = abobj_get_moduled_name(old_obj);

    if (new_obj == (ABObj) NULL)
	new_name = "";
    else
	new_name = abobj_get_moduled_name(new_obj);

    /* Check for object change auto-apply vs. same object auto-apply */
    if( (new_obj != (ABObj) NULL) && (new_obj != old_obj) ) 
	changing_objects = TRUE;

    if (dtb_app_resource_rec.implied_apply == True)
        answer = DTB_ANSWER_ACTION1;
    else
    {
    	help_data = (DtbObjectHelpData) util_malloc(sizeof(DtbObjectHelpDataRec));
    	help_data->help_volume = "";
    	help_data->help_locationID = "";
 
    	if (changing_objects) 
    	{
	    sprintf(buffer, CATGETS(Dtb_project_catd, 100, 31,
		"Help properties for \"%s\"\n\
		have been modified but not Applied.\n\n\
		You can Apply the Changes or Cancel the\n\
		Load operation for \"%s\"."),old_name, new_name);

	    help_data->help_text = CATGETS(Dtb_project_catd, 100, 93,
	    "Click Apply Changes to apply the changes to the\ncurrent object and load the selected object.\n\nClick Cancel if you don't want to apply the\nchanges to the current object. You can then\nclick Reset to undo the changes before loading\nthe selected object.");
    	}
    	else 
    	{
	    if(new_obj != (ABObj) NULL) 
	    {
		sprintf(buffer, CATGETS(Dtb_project_catd, 100, 32,
		"Help properties for \"%s\"\n\
		have been modified but not Applied.\n\n\
		You can Apply the Changes or Cancel the\n\
		Close operation."), old_name);

		help_data->help_text = CATGETS(Dtb_project_catd, 100, 94,
		"Click Apply Changes to apply the changes to the\ncurrent object and close the Help Editor.\n\nClick Cancel if you don't want to apply the\nchanges to the current object and want the Help\nEditor to remain displayed.  You can then click\nReset to undo the changes before closing the\nHelp Editor.");
	    }
	    else 
	    {
		sprintf(buffer,CATGETS(Dtb_project_catd, 100, 33,
		"Help properties for \"%s\"\n\
		have been modified but not Applied.\n\n\
		You can Apply the Changes or Cancel the\n\
		'Change Object-Type' operation."), old_name);

		help_data->help_text = CATGETS(Dtb_project_catd, 100, 95,
		"Click Apply Changes to apply the changes to the\ncurrent object and display the new object type.\n\nClick Cancel if you don't want to apply the\nchanges to the current object. You can then\nclick Reset to undo the changes before changing\nto a different object type.");
	    }
    	}

    	/* Popup modal message and wait for answer */
    
    	xm_buf = XmStringCreateLocalized(buffer);
    	dtb_help_ed_wrn_msg_initialize(&dtb_help_ed_wrn_msg);
    	answer = dtb_show_modal_message(list, &dtb_help_ed_wrn_msg,
		xm_buf, help_data, NULL);

    	util_free(help_data);
    	XmStringFree(xm_buf);
    }

    /* Process answer */
    switch(answer) 
    {
	case DTB_ANSWER_ACTION1: /* Apply Changes */
		update_help_attrs(old_obj);
		if (changing_objects) 
		    fetch_help_attrs(new_obj);
		break;
	case DTB_ANSWER_CANCEL: /* Cancel */
		if(changing_objects) {
		    util_dprintf(2,"Resetting <%s> as the current item\n",
			old_name);
		    ui_list_select_item(list,old_name,FALSE);
		}
		break;
    }

    if (old_obj != (ABObj)NULL) XtFree(old_name);
    if (new_obj != (ABObj)NULL) XtFree(new_name);

    /* Pass along the answer in case the caller needs it */
    return (answer);
}

static void
clear_editor_fields(void)
{
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    prop_field_set_value(&(hes->text),"",False);
    prop_field_set_value(&(hes->volume),"",False);
    prop_field_set_value(&(hes->locID),"",False);

    prop_set_changebar(hes->text.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->volume.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->locID.changebar,PROP_CB_OFF);
    prop_changebars_cleared(hes->prop_sheet);
}

/*
** Get the help attributes from an object and update the Help Editor's
** fields accordingly.
*/
static void
fetch_help_attrs(
    ABObj obj
)
{
    STRING help_vol, help_id, help_text;
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    util_dprintf(2,"Load object help attributes into editor display fields\n");

    obj_get_help_data(obj,&help_vol,&help_id,&help_text);

    /* Fill the editor's fields with the object's data */
    prop_field_set_value(&(hes->text),help_text,False);
    prop_field_set_value(&(hes->volume),help_vol,False);
    prop_field_set_value(&(hes->locID),help_id,False);

    /* Reset changebars */
    prop_set_changebar(hes->text.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->volume.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->locID.changebar,PROP_CB_OFF);
    prop_changebars_cleared(hes->prop_sheet);

    /* Update current object */
    HelpObj = obj;
    HelpObjTypeInfo.type    = obj_get_type(HelpObj);
    HelpObjTypeInfo.subtype = obj_get_subtype(HelpObj);
}


/* 
** Get the new help text out of the Help Editor and update the
** object's help attributes accordingly.
*/
static void
update_help_attrs(
	ABObj	obj
)
{
    char *help_text, *help_vol, *help_location;
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    help_text = prop_field_get_value(&(hes->text));
    help_vol = prop_field_get_value(&(hes->volume));
    help_location = prop_field_get_value(&(hes->locID));

    util_dprintf(2,"Set Help Text: vol=%s, loc=%s, text=%s\n", 
	help_vol, help_location, help_text);
    obj_set_help_data(obj,help_vol,help_location,help_text);
    XtFree(help_text);
    XtFree(help_vol);
    XtFree(help_location);

    /* We've modified this object, so we must set the "save needed" flag */
    abobj_set_save_needed(obj_get_module(obj),TRUE);
}


/*
** Test function to determine whether an object should appear in the Help
** Editor's list.
*/
static BOOL
help_list_test(
	ABObj test_obj
)
{
    /* Is this an object we'd want to display? */
    if (obj_is_salient_ui(test_obj) && 
	obj_is_defined(test_obj) &&
	(!obj_is_list_item(test_obj)) &&	/* no List Items */
	/* (!obj_is_popup_win(test_obj)) &&	    no CustomDialogs */
	(obj_get_module(test_obj) != NULL) && 
	(obj_has_flag(test_obj, MappedFlag) || (obj_is_message(test_obj)))
       )
    {
	/* Yes it is.  If we're looking for a current type, do they match ? */
	return(help_obj_is_target_type(test_obj));
    }
    return(False);
}

static BOOL
help_obj_is_target_type(
	ABObj obj
)
{
    AB_OBJECT_TYPE	type = obj_get_type(obj);
    int			subtype = obj_get_subtype(obj);

    /* In some cases we care about both type & subtype and must check both */
    if( (HelpObjTypeInfo.type == AB_TYPE_ITEM) ||
	(HelpObjTypeInfo.type == AB_TYPE_CONTAINER) ) {
	if(type == HelpObjTypeInfo.type &&
	   subtype == HelpObjTypeInfo.subtype) {
		return(True);
	}
	else return(False);
    }
    /* 
    ** Otherwise we only need to check object type, and in fact don't want to
    ** check subtype because it might confuse things (e.g. we're looking for
    ** any Button palette item, which includes several subtypes of type 
    ** AB_TYPE_BUTTON).
    */
    else {
	if(type == HelpObjTypeInfo.type) return(True);
	else return(False);
    }

    /* Should never get here - every test above returns True or False */
}

static int
object_was_created(
    ObjEvCreateInfo info
)
{
    return abobj_list_obj_created(HelpObjList, info->obj, help_list_test);
}


static int
object_was_deleted(
    ObjEvDestroyInfo info
)
{
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    /* 
    ** If this happens to be the object currently being displayed,
    ** clear out the editor's fields, remove it from the object list
    ** and leave the list & editor without a selected object.  Don't change
    ** the current object type, though.
    **
    ** Otherwise, just delete it from the list.
    */
    if((HelpObj != NULL) && (info->obj == HelpObj)) {
	clear_editor_fields();
	HelpObj = (ABObj) NULL;
    }

    abobj_list_obj_destroyed(HelpObjList, info->obj, help_list_test);

    return 0;
}

static int
object_was_renamed(
    ObjEvAttChangeInfo	info
)
{
    return abobj_list_obj_renamed(
		HelpObjList, 
		info->obj, 
		istr_string(info->old_name),
		help_list_test);
}

static int
object_was_updated(
    ObjEvUpdateInfo	info
)
{
    return abobj_list_obj_updated(HelpObjList, info, help_list_test);
}

void
help_test_enable(
    ABObj obj
)
{
    ABObj help_obj, help_btn;

    util_dprintf(2,"In help_test_enable()\n");

    /* Make sure we have an object to work with */
    if(obj == (ABObj) NULL) return;

    /* Make sure the object has help information to be enabled in test mode */
    if(obj_has_help_data(obj) == False) return;

    /*
    ** Composite objects have a specifically-identified help object
    ** to which we should attach the help callback.  For other objects types,
    ** the help object is just the object itself.
    */
    help_obj = objxm_comp_get_subobj(obj,AB_CFG_HELP_OBJ);
    if(help_obj == NULL) return;
    
    /*
    util_dprintf(2,"    help activated on %s\n", abobj_get_moduled_name(obj));
    */

    /* Add help callback */
    XtAddCallback((Widget)help_obj->ui_handle,XmNhelpCallback,
    	help_dispatchCB,(XtPointer)obj);
    
    /* 
    ** Special case handling for a dialog, where the help button (if any)
    ** needs to have an activate callback to display the dialog's help
    */
    if(obj_is_popup_win(obj)) {
	if( (help_btn=obj_get_help_act_button(obj)) != (ABObj)NULL) {
	    XtAddCallback((Widget)help_btn->ui_handle,XmNactivateCallback,
		dtb_call_help_callback,help_obj->ui_handle);
	}
    }
}

void
help_test_disable(
    ABObj obj
)
{
    ABObj help_obj, help_btn;

    util_dprintf(2,"In help_test_disable()\n");

    /* Make sure we have an object to work with */
    if(obj == (ABObj) NULL) return;

    /* Make sure the object has help information to be enabled in test mode */
    if(obj_has_help_data(obj) == False) return;

    /*
    ** Composite objects have a specifically-identified help object
    ** to which we should attach the help callback.  
    */
    help_obj = objxm_comp_get_subobj(obj,AB_CFG_HELP_OBJ);
    if(help_obj == NULL) return;

    /*
    util_dprintf(2,"    help deactivated on %s\n",abobj_get_moduled_name(obj));
    */

    XtRemoveCallback((Widget)help_obj->ui_handle,XmNhelpCallback,
    	help_dispatchCB,(XtPointer)obj);

    /* 
    ** Special case handling for a dialog, where the help button (if any)
    ** has an activate callback that needs to be removed
    */
    if(obj_is_popup_win(obj)) {
	if( (help_btn=obj_get_help_act_button(obj)) != (ABObj)NULL) {
	    XtRemoveCallback((Widget)help_btn->ui_handle,XmNactivateCallback,
		dtb_call_help_callback,help_obj->ui_handle);
	}
    }
}

/*
** Callback invoked when the user hits the Help key on an object which
** has help.  This is how help will get displayed in Test mode.
*/
static void
help_dispatchCB(Widget widget, XtPointer clientData, XtPointer callData)
{
    ABObj 		obj = (ABObj)clientData;
    STRING 		help_volume, help_location, help_text;
    int             	i;
    Arg             	wargs[10];
    char		buffer[100];
    extern Widget	AB_toplevel;
    Widget		back_button;
    static Widget	Quick_help_dialog = (Widget)NULL;
    static Widget	MoreButton;

    util_dprintf(2,"In help_dispatchCB()\n");
    obj_get_help_data(obj,&help_volume,&help_location,&help_text);
    util_dprintf(2,"help info: vol=%s, loc=%s, text=%s\n",
	(help_volume   == 0? "(nil)" : help_volume), 
	(help_location == 0? "(nil)" : help_location),
	(help_text     == 0? "(nil)" : help_text) );

    /* 
    ** In order to save the more-help info (help volume & location ID) as part
    ** of the quick help dialog's backtrack mechanism, we have to splice the 
    ** volume & ID strings together and save them as the help volume field.
    ** If there isn't supplemental help information, we save a null string.
    **
    ** Checking the status of the more-help info also lets us decide whether
    ** the "More..." button should be enabled on the dialog.
    */
    if( help_volume     ==0 || *(help_volume) == 0 ||
	help_location   ==0 || *(help_location)== 0){
		buffer[0] = '\0';
    }
    else {
	sprintf(buffer,"%s/%s",help_volume,help_location);
    }

    /* 
    ** If this is our first time to post help, create the proper dialog and
    ** set its attributes to suit the current object.  If not, then just
    ** update the attributes.
    **
    ** (You have to be careful about gratuitous SetValues on the dialog because
    ** its internal stack mechanism takes repeated settings as separate items
    ** and updates the stack for each.)
    */
    if(Quick_help_dialog == (Widget)NULL) {
        /* Create shared help dialog */
        i = 0;
	XtSetArg(wargs[i],XmNtitle, "Application Help");            i++;
	XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_DYNAMIC_STRING); i++;
	XtSetArg(wargs[i],DtNstringData,help_text);                 i++;
        XtSetArg(wargs[i],DtNhelpVolume,buffer);		    i++;
	Quick_help_dialog = DtCreateHelpQuickDialog(AB_toplevel,
		"Help",wargs,i);

	/* 
	** Fetch out the Dialog's More button child and hook the 'more help'
	** handler to its activateCallback.  Set it's current status to
	** indicate whether this object has supplemental help data. 
	*/
	MoreButton = DtHelpQuickDialogGetChild(Quick_help_dialog,
		DtHELP_QUICK_MORE_BUTTON);
	XtManageChild(MoreButton);
	XtAddCallback(MoreButton,XmNactivateCallback,more_help_dispatch,
		(XtPointer)Quick_help_dialog);
	if(buffer[0] == '\0') XtSetSensitive(MoreButton,False);

	/* 
	** Fetch out the Dialog's Backtrack button child & hook a callback
	** that will control button sensitivity based on the presence of more
	** help data.
	*/
	back_button = DtHelpQuickDialogGetChild(Quick_help_dialog,
		DtHELP_QUICK_BACK_BUTTON);
	XtAddCallback(back_button,XmNactivateCallback,help_back_hdlr,
		(XtPointer)Quick_help_dialog);
    }
    /* Otherwise the dialog already exists so we just set the attributes. */
    else {
	/* 
	** If we have supplemental help info, enable the more button.
	** Also save this info for later use in the backtrack handler.
	*/
	if(buffer[0] == '\0') {
	    XtSetSensitive(MoreButton,False);
	}
	else {
	    XtSetSensitive(MoreButton,True);
	}

        XtVaSetValues(Quick_help_dialog,
    	    DtNhelpType, DtHELP_TYPE_DYNAMIC_STRING,
            DtNhelpVolume,buffer,
            DtNstringData,help_text,
	    NULL);
    }

    /* Now display the help dialog */
    XtManageChild(Quick_help_dialog);
}

/*
** This function is used in Test Mode to exercise on-item help in an
** application under development.  It is special because it needs to be
** able to transform the widget returned by DtHelpReturnSelectedWidget()
** into the help obj for composite objects.
**
** See dtb_do_onitem_help() (in dtb_utils.c) for the version used by 
** App Builder to do its own on-item help.  (That function is the one
** apps built with App Builder will use as well.)
*/
static void
help_test_onitem_help(void)
{
    ABObj	obj, root_obj, help_obj;
    Widget 	target;
    char	*name;
    STRING 	help_vol, help_id, help_text;
    AB_TRAVERSAL    trav;

    /* Call the DtHelp routine that supports interactive on-item help. */
    if(DtHelpReturnSelectedWidgetId(AB_toplevel,(Cursor)NULL,&target)
        != DtHELP_SELECT_VALID) return;
	
    /* Convert the widget returned by DtHelp into an (sub)object */
    if( (obj = objxm_get_obj_from_widget(target)) == NULL) return;

    /* Make sure we have the root object for whatever the user selected */
    root_obj = obj_get_root(obj);

    name = abobj_get_moduled_name(root_obj);
    util_dprintf(2,"on-item help: target is %s (obj %lx, widget %lx)\n",
	name,root_obj,target);

    /* If this object has help, call the help callback on the help subobj */
    if(obj_has_help_data(obj) == True) {
	help_obj = objxm_comp_get_subobj(root_obj,AB_CFG_HELP_OBJ);
	if(help_obj != NULL) {
	    obj_get_help_data(obj,&help_vol,&help_id,&help_text);
	    util_dprintf(2,"Object help text:\n%s\n",help_text);
    	    XtCallCallbacks((Widget)help_obj->ui_handle,XmNhelpCallback,
		(XtPointer)NULL);
	}
    }
    /* 
    ** Nope, no help on this object, so wander up the object hierarchy
    ** looking for one that does have help.  If we find it, invoke it.
    */
    else {
	/* 
	** Do conditional traversal, where the condition is our obj_has_help
	** function.  Combined with TRAV_PARENTS, this means that a single
	** trav_next call will either return a parent with help or NULL.
	*/
    	trav_open_cond(&trav,obj,AB_TRAV_PARENTS,obj_has_help_data);
	root_obj = trav_next(&trav);
	trav_close(&trav);
    	if(root_obj == (ABObj)NULL) {
		return;		/* No help to display! */
	}
	else {
	    /* Found obj w/help.  Get help subobj & post help info */
	    help_obj = objxm_comp_get_subobj(root_obj,AB_CFG_HELP_OBJ);
	    if(help_obj != NULL) {
	        obj_get_help_data(obj,&help_vol,&help_id,&help_text);
	        util_dprintf(2,"Object help text:\n%s\n",help_text);
    	        XtCallCallbacks((Widget)help_obj->ui_handle,XmNhelpCallback,
		    (XtPointer)NULL);
	    }
	}
    }
}

/*
** Called when the user attempts to dismiss the Help Editor via the Motif
** window menu.
*/
static void
verify_closeCB(
    Widget	widget,
    XtPointer   client_data,
    XtPointer   call_data
)
{
    DTB_MODAL_ANSWER		answer;
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    /* 
    ** If there are pending changes for the current object, handle the
    ** implied auto-apply.
    */
    if(HelpObj != NULL && prop_changebars_pending(hes->prop_sheet) == True) 
    {
	answer = do_auto_apply(widget, HelpObj, HelpObj);
	if (answer == DTB_ANSWER_ACTION1) 
	    ui_win_show(AB_help_dialog, False, XtGrabNone);
    }
    else 
    {
	 /* Nope, no pending changes, so just dismiss the Help Editor */
	 ui_win_show(AB_help_dialog, False, XtGrabNone);
    }
}

static void
change_objecttype_palCB(
    Widget	widget,
    XtPointer   client_data,
    XtPointer   call_data
)
{
    PalEditableObjInfo	*ed_obj_info = (PalEditableObjInfo*)client_data;

    change_objtype(widget,ed_obj_info->type, ed_obj_info->subtype);
}

static void
change_objtype(
    Widget		widget,
    AB_OBJECT_TYPE	newtype,
    int			newsubtype
)
{
    DTB_MODAL_ANSWER		answer;
    int				i;
    HelpEditorSettingsRec	*hes = &Help_Editor_Settings_Rec;

    /*
    ** If there are pending changes, process the requested
    ** load in a special way that allows the user to abort it
    ** if they so choose.  
    */
    if(HelpObj != NULL && prop_changebars_pending(hes->prop_sheet) == True) 
    {
	answer = do_auto_apply(widget, HelpObj, (ABObj)NULL);
	if (answer == DTB_ANSWER_CANCEL) 
	{
	    update_object_menu_from_obj(HelpObjectMenu,HelpObj);
	    return;
	}
    }

    /*
    ** Either nothing to auto-apply, or the user said "Sure, go ahead".
    ** Because the do_auto_apply() function didn't have a new object to
    ** change to (automatically) we have to do it manually
    */

    /* First, clear out fields in Help Editor */
    clear_editor_fields();

    /* Update current-objecttype (from menu selection) & clear current object */
    HelpObj = (ABObj)NULL;
    HelpObjTypeInfo.type    = newtype;
    HelpObjTypeInfo.subtype = newsubtype;

    /* Load object list with new type */
    (void) abobj_list_load(HelpObjList,proj_get_project(),help_list_test);

    /* Select 1st item in the list (if there is one) and load its attributes */
    XtVaGetValues(HelpObjList,
    	XmNitemCount,&i,
    	NULL);
    if(i > 0) {
    	/* 
    	** This will call the item_selected callback for the list,
    	** which will update the Help Editor's fields.
    	*/
    	XmListSelectPos(HelpObjList,1,True);
    }
    return;
}

/* Update the object menu to match the type of a given object */
static void
update_object_menu_from_obj(
    Widget	menu,
    ABObj	obj
)
{
    PalItemInfo*	palitem = (PalItemInfo*)NULL;
    STRING		name;
    XmString		xmlabel;
    Widget		menu_selection;

    if(obj == (ABObj)NULL) return;

    /* Get palette item info for target object */
    if( (palitem = pal_get_item_info(obj)) == (PalItemInfo*)NULL) return;

    /* Get selection button gadget for specified option menu */
    menu_selection = XmOptionButtonGadget(menu);

    xmlabel = XmStringCreateLocalized(palitem->name);
    XtVaSetValues(menu_selection,
	XmNlabelString, xmlabel,
	NULL);
    XmStringFree(xmlabel);
}


/*
** Look around and see if there are any "toplevel" objects in existence.
** This routine is used to initialize the Help Editor when there is no
** obvious "current" object (e.g. a selected object or one from whose
** prop sheet the Help Editor was brought up).
**
** It searches for some type of window (of which there must be one if there
** are any salient objects at all), preferentially looking for a base window,
** then a popup window if there are no base windows, and then a file chooser
** if there are no base or popup windows.
*/
static ABObj
find_toplevel_obj(void)
{
    ABObj		obj, bwobj, pwobj, fcobj;
    AB_TRAVERSAL	trav;

    bwobj = pwobj = fcobj = (ABObj) NULL;

    for(trav_open(&trav,proj_get_project(),AB_TRAV_WINDOWS);
	(obj=trav_next(&trav)) != NULL; ) {

	if(obj_is_base_win(obj))  {
		bwobj = obj;
		break;
	}
	if(obj_is_popup_win(obj)    && (pwobj == (ABObj)NULL) ) pwobj = obj;
	if(obj_is_file_chooser(obj) && (fcobj == (ABObj)NULL) ) fcobj = obj;
    }
    trav_close(&trav);

    if(bwobj != (ABObj)NULL) return(bwobj);
    if(pwobj != (ABObj)NULL) return(pwobj);
    if(fcobj != (ABObj)NULL) return(fcobj);
    return((ABObj)NULL);
}
/*
 * Declarations of global widgets used by callbacks.
 */
/*
 * End declarations of global widgets
 */


/*** DTB_USER_CODE_END
 ***
 *** End of user code section
 ***
 **************************************************************************/



void 
help_okCB(
    Widget widget,
    XtPointer clientData,
    XtPointer callData
)
{
    /*** DTB_USER_CODE_START vvv Add C variables and code below vvv ***/

/*
** Callback invoked when the user hits the OK button on the Help Editor
** dialog.
*/
    HelpEditorSettingsRec       *hes = &Help_Editor_Settings_Rec;

    /* Set help attributes in object */
    if(HelpObj != NULL) update_help_attrs(HelpObj);

    /* Reset changebars */
    prop_set_changebar(hes->text.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->volume.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->locID.changebar,PROP_CB_OFF);
    prop_changebars_cleared(hes->prop_sheet);

    /* Dismiss the dialog */
    ui_win_show(AB_help_dialog, False, XtGrabNone);

    /*** DTB_USER_CODE_END   ^^^ Add C variables and code above ^^^ ***/
    
    /*** DTB_USER_CODE_START vvv Add C code below vvv ***/
    /*** DTB_USER_CODE_END   ^^^ Add C code above ^^^ ***/
}


void 
help_cancelCB(
    Widget widget,
    XtPointer clientData,
    XtPointer callData
)
{
    /*** DTB_USER_CODE_START vvv Add C variables and code below vvv ***/

    /*
    ** Callback invoked when the user hits the Cancel button on the Help Editor
    ** dialog.
    */

    clear_editor_fields();

    /* Dismiss the dialog */
    ui_win_show(AB_help_dialog, False, XtGrabNone);

    /*** DTB_USER_CODE_END   ^^^ Add C variables and code above ^^^ ***/
    
    /*** DTB_USER_CODE_START vvv Add C code below vvv ***/
    /*** DTB_USER_CODE_END   ^^^ Add C code above ^^^ ***/
}


void 
help_applyCB(
    Widget widget,
    XtPointer clientData,
    XtPointer callData
)
{
    /*** DTB_USER_CODE_START vvv Add C variables and code below vvv ***/

    /*
    ** Callback invoked when the user hits the Apply button on the Help Editor
    ** dialog.
    */

    HelpEditorSettingsRec       *hes = &Help_Editor_Settings_Rec;

    /* Set help attributes in object */
    if(HelpObj != NULL) update_help_attrs(HelpObj);

    /* Reset changebars */
    prop_set_changebar(hes->text.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->volume.changebar,PROP_CB_OFF);
    prop_set_changebar(hes->locID.changebar,PROP_CB_OFF);
    prop_changebars_cleared(hes->prop_sheet);

    /*** DTB_USER_CODE_END   ^^^ Add C variables and code above ^^^ ***/
    
    /*** DTB_USER_CODE_START vvv Add C code below vvv ***/
    /*** DTB_USER_CODE_END   ^^^ Add C code above ^^^ ***/
}


void 
help_resetCB(
    Widget widget,
    XtPointer clientData,
    XtPointer callData
)
{
    /*** DTB_USER_CODE_START vvv Add C variables and code below vvv ***/

    /*
    ** Callback invoked when the user hits the Reset button on the Help Editor
    ** dialog.
    */
    if(HelpObj != (ABObj)NULL) fetch_help_attrs(HelpObj);

    /*** DTB_USER_CODE_END   ^^^ Add C variables and code above ^^^ ***/
    
    /*** DTB_USER_CODE_START vvv Add C code below vvv ***/
    /*** DTB_USER_CODE_END   ^^^ Add C code above ^^^ ***/
}



/**************************************************************************
 *** DTB_USER_CODE_START
 ***
 *** All automatically-generated data and functions have been defined.
 ***
 *** Add new functions here, or at the top of the file.
 ***/

/*
** Callback that is added to the QuickHelpDialog widget's "Backtrack" button
** and is used to control the "More.." button.  At each step in the backtrack,
** this routine checks to see if there is help volume & location info stored
** in the dialog's helpVolume resource.  If so, then the "More..." button is
** enabled.  If not, then it is disabled.
*/
static void
help_back_hdlr(Widget widget, XtPointer clientData, XtPointer callData)
{
    String		buffer, text, vol, loc;
    char		*cp;
    Widget		more_button;
    Widget		help_dialog = (Widget)clientData;

    /* Fetch the saved volume/locationID information from the dialog widget */
    XtVaGetValues(help_dialog,
	DtNhelpVolume,&buffer,
	DtNstringData,&text,
	NULL);

    /* Get a handle to the "More..." button */
    more_button = DtHelpQuickDialogGetChild(help_dialog,
		DtHELP_QUICK_MORE_BUTTON);
    /* 
    ** Parse the combined volume/locationID string.  Disable the "More..."
    ** button if there isn't any help info, and enable it if there is.
    */
    if( buffer == 0 || (*buffer == 0) ||
	(cp=strrchr(buffer,'/')) == (char *)NULL) {
		XtSetSensitive(more_button,False);
    }
    else {
		XtSetSensitive(more_button,True);
    }
}
/*
** This callback is invoked when the user presses "More..." on the
** QuickHelpDialog.  It figures out whether a help volume entry is associated
** with the displayed help text, and if so it brings up a GeneralHelpDialog
** to display the appropriate help volume information.
*/
static void
more_help_dispatch(Widget widget, XtPointer clientData, XtPointer callData)
{
    int             	i;
    Arg             	wargs[10];
    String		buffer, vol, loc;
    char		*cp;
    static Widget	GeneralHelpDialog = (Widget) NULL;
    Widget		help_dialog = (Widget)clientData;
    Widget		more_button;

    /* Fetch the saved volume/locationID information from the dialog widget */
    XtVaGetValues(help_dialog,
	DtNhelpVolume,&buffer,
	NULL);

    /* 
    ** Parse the combined volume/locationID string.  If that fails there
    ** must be no data, so don't bother displaying the GeneralHelpDialog.
    ** (We shouldn't be in this callback routine if that happens, though...)
    */
    if( (cp=strrchr(buffer,'/')) != (char *)NULL) {
	*cp++ = 0;
	vol = buffer;
	loc = cp; 
    } else {
	/* No slash found, give up */
	return;
    }

    if(GeneralHelpDialog == (Widget)NULL) {
	/* Create General Help Dialog */
        i = 0;
	XtSetArg(wargs[i],XmNtitle, "Application Help");        i++;
	XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_TOPIC);      i++;
	XtSetArg(wargs[i],DtNhelpVolume, vol);			i++;
	XtSetArg(wargs[i],DtNlocationId,loc);			i++;
        XtSetArg(wargs[i],DtNcolumns,100);                  	i++;
        XtSetArg(wargs[i],DtNrows,20);                      	i++;

	GeneralHelpDialog = DtCreateHelpDialog(dtb_get_toplevel_widget(),
		"GeneralHelp",wargs,i);
    }
    else {
        i = 0;
        XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_TOPIC);  	i++;
        XtSetArg(wargs[i],DtNhelpVolume,vol);			i++;
        XtSetArg(wargs[i],DtNlocationId,loc);			i++;
        XtSetValues(GeneralHelpDialog,wargs,i);
    }

    /* Now take down the quick help dialog and display the full help one */
    XtManageChild(GeneralHelpDialog);
    XtUnmanageChild(help_dialog);
}

/*** DTB_USER_CODE_END
 ***
 *** End of user code section
 ***
 **************************************************************************/


