/* $TOG: ComboBox.c /main/16 1999/07/28 10:59:19 vipin $ */
/*
 *  @OPENGROUP_COPYRIGHT@
 *  COPYRIGHT NOTICE
 *  Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc.
 *  Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
 *  ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for
 *  the full copyright text.
 *  
 *  This software is subject to an open license. It may only be
 *  used on, with or for operating systems which are themselves open
 *  source systems. You must contact The Open Group for a license
 *  allowing distribution and sublicensing of this software on, with,
 *  or for operating systems which are not Open Source programs.
 *  
 *  See http://www.opengroup.org/openmotif/license for full
 *  details of the license agreement. Any use, reproduction, or
 *  distribution of the program constitutes recipient's acceptance of
 *  this agreement.
 *  
 *  EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
 *  PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
 *  WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
 *  OR FITNESS FOR A PARTICULAR PURPOSE
 *  
 *  EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
 *  NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
 *  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
 *  EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGES.
 */
/*
 * HISTORY
 */

/* ComboBox.c */

#include <stdio.h>
#include <string.h>
#include <Xm/XmP.h>
#include <Xm/XmosP.h>		/* for bzero et al */
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <Xm/AccTextT.h>
#include <Xm/ArrowB.h>
#include <Xm/ComboBoxP.h>
#include <Xm/DisplayP.h>
#include <Xm/DrawP.h>
#include <Xm/GrabShellP.h>
#include <Xm/List.h>
#include <Xm/TextF.h>
#include <Xm/TraitP.h>
#include <Xm/TransltnsP.h>
#include <Xm/VendorS.h>
#include "GeoUtilsI.h"
#include "MenuShellI.h"
#include "MessagesI.h"
#include "RepTypeI.h"
#include "TraversalI.h"
#include "UniqueEvnI.h"
#include "XmI.h"
#include "XmStringI.h"

#define	NOKIDS		_XmMMsgComboBox_0000
#define	TYPEWARNING	_XmMMsgComboBox_0001
#define	MISSINGKID	_XmMMsgComboBox_0004
#define	UNMANAGEDKID	_XmMMsgComboBox_0005
#define	MBWARNING	_XmMMsgComboBox_0006
#define	WRONGPARAMS	_XmMMsgComboBox_0007
#define	WRONGWIDGET	_XmMMsgComboBox_0008
#define SELECTBADITEM	_XmMMsgComboBox_0009
#define SETBADITEM	_XmMMsgComboBox_0010
#define DELETEBADPOS	_XmMMsgComboBox_0011
#define NOTACOMBOBOX	_XmMMsgComboBox_0012
#define NOSETKIDS	_XmMMsgComboBox_0013
#define MODEWARNING	_XmMMsgComboBox_0014

#define default_translations	_XmComboBox_defaultTranslations

#define	LIST_CHILD_NAME		"List"
#define	TEXT_CHILD_NAME		"Text"
#define	SHELL_CHILD_NAME	"GrabShell"

/* From GrabShell.c */
#define Events	(EnterWindowMask | LeaveWindowMask | ButtonPressMask | \
		 ButtonReleaseMask)

/* CB_ShellState() values. */
enum { POPPED_DOWN, POPPING_UP, POPPED_UP, POPPING_DOWN };

/********    Static Function Declarations    ********/

/** Widget Class Methods **/
static void Initialize (Widget, Widget, ArgList, Cardinal *);
static void ClassInitialize (void);
static void ClassPartInitialize (WidgetClass wc);
static Boolean SetValues (Widget, Widget, Widget, ArgList, Cardinal *);
static XtGeometryResult GeometryManager (Widget,
					 XtWidgetGeometry *,
					 XtWidgetGeometry *);
static void InsertChild (Widget child);
static void Resize (Widget widget);
static void Redisplay (Widget widget, XEvent *event, Region region);
static XtGeometryResult QueryGeometry (Widget,
				       XtWidgetGeometry *,
				       XtWidgetGeometry *);
static void ChangeManaged (Widget widget);
static void Destroy (Widget widget);
static void ConstraintDestroy (Widget widget);
static Boolean ComboBoxParentProcess (Widget wid, XmParentProcessData event);

/** Event Handlers **/
static void PopupEH (Widget, XtPointer, XEvent *, Boolean *);
static void SBBtnDownEH (Widget, XtPointer, XEvent *, Boolean *);
static void SBBtnUpEH (Widget, XtPointer, XEvent *, Boolean *);

/** Callbacks **/
static void TextChangedCB (Widget, XtPointer, XtPointer);
static void ListSelectionCB (Widget, XtPointer, XtPointer);
static void ShellPopupCB (Widget, XtPointer, XtPointer);
static void ShellPopdownCB (Widget, XtPointer, XtPointer);
static void FocusMovedCB (Widget, XtPointer, XtPointer);

/** Action Routines **/
static void CBArmAndDropDownList (Widget, XEvent *, String *, Cardinal *);
static void CBDisarm (Widget, XEvent *, String *, Cardinal *);
static void CBDropDownList (Widget, XEvent *, String *, Cardinal *);
static void CBFocusIn (Widget, XEvent *, String *, Cardinal *);
static void CBFocusOut (Widget, XEvent *, String *, Cardinal *);
static void CBCancel (Widget, XEvent *, String *, Cardinal *);
static void CBActivate (Widget, XEvent *, String *, Cardinal *);
static void CBListAction (Widget, XEvent *, String *, Cardinal *);
static void CBTextFocusOut (Widget, XEvent *, String *, Cardinal *);

/** Default resource value call procs. **/ 
static void CheckSetRenderTable (Widget, int, XrmValue *);

/** Synthetic Resource access methods **/
static XmImportOperator CBSetSelectedItem (Widget, int, XtArgVal *);
static XmImportOperator CBSetSelectedPos (Widget, int, XtArgVal *);
static void CBGetSelectedItem (Widget, int, XtArgVal *);
static void CBGetSelectedPos (Widget, int, XtArgVal *);
static void CBGetColumns (Widget, int, XtArgVal *);
static void CBGetItems (Widget, int, XtArgVal *);
static void CBGetItemCount (Widget, int, XtArgVal *);
static void CBGetVisibleItemCount (Widget, int, XtArgVal *);

/** Additional Functions **/
static Boolean PopdownList (Widget cb, XEvent *event);
static void CreateChildren (Widget, ArgList, Cardinal *);
static Widget CreateEditBox (Widget, String, Widget, ArgList, Cardinal *);
static Widget CreateScrolledList (Widget, String, Widget, ArgList, Cardinal *);
static Widget CreatePulldown (Widget, String, Widget, ArgList, Cardinal *);
static void ComputeSize (Widget, Dimension, Dimension, Dimension*, Dimension*);
static void DoLayout (Widget);
static void GetThickness (Widget, Dimension *, Dimension *);
static void GetArrowGC (Widget);
static void DrawArrow (Widget, Boolean);
static void DrawShadows (Widget);
static Boolean Hit (XButtonEvent*, XRectangle);
static void HighlightBorder (Widget w);
static void UnhighlightBorder (Widget w);
static XmComboBoxWidget FindComboBox (Widget w);
static void CallSelectionCallbacks (Widget w, XEvent *event);
static void SetHitArea (Widget w);
static Boolean ReduceResources (Widget widget, Dimension *width,
				Dimension *height, Mask flags);
static int Reduce (Dimension *size, int max_change, int min_size);
static void GetIdealTextSize (Widget cb, int *width, int *height);
static Dimension GetDefaultArrowSize (Widget cb);
static XmString GetEditBoxValue (Widget cb);
static void SetEditBoxValue (Widget cb, XmString value);

/********    End Static Function Declarations    ********/


#define POPUP_EVENT_MASK		\
	(EnterWindowMask | ButtonPressMask | ButtonReleaseMask)

#define	MINIMUM_SHADOWTHICKNESS		1
#define	MINIMUM_HIGHLIGHTTHICKNESS	2
#define	MINIMUM_ARROWSPACE		1
#define MINTXT				1
#define MINLIST				1

#define	DEFAULT_MARGINWIDTH 		2
#define	DEFAULT_MARGINHEIGHT 		2

#define	HIGHLIGHT_THICKNESS		((Mask) (1<<0))
#define	SHADOW_THICKNESS		((Mask) (1<<1))
#define	ARROW_SPACING			((Mask) (1<<2))
#define	MARGIN_WIDTH			((Mask) (1<<3))
#define	MARGIN_HEIGHT			((Mask) (1<<4))
#define	ARROW_SIZE			((Mask) (1<<5))

#define DEFAULT_ARROW_SCALING	0.75
#define	SQRT3_OVER_2		0.8660254037844

#define XmUNSPECIFIED_COLUMNS		((short) XmUNSPECIFIED_COUNT)
#define	XmUNSPECIFIED_ITEMS		((XmStringTable) XmUNSPECIFIED)


/*
 * These instance fields have special non-obvious meanings, and are
 * used for passing synthetic resource values to and from children.
 * The value of these fields is NOT maintained -- always query the
 * child for the current value.
 */

#define CBS_Columns(w)	     (((XmComboBoxWidget)(w))->combo_box.columns)
#define CBS_ItemCount(w)     (((XmComboBoxWidget)(w))->combo_box.item_count)
#define CBS_Items(w) 	     (((XmComboBoxWidget)(w))->combo_box.items)
#define CBS_SelectedItem(w)  (((XmComboBoxWidget)(w))->combo_box.selected_item)
#define CBS_SelectedPosition(w)		\
	(((XmComboBoxWidget)(w))->combo_box.selected_position)
#define CBS_VisibleItemCount(w)		\
	(((XmComboBoxWidget)(w))->combo_box.visible_item_count)


#define	Offset(field) XtOffsetOf(XmComboBoxRec, field)

static XtResource resources[] =
{
  {
    XmNarrowSize, XmCArrowSize, XmRHorizontalDimension,
    sizeof(Dimension), Offset(combo_box.arrow_size),
    XmRImmediate, (XtPointer)XmINVALID_DIMENSION
  },
  {
    XmNarrowSpacing, XmCArrowSpacing, XmRHorizontalDimension,
    sizeof(Dimension), Offset(combo_box.arrow_spacing),
    XmRImmediate, (XtPointer)XmINVALID_DIMENSION
  },
  {
    XmNhighlightThickness, XmCHighlightThickness, XmRHorizontalDimension,
    sizeof(Dimension), Offset(combo_box.highlight_thickness),
    XmRCallProc, (XtPointer) _XmSetThickness
  }, 
  {
    XmNmarginHeight, XmCMarginHeight, XmRVerticalDimension,
    sizeof(Dimension), Offset(combo_box.margin_height),
    XmRImmediate, (XtPointer)DEFAULT_MARGINHEIGHT
  },
  {
    XmNmarginWidth, XmCMarginWidth, XmRHorizontalDimension,
    sizeof(Dimension), Offset(combo_box.margin_width),
    XmRImmediate, (XtPointer)DEFAULT_MARGINWIDTH
  },
  {
    XmNmatchBehavior, XmCMatchBehavior, XmRMatchBehavior,
    sizeof(unsigned char), Offset(combo_box.match_behavior),
    XmRImmediate, (XtPointer)XmINVALID_MATCH_BEHAVIOR
  },
  {
    XmNnavigationType, XmCNavigationType, XmRNavigationType,
    sizeof(unsigned char), Offset(manager.navigation_type),
    XmRImmediate, (XtPointer)XmSTICKY_TAB_GROUP
  },
  {
    XmNselectionCallback, XmCCallback, XmRCallback,
    sizeof(XtCallbackList), Offset(combo_box.selection_callback),
    XmRCallback, NULL
  },
  {
    XmNselectedItem, XmCSelectedItem, XmRXmString,
    sizeof(XmString), Offset(combo_box.selected_item),
    XmRImmediate, (XtPointer)NULL
  },
  {
    XmNselectedPosition, XmCSelectedPosition, XmRInt,
    sizeof(int), Offset(combo_box.selected_position),
    XmRImmediate, (XtPointer)XmINVALID_POSITION
  },
  {
    XmNshadowThickness, XmCShadowThickness, XmRHorizontalDimension,
    sizeof(Dimension), Offset(manager.shadow_thickness),
    XmRCallProc, (XtPointer) _XmSetThickness
  },
  {
    XmNcomboBoxType, XmCComboBoxType, XmRComboBoxType,
    sizeof(unsigned char), Offset(combo_box.type),
    XtRImmediate, (XtPointer)XmCOMBO_BOX
  },
/* Borrow the "text_changed" instance field .. ! */
  {       
    "pri.vate", "Pri.vate", XmRBoolean,
    sizeof(Boolean), Offset(combo_box.text_changed),
    XmRImmediate, (XtPointer) False
  },
  {
    XmNfontList, XmCFontList, XmRFontList,
    sizeof(XmRenderTable), Offset(combo_box.render_table),
    XmRCallProc, (XtPointer)CheckSetRenderTable
  },
  {
    XmNrenderTable, XmCRenderTable, XmRRenderTable,
    sizeof(XmRenderTable), Offset(combo_box.render_table),
    XmRCallProc, (XtPointer)CheckSetRenderTable
  },
  {
    XmNlist, XmCList, XmRWidget,
    sizeof(Widget), Offset(combo_box.list),
    XmRImmediate, (XtPointer)NULL
  },
  {
    XmNtextField, XmCTextField, XmRWidget,
    sizeof(Widget), Offset(combo_box.edit_box),
    XmRImmediate, (XtPointer)NULL
  },
  {
    XmNitems, XmCItems, XmRXmStringTable,
    sizeof(XmStringTable), Offset(combo_box.items),
    XmRImmediate, (XtPointer)XmUNSPECIFIED_ITEMS
  },
  {
    XmNitemCount, XmCItemCount, XmRInt,
    sizeof(int), Offset(combo_box.item_count),
    XmRImmediate, (XtPointer)XmUNSPECIFIED_COUNT
  },
  {
    XmNvisibleItemCount, XmCVisibleItemCount, XmRInt,
    sizeof(int), Offset(combo_box.visible_item_count),
    XmRImmediate, (XtPointer)10
  },
  {
    XmNcolumns, XmCColumns, XmRShort,
    sizeof(short), Offset(combo_box.columns),
    XmRImmediate, (XtPointer)XmUNSPECIFIED_COLUMNS
  },
  {
    XmNpositionMode, XmCPositionMode, XmRPositionMode,
    sizeof(XtEnum), Offset(combo_box.position_mode),
    XmRImmediate, (XtPointer)XmZERO_BASED
  }
};

/*
 * Resolution independent resources and resources needing special processing.
 */

static XmSyntheticResource syn_resources[] =
{
  {
    XmNselectedItem, sizeof(XmString), Offset(combo_box.selected_item),
    CBGetSelectedItem, CBSetSelectedItem
  },
  {
    XmNselectedPosition, sizeof(int), Offset(combo_box.selected_position),
    CBGetSelectedPos, CBSetSelectedPos
  },
  {
    XmNmarginWidth, sizeof(Dimension), Offset(combo_box.margin_width),
    XmeFromHorizontalPixels, XmeToHorizontalPixels
  },
  {
    XmNmarginHeight, sizeof(Dimension), Offset(combo_box.margin_height),
    XmeFromVerticalPixels, XmeToVerticalPixels
  },
  {
    XmNhighlightThickness, sizeof(Dimension),
    Offset(combo_box.highlight_thickness),
    XmeFromHorizontalPixels, XmeToHorizontalPixels
  },
  {
    XmNshadowThickness, sizeof(Dimension), Offset(manager.shadow_thickness),
    XmeFromHorizontalPixels, XmeToHorizontalPixels
  },
  {
    XmNarrowSize, sizeof(Dimension), Offset(combo_box.arrow_size),
    XmeFromHorizontalPixels, XmeToHorizontalPixels
  },
  {
    XmNarrowSpacing, sizeof(Dimension), Offset(combo_box.arrow_spacing),
    XmeFromHorizontalPixels, XmeToHorizontalPixels
  },
  {
    XmNcolumns, sizeof(short), Offset(combo_box.columns), 
    CBGetColumns, NULL
  }, 
  {
    XmNitems, sizeof(XmStringTable), Offset(combo_box.items),
    CBGetItems, NULL
  },                                        
  {
    XmNitemCount, sizeof(int), Offset(combo_box.item_count), 
    CBGetItemCount, NULL
  }, 
  {
    XmNvisibleItemCount, sizeof(int), Offset(combo_box.visible_item_count), 
    CBGetVisibleItemCount, NULL
  }
};
#undef Offset

static XtAccelerators parsed_accelerators;
static XtAccelerators parsed_list_accelerators;
static XtTranslations parsed_list_translations;
static XtTranslations parsed_text_focus_translations;

static XtActionsRec actionsList[] = {
  { "CBArmAndDropDownList",	CBArmAndDropDownList	},
  { "CBDisarm",			CBDisarm		},
  { "CBDropDownList",     	CBDropDownList		},
  { "CBFocusIn",		CBFocusIn		},
  { "CBFocusOut",		CBFocusOut		},
  { "CBCancel",		 	CBCancel		},
  { "CBActivate",		CBActivate		},
  { "CBListAction",		CBListAction		},
  { "CBTextFocusOut",		CBTextFocusOut		}
};

/* Class Record Initialization */
externaldef(xmcomboboxclassrec) XmComboBoxClassRec xmComboBoxClassRec =
{
  {
    /* core_class fields */
    (WidgetClass) &xmManagerClassRec,	/* superclass             */
    "XmComboBox",                       /* class_name             */
    sizeof(XmComboBoxRec),              /* widget_size            */
    ClassInitialize,			/* class_initialize       */
    ClassPartInitialize,                /* class_part_initialize  */
    FALSE,                              /* class_inited           */
    Initialize,             		/* initialize             */
    NULL,                               /* initialize_hook        */
    XtInheritRealize,                   /* realize                */
    actionsList,                  	/* actions                */
    XtNumber(actionsList),		/* num_actions            */
    (XtResourceList)resources,          /* resources              */
    XtNumber(resources),                /* num_resources          */
    NULLQUARK,                          /* xrm_class              */
    True,                               /* compress_motion        */
    XtExposeCompressMaximal | 		/* compress_exposure      */
        XtExposeNoRegion,
    FALSE,                              /* compress_enterleave    */
    FALSE,                              /* visible_interest       */
    Destroy,                   		/* destroy                */
    Resize,     			/* resize                 */
    Redisplay,    			/* expose                 */
    SetValues,         			/* set_values             */
    NULL,                               /* set_values_hook        */
    XtInheritSetValuesAlmost,           /* set_values_almost      */
    NULL,                      		/* get_values_hook        */
    NULL,               		/* accept_focus           */
    XtVersion,				/* version                */
    NULL,                               /* callback_private       */
    default_translations,		/* tm_table               */
    QueryGeometry,   			/* Query Geometry proc    */
    NULL,                               /* disp accelerator       */
    NULL                                /* extension              */
  },
  {
    /* composite_class fields */
    GeometryManager, 			/* geometry_manager       */
    ChangeManaged,   			/* change_managed         */
    InsertChild,              		/* insert_child           */
    XtInheritDeleteChild,	        /* delete_child           */
    NULL                     		/* extension              */
  },
  {
    /* constraint_class fields */
    NULL,				/* constraint resource    */
    0,        				/* number of constraints  */
    sizeof(XmManagerConstraintRec),	/* size of constraint     */
    NULL,				/* initialization         */
    ConstraintDestroy,			/* destroy proc           */
    NULL,				/* set_values proc        */
    NULL				/* extension              */
  },
  {
    /* manager_class fields */
    XtInheritTranslations,		/* translations           */
    syn_resources,			/* syn_resources          */
    XtNumber(syn_resources),		/* num_syn_resources      */
    NULL,				/* syn_cont_resources     */
    0,					/* num_syn_cont_resources */
    ComboBoxParentProcess,		/* parent_process         */
    NULL				/* extension              */
  },
  {
    /* combo_box class fields */
    NULL                             	/* extension		  */
  }
};


externaldef(xmcomboboxwidgetclass) WidgetClass xmComboBoxWidgetClass =
       (WidgetClass) &xmComboBoxClassRec;


/* ------------- WIDGET CLASS METHODS ---------- */


/*
 * Initialize()
 *	Called by the Intrinsics when a new ComboBox is created.
 */

/*ARGSUSED*/
static void
Initialize(Widget    request,	/* unused */
	   Widget    new_w,
	   ArgList   args,
	   Cardinal *num_args)
{
  XmComboBoxWidget newcb = (XmComboBoxWidget)new_w;
  Widget	   ancestor;

  /* Setup internal state. */
  CB_ShellState(newcb) = POPPED_DOWN;
  newcb->combo_box.scrolling = FALSE;
  newcb->combo_box.vsb = NULL;
  newcb->combo_box.hsb = NULL;
  CB_TextChanged(newcb) = FALSE;

  /* Set defaults */
  CB_Highlighted(newcb) = FALSE;
  CB_ArrowPressed(newcb) = FALSE;
  CB_ScrolledW(newcb) = CB_ListShell(newcb) = 0;
  newcb->combo_box.ideal_ebwidth = newcb->combo_box.ideal_ebheight = 0;
  CB_HitRect(newcb).width = 0;
  CB_HitRect(newcb).height = 0;
  CB_HitRect(newcb).x = 0;
  CB_HitRect(newcb).y = 0;

  newcb->combo_box.arrow_shadow_width = newcb->manager.shadow_thickness;

  if (newcb->core.accelerators == NULL)
    newcb->core.accelerators = parsed_accelerators;

  /* Validate XmNpositionMode. */
  if (!XmRepTypeValidValue(XmRID_POSITION_MODE,
			   CB_PositionMode(newcb), (Widget) newcb))
    {
      CB_PositionMode(newcb) = XmZERO_BASED;
    }

  /* The list child cannot be set. */
  if (CB_List(newcb))
    {
      CB_List(newcb) = NULL;
      XmeWarning(new_w, NOSETKIDS);
    }

  /* The editbox child cannot be set. */
  if (CB_EditBox(newcb))
    {
      CB_EditBox(newcb) = NULL;
      XmeWarning(new_w, NOSETKIDS);
    }

  /* Get arrow GC. */
  GetArrowGC((Widget) newcb);

  if (CB_ArrowSpacing(newcb) == XmINVALID_DIMENSION)
    {
      CB_ArrowSpacing(newcb) = CB_MarginWidth(newcb);
      /* ArrowSize set dynamically in DoLayout() */
    }

  if (CB_MatchBehavior(newcb) == XmINVALID_MATCH_BEHAVIOR)
    {
      if (CB_Type(newcb) == XmDROP_DOWN_LIST)
	CB_MatchBehavior(newcb) = XmQUICK_NAVIGATE;
      else
	CB_MatchBehavior(newcb) = XmNONE;
    }
  else if ((CB_MatchBehavior(newcb) == XmQUICK_NAVIGATE) &&
	   (CB_Type(newcb) != XmDROP_DOWN_LIST))
    {
      CB_MatchBehavior(newcb) = XmNONE;
      XmeWarning(new_w, MBWARNING);
    }

  /* Get a real render table. */
  if (CB_RenderTable(newcb) == NULL)
    CB_RenderTable(newcb) = XmeGetDefaultRenderTable(new_w, XmTEXT_FONTLIST);
  CB_RenderTable(newcb) = XmFontListCopy(CB_RenderTable(newcb));

  /* Create the widgets that make up a ComboBox. */
  CreateChildren(new_w, args, num_args);

  /* Walk up hierarchy to find vendor shell. */
  ancestor = XtParent(new_w);
  while (ancestor && !XmIsVendorShell(ancestor))
    ancestor = XtParent(ancestor);

  /* Setup focus moved callback so ComboBox can draw highlighting rect. */
  if (ancestor)
    XmeAddFocusChangeCallback(ancestor, FocusMovedCB, (XtPointer) new_w);

  /* Since we cannot add children to a ComboBox, we can consider it
     as a Primitive, and set its preferred width/height. */
  if (XtWidth(new_w) == 0 || XtHeight(new_w) == 0)
    ComputeSize((Widget) new_w, 0, 0, &(XtWidth(new_w)), &(XtHeight(new_w)));

  /* Reset synthetic child resources so SetValues can detect all changes. */
  CBS_Columns(newcb) = XmUNSPECIFIED_COLUMNS;
  CBS_ItemCount(newcb) = XmUNSPECIFIED_COUNT;
  CBS_Items(newcb) = XmUNSPECIFIED_ITEMS;
  CBS_VisibleItemCount(newcb) = XmUNSPECIFIED_COUNT;

  /* Reset fields whose value we ignore after creation. */
  CBS_SelectedPosition(newcb) = XmINVALID_POSITION;
  CBS_SelectedItem(newcb) = NULL;
}

/*
 * ClassInitialize()
 *	This routine is invoked only once.
 */

static void
ClassInitialize(void)
{
  /* Parse the default translation and accelerator tables */
  parsed_accelerators =
    XtParseAcceleratorTable(_XmComboBox_defaultAccelerators);
  parsed_list_accelerators =
    XtParseAcceleratorTable(_XmComboBox_dropDownComboBoxAccelerators);
  parsed_list_translations =
    XtParseTranslationTable(_XmComboBox_dropDownListTranslations);
  parsed_text_focus_translations =
    XtParseTranslationTable(_XmComboBox_textFocusTranslations);
}

/*
 * ClassPartInitialize()
 *	Set up the fast subclassing.  Invoked for every (sub)class of
 * ComboBox. 
 */
static void
ClassPartInitialize(WidgetClass wc)
{
  _XmFastSubclassInit (wc, XmCOMBO_BOX_BIT);
}

/*
 * InsertChild()
 *	Called by the intrinsics when a new child is added to a ComboBox.
 * Generate an error message if an application tries to insert widgets
 * into the ComboBox. 
 */

static void
InsertChild(Widget child)
{
  XmComboBoxWidget cb = (XmComboBoxWidget) XtParent(child);
  XtWidgetProc insert_child;

  /*
   * If the ComboBox has already created its children, the application
   * is trying to add another child, which is an error.
   */
  if (cb->composite.num_children > 2)
    XmeWarning((Widget)cb, NOKIDS);

  else {
    /* Call manager InsertChild to update child info. */
  _XmProcessLock();
  insert_child = ((XmManagerWidgetClass) xmManagerWidgetClass)
			->composite_class.insert_child;
  _XmProcessUnlock();
  (*insert_child)(child);
  }
}


/*
 * SetValues()
 *	Called by Intrinsics when XtSetValues is called on the ComboBox.
 */

/*ARGSUSED*/
static Boolean
SetValues(Widget    current,
	  Widget    request,	/* unused */
	  Widget    new_w,
	  ArgList   user_args,	/* unused */
	  Cardinal *num_args)	/* unused */
{
  XmComboBoxWidget curcb = (XmComboBoxWidget)current;
  XmComboBoxWidget newcb = (XmComboBoxWidget)new_w;
  Boolean	   resize = FALSE;
  Boolean	   dolayout = FALSE;
  Boolean	   redisplay = FALSE;
  Cardinal	   nlist = 0;
  Arg		   list_args[10];
  Cardinal	   nshell = 0;
  Arg		   shell_args[10];
  Cardinal	   neditbox = 0;
  Arg		   editbox_args[10];
  XmFontList	   old_render_table = NULL;

  /* The position_mode cannot be changed after creation. */
  if (CB_PositionMode(curcb) != CB_PositionMode(newcb))
    {
      CB_PositionMode(newcb) = CB_PositionMode(curcb);
      XmeWarning(current, MODEWARNING);
    }

  /* The ComboBox type cannot change after creation. */
  if (CB_Type(curcb) != CB_Type(newcb))
    {
      CB_Type(newcb) = CB_Type(curcb);
      XmeWarning(current, TYPEWARNING);
    }

  /* The list child cannot be set. */
  if (CB_List(curcb) != CB_List(newcb))
    {
      CB_List(newcb) = CB_List(curcb);
      XmeWarning(current, NOSETKIDS);
    }

  /* The editbox child cannot be set. */
  if (CB_EditBox(curcb) != CB_EditBox(newcb))
    {
      CB_EditBox(newcb) = CB_EditBox(curcb);
      XmeWarning(current, NOSETKIDS);
    }

  /* Validate XmNmatchBehavior and propagate it to the list. */
  if (CB_MatchBehavior(curcb) != CB_MatchBehavior(newcb))
    {
      if (CB_Type(curcb) != XmDROP_DOWN_LIST &&
	  CB_MatchBehavior(newcb) == XmQUICK_NAVIGATE)
	{
	  CB_MatchBehavior(newcb) = CB_MatchBehavior(curcb);
	  XmeWarning(current, MBWARNING);
	}
      else if (CB_Type(curcb) == XmDROP_DOWN_LIST)
	{
	  XtSetArg(list_args[nlist], XmNmatchBehavior, CB_MatchBehavior(newcb)),
	    nlist++;
	}
    }

  /* Propagate XmNcolumns to the edit box. */
  if (CBS_Columns(newcb) != XmUNSPECIFIED_COLUMNS)
    {
      XtSetArg(editbox_args[neditbox], XmNcolumns, CBS_Columns(newcb)), 
        neditbox++;
      CBS_Columns(newcb) = XmUNSPECIFIED_COLUMNS;
      resize = dolayout = redisplay = TRUE;
    }

  /* Propagate XmNitems to the list. */
  if (CBS_Items(newcb) != XmUNSPECIFIED_ITEMS)
    {
      XtSetArg(list_args[nlist], XmNitems, CBS_Items(newcb)), nlist++;
      CBS_Items(newcb) = XmUNSPECIFIED_ITEMS;
    }

  /* Propagate XmNitemCount to the list. */
  if (CBS_ItemCount(newcb) != XmUNSPECIFIED_COUNT)
    {
      XtSetArg(list_args[nlist], XmNitemCount, CBS_ItemCount(newcb)), nlist++;
      CBS_ItemCount(newcb) = XmUNSPECIFIED_COUNT;
    }

  /* Propagate XmNvisibleItemCount to the list. */
  if (CBS_VisibleItemCount(newcb) != XmUNSPECIFIED_COUNT)
    {
      XtSetArg(list_args[nlist], XmNvisibleItemCount, 
	       CBS_VisibleItemCount(newcb)), nlist++;
      CBS_VisibleItemCount(newcb) = XmUNSPECIFIED_COUNT;
      resize = dolayout = redisplay = TRUE;
    }

  /* Propagate our XmNborderWidth to the popup list shell. */
  if (XtBorderWidth(curcb) != XtBorderWidth(newcb))
    {
      if (CB_Type(curcb) != XmCOMBO_BOX)
	{
	  XtSetArg(shell_args[nshell], XmNborderWidth, XtBorderWidth(newcb)), 
	    nshell++;

	  redisplay = TRUE;
	}
    }

  /* Propagate XmNrenderTable to all children. */
  if (CB_RenderTable(curcb) != CB_RenderTable(newcb))
    {
      if (CB_RenderTable(newcb) == NULL)
	CB_RenderTable(newcb) = XmeGetDefaultRenderTable(new_w, 
							 XmTEXT_FONTLIST);
      CB_RenderTable(newcb) = XmFontListCopy(CB_RenderTable(newcb));

      XtSetArg(editbox_args[neditbox], XmNrenderTable, CB_RenderTable(newcb)), 
        neditbox++;
      XtSetArg(list_args[nlist], XmNrenderTable, CB_RenderTable(newcb)), 
        nlist++;

      /* Free the old render table after we update the children. */
      old_render_table = CB_RenderTable(curcb);
    }

  /* Process resources that affect layout. */
  if ((CB_HighlightThickness(curcb) != CB_HighlightThickness(newcb)) ||
      (CB_MarginWidth(curcb) != CB_MarginWidth(newcb)) ||
      (CB_MarginHeight(curcb) != CB_MarginHeight(newcb)) ||
      (CB_RenderTable(curcb) != CB_RenderTable(newcb)))
    {
      resize = dolayout = redisplay = TRUE;
    }

  if (MGR_ShadowThickness(curcb) != MGR_ShadowThickness(newcb))
    {
      resize = dolayout = redisplay = TRUE;

      if (CB_Type(newcb) != XmDROP_DOWN_LIST)
	{
	  XtSetArg(editbox_args[neditbox], XmNshadowThickness, 
		   MGR_ShadowThickness(newcb)), neditbox++;
	}

      if (CB_Type(curcb) != XmCOMBO_BOX)
	{
	  XtSetArg(shell_args[nshell], XmNshadowThickness, 
		   MGR_ShadowThickness(newcb)), nshell++;
	}
    }

  if ((CB_ArrowSpacing(curcb) != CB_ArrowSpacing(newcb)) ||
      (CB_ArrowSize(curcb) != CB_ArrowSize(newcb)))
    {
      if (CB_Type(curcb) != XmCOMBO_BOX)
	{
	  resize = dolayout = redisplay = TRUE;
	}
    }

  /* Propagate shadow GC changes to the popup list shell. */
  if ((curcb->manager.top_shadow_color !=
       newcb->manager.top_shadow_color) ||
      (curcb->manager.top_shadow_pixmap !=
       newcb->manager.top_shadow_pixmap) ||
      (curcb->manager.bottom_shadow_color !=
       newcb->manager.bottom_shadow_color) ||
      (curcb->manager.bottom_shadow_pixmap !=
       newcb->manager.bottom_shadow_pixmap))
    {
      if (CB_Type(curcb) != XmCOMBO_BOX)
	{
	  XtSetArg(shell_args[nshell], XmNtopShadowColor,
		   newcb->manager.top_shadow_color), nshell++;
	  XtSetArg(shell_args[nshell], XmNbottomShadowColor,
		   newcb->manager.bottom_shadow_color), nshell++;
	  XtSetArg(shell_args[nshell], XmNtopShadowPixmap,
		   newcb->manager.top_shadow_pixmap), nshell++;
	  XtSetArg(shell_args[nshell], XmNbottomShadowPixmap,
		   newcb->manager.bottom_shadow_pixmap), nshell++;
	}
    }

  if (XtBackground(curcb) != XtBackground(newcb))
    {
      if (CB_Type(newcb) != XmCOMBO_BOX)
	{
	  XtReleaseGC((Widget) newcb, newcb->combo_box.arrow_GC);
	  GetArrowGC((Widget) newcb);

	  /* Core won't request redisplay if a background pixmap is used. */
	  redisplay = TRUE;
	}
    }

  if (XtIsSensitive((Widget)curcb) != XtIsSensitive((Widget)newcb))
    {
      redisplay = TRUE;
    }


  /* Push all the accrued updates onto the children. */
  assert(nshell <= XtNumber(shell_args));
  if (nshell && CB_ListShell(newcb))
    XtSetValues(CB_ListShell(newcb), shell_args, nshell);

  assert(nlist <= XtNumber(list_args));
  if (nlist && CB_List(newcb))
    XtSetValues(CB_List(newcb), list_args, nlist);

  assert(neditbox <= XtNumber(editbox_args));
  if (neditbox && CB_EditBox(newcb))
    XtSetValues(CB_EditBox(newcb), editbox_args, neditbox);

  if (old_render_table)
	XmFontListFree(old_render_table);


  /* Recompute our ideal size. */


  if ( resize)
    {
      if (XtIsRealized((Widget)newcb))
          XtWidth(newcb) = XtHeight(newcb) = 0;

      ComputeSize((Widget)newcb, 0, 0, &(XtWidth(newcb)), &(XtHeight(newcb)));
    }

  /*
   * Recompute our layout, if realized. 
   * We could check and not resize and add a SetValuesAlmost (like the
   * one in Label) that would call Resize if the parent denies the request.
   */
  if (XtIsRealized((Widget)new_w) && dolayout)
    DoLayout(new_w);

  return redisplay;
}

/* ReduceResources()
 *	Called from ChangeManaged when some resources need to be
 * diminished in order to satisfy a resource request.
 */

/*ARGSUSED*/
static Boolean
ReduceResources(Widget     widget,
		Dimension *width,
		Dimension *height,
		Mask	   flags)
{
  XmComboBoxWidget newcb = (XmComboBoxWidget)widget;
  int delta;

  /* Space is stolen from resources in this order:
   *	MarginHeight, MarginWidth, ArrowSpacing, 
   *	ShadowThickness, HighlightThickness
   */

  /* Reduce width.  Try resources in order. */
  if (*width)
    {
      if (!(flags & MARGIN_WIDTH))
	*width -= Reduce(&CB_MarginWidth(newcb), *width, 0);
    }
  if (*width)
    {
      if (!(flags & ARROW_SPACING))
	{
	  assert(CB_Type(newcb) != XmCOMBO_BOX);
	  *width -= Reduce(&CB_ArrowSpacing(newcb), *width,
			    MINIMUM_ARROWSPACE);
	}
    }
  if (*width)
    {
      if (!(flags & SHADOW_THICKNESS))
	{
	  delta = Reduce(&MGR_ShadowThickness(newcb), *width,
			 MINIMUM_SHADOWTHICKNESS);
	  *width -= delta;

	  /* This affects height too. */
	  if (*height)
	    Reduce(height, delta, 0);

	  /* Keep the shell's shadowThickness in sync. */
	  if (CB_Type(newcb) != XmCOMBO_BOX)
	    {
	      Arg      args[1];
	      Cardinal n = 0;

	      XtSetArg(args[n], XmNshadowThickness,
		       MGR_ShadowThickness(newcb)), n++;
	      assert(n <= XtNumber(args));
	      XtSetValues(CB_ListShell(newcb), args, n);
	    }
	}
    }
  if (*width)
    {
      if (!(flags & HIGHLIGHT_THICKNESS))
	{
	  delta = Reduce(&CB_HighlightThickness(newcb), *width,
			 MINIMUM_HIGHLIGHTTHICKNESS);
	  *width -= delta;

	  /* This affects height too. */
	  if (*height)
	    Reduce(height, delta, 0);
	}
    }

  /* Reduce height.  Try resouces in order. */
  if (*height)
    {
      if (!(flags & MARGIN_HEIGHT))
	*height -= Reduce(&CB_MarginHeight(newcb), *height, 0);
    }
  if (*height)
    {
      if (!(flags & SHADOW_THICKNESS))
	*height -= Reduce(&MGR_ShadowThickness(newcb), *height,
			  MINIMUM_SHADOWTHICKNESS);
    }
  if (*height)
    {
      if (!(flags & HIGHLIGHT_THICKNESS))
	*height -= Reduce(&CB_HighlightThickness(newcb), *height,
			  MINIMUM_HIGHLIGHTTHICKNESS);
    }

  return (!*width && !*height);
}

/*
 * Reduce()
 *	A helper for ReduceResources, this routine will decrement a
 * single resource.
 */

static int
Reduce(Dimension *size,
       int	  max_change,
       int	  min_size)
{
  int change = 0;

  if (*size > min_size)
    {
      change = MIN(*size - min_size, max_change);
      *size -= change;
    }

  return change;
}

/*
 * Resize()
 *	Sizes and places the children of the ComboBox depending on its
 * type:  XmCOMBO_BOX, XmDROP_DOWN_COMBO_BOX, or XmDROP_DOWN_LIST.
 * The instrinsics call Expose() immediately after calling this method.
 */

static void
Resize(Widget widget)
{
  /* CR 6911: Need to erase old margins, arrow spacing, and arrow areas. */
  /* CR 7255: Erase old margins too. */
  if (XtIsRealized(widget))
    XClearWindow (XtDisplay(widget), XtWindow(widget));

  DoLayout(widget);
}


static void
CheckMinimalSize(
	  Widget widget,
	  Dimension * pwidth,
	  Dimension * pheight)

{
    XmComboBoxWidget cb = (XmComboBoxWidget) widget;
    Dimension min_height, min_width;

    min_height = min_width =
	2 * (MINIMUM_SHADOWTHICKNESS + MINIMUM_HIGHLIGHTTHICKNESS) + MINTXT;
    if (CB_Type(cb) != XmCOMBO_BOX) {
	if (CB_ArrowSize(cb) == XmINVALID_DIMENSION)
	    CB_ArrowSize(cb) = GetDefaultArrowSize((Widget)cb);
	min_width += MINIMUM_ARROWSPACE + (int)CB_ArrowSize(cb);
    }
    else
	min_height += MINLIST;

    *pwidth = MAX(min_width, *pwidth);
    *pheight = MAX(min_height, *pheight);
}

/*
 * ChangeManaged()
 *	Called whenever there is a change in the managed set.
 */

static void
ChangeManaged(Widget widget)
{
  XmComboBoxWidget cb = (XmComboBoxWidget) widget;
  XtWidgetGeometry desired;
  Dimension thickW, thickH, width, height;
  Dimension widthXcess = 0, heightXcess = 0;

  desired.request_mode = 0;
  if (!XtIsRealized((Widget)cb))
    {
      /* Only attempt to change non-specified sizes. */
      desired.width = XtWidth(cb);   /* might be 0 */
      desired.height = XtHeight(cb); /* might be 0 */
    }
  else
    {
      desired.width = 0;
      desired.height = 0;
    }

  /* Compute our desired size.
   *	If user has set width or height, use it
   *	else use preferred sized of children
   */
  if (desired.width == 0 || desired.height == 0)
      ComputeSize((Widget)cb, 0, 0, &desired.width, &desired.height);
  else 
      CheckMinimalSize((Widget)cb, &desired.width, &desired.height);

  /* Make request to parent*/
  desired.request_mode = (CWWidth | CWHeight);
  _XmMakeGeometryRequest((Widget) cb, &desired);

  /* Determine if not enough space due to resources requested */
  GetThickness((Widget) cb, &thickW, &thickH);
  width = 2*thickW + MINTXT;
  height = 2*thickH + MINTXT;
  if (CB_Type(cb) != XmCOMBO_BOX)
    width += CB_ArrowSize(cb) + CB_ArrowSpacing(cb);
  else
    height += MINLIST;

  if (width > XtWidth(cb))
    widthXcess = width - XtWidth(cb);
  if (height > XtHeight(cb))
    heightXcess = height - XtHeight(cb);

  if (widthXcess || heightXcess)
    {
      /* take it off the resources
       * this will reduce each to their minimum value if needed
       */
      if (widthXcess || heightXcess)
	ReduceResources((Widget)cb, &widthXcess, &heightXcess, 0);
    }

  /* Layout the widget */
  DoLayout(widget);
}

/*
 * GeometryManager()
 *	Handle geometry change requests from our children.
 */

static XtGeometryResult
GeometryManager(Widget		  mychild,
		XtWidgetGeometry *request,
		XtWidgetGeometry *reply)
{
    XmComboBoxWidget cb = (XmComboBoxWidget) XtParent(mychild);
    XtWidgetGeometry my_request;
    int width, height ; /* make them int so that we deal with negative */
    XtGeometryResult res ;
    Dimension almost_list_width = 0 ;

    /*
     * Reject anything requests a change of position.  We don't even
     * consider "almosting" what goes with it, i.e. a size request.
     */
    if (request->request_mode & (CWX | CWY)) 
	return XtGeometryNo ;

    my_request.request_mode = 0;
    width = XtWidth(cb) ;
    height = XtHeight(cb) ;

    /*
     * Try to honor a child's request to change size by passing the
     * decision to our parent.  For now we don't compromise or check
     * the other child's preferred size, only our own minimal size.
     */

    if (request->request_mode & XtCWQueryOnly)
	my_request.request_mode |= XtCWQueryOnly;
    
    if (request->request_mode & CWWidth) {
	my_request.request_mode |= CWWidth;
	width += (int)request->width - (int)XtWidth(mychild);
	if (mychild == CB_EditBox(cb))
	    cb->combo_box.ideal_ebwidth = request->width;  
	else {
	    /* 
	     * Compromise rather than letting the list shrink smaller
	     * than the Text's preferred width.
	     */
	    Dimension thickW, thickH;

	    GetThickness((Widget)cb, &thickW, &thickH);
	    if (!cb->combo_box.ideal_ebwidth)
		GetIdealTextSize((Widget)cb, &cb->combo_box.ideal_ebwidth, 
				 NULL);
	    if (width < cb->combo_box.ideal_ebwidth 
		+ 2 * (thickW + XtBorderWidth(CB_EditBox(cb)))) {
		width = cb->combo_box.ideal_ebwidth 
		    + 2 * (thickW + XtBorderWidth(CB_EditBox(cb))) ;
		almost_list_width = cb->combo_box.ideal_ebwidth ;
		my_request.request_mode |= XtCWQueryOnly;
	    }
	}
    }
    
    if (request->request_mode & CWHeight) {
	my_request.request_mode |= CWHeight;
	height += (int)request->height - (int)XtHeight(mychild);
	if (mychild == CB_EditBox(cb))
	    cb->combo_box.ideal_ebheight = request->height;  
    }

    if (request->request_mode & CWBorderWidth) {
	my_request.request_mode |= (CWWidth | CWHeight);
	width += 2*((int)request->border_width - (int)XtBorderWidth(mychild));
	height += 2*((int)request->border_width - (int)XtBorderWidth(mychild));
    }

    if (width > 0) my_request.width = (Dimension) width ;
    if (height > 0) my_request.height = (Dimension) height ;
      
    CheckMinimalSize((Widget)cb, &my_request.width, &my_request.height);

    res = XtMakeGeometryRequest((Widget)cb, &my_request, NULL);
    if (res == XtGeometryYes) {
	if (!(my_request.request_mode & XtCWQueryOnly))	    {
	    XtWidgetProc resize;
	    if (request->request_mode & CWWidth)
		XtWidth(mychild) = request->width;
	    if (request->request_mode & CWHeight)
		XtHeight(mychild) = request->height;
	    if (request->request_mode & CWBorderWidth)
		XtBorderWidth(mychild) = request->border_width;

	    _XmProcessLock();
	    resize = XtCoreProc(cb, resize);
	    _XmProcessUnlock();
	    (*resize)((Widget) cb) ;
	} else
        if (almost_list_width) {
	    reply->request_mode = request->request_mode ;
	    reply->width = almost_list_width ;
	    reply->height = request->height ;
	    reply->border_width = request->border_width ;
	    return XtGeometryAlmost ;
	}
	return XtGeometryYes;
    } else
	return XtGeometryNo;
}



/*
 * Redisplay()
 *	General redisplay function called on exposure events.
 */

/*ARGSUSED*/
static void
Redisplay(Widget widget,
	  XEvent *event,	/* unused */
	  Region region)	/* unused */
{
  XmComboBoxWidget cb = (XmComboBoxWidget) widget;

  if (XtIsRealized(widget))
    {
      if (CB_Type(cb) != XmCOMBO_BOX)
	DrawArrow(widget, CB_ArrowPressed(cb));
      DrawShadows(widget);

      /* CR 6356: Refresh the highlight border too. */
      if (CB_Highlighted(cb))
	HighlightBorder(widget);
      else
	UnhighlightBorder(widget);
    }
}

/*
 * Destroy()
 *	Called by the Intrinsics when a ComboBox widget is destroyed.
 */

static void
Destroy(Widget widget)
{
  XmComboBoxWidget cb = (XmComboBoxWidget) widget;
  Widget           ancestor;

  if (CB_Type(widget) != XmCOMBO_BOX)
    XtRemoveEventHandler(widget, POPUP_EVENT_MASK, FALSE, 
			 PopupEH, (XtPointer)widget);

  /* The remove focus moved callback from the vendor shell. */
  ancestor = widget;
  while (ancestor && !XmIsVendorShell(ancestor))
    ancestor = XtParent(ancestor);

  if (ancestor && !ancestor->core.being_destroyed)
    XmeRemoveFocusChangeCallback(ancestor, FocusMovedCB, (XtPointer) widget);

  if (cb->combo_box.arrow_GC)
    XtReleaseGC(widget, cb->combo_box.arrow_GC);

  XmFontListFree(CB_RenderTable(cb));
}

/*
 * QueryGeometry()
 */

static XtGeometryResult
QueryGeometry(Widget		widget,
	      XtWidgetGeometry *intended,
	      XtWidgetGeometry *desired)
{
  /* Deal with user initial size setting. */
  if (!XtIsRealized(widget))
    {
      desired->width = XtWidth(widget);    /* might be 0 */
      desired->height = XtHeight(widget);  /* might be 0 */
    } 
  else
    {	
      /* Always computes natural size afterwards. */
      desired->width = 0;
      desired->height = 0;
    }

  ComputeSize(widget, 0, 0, &desired->width, &desired->height);

  /* This function will set CWidth and CHeight. */
  return XmeReplyToQueryGeometry(widget, intended, desired);
}

/*
 * ConstraintDestroy()
 */

static void
ConstraintDestroy(Widget child)
{
  XmComboBoxWidget cb;

  if (!XtIsRectObj (child))
    return;

  cb = (XmComboBoxWidget) XtParent(child);

  if (child == CB_EditBox(cb))
    {
      CB_EditBox(cb) = NULL;
    }
  else if (child == CB_ScrolledW(cb))
    {
      CB_ScrolledW(cb) = NULL;
      CB_List(cb) = NULL;
      cb->combo_box.vsb = NULL;
      cb->combo_box.hsb = NULL;
    }
  else if (child == CB_ListShell(cb))
    {
      CB_ListShell(cb) = NULL;
      CB_ScrolledW(cb) = NULL;
      CB_List(cb) = NULL;
      cb->combo_box.vsb = NULL;
      cb->combo_box.hsb = NULL;
    }
}

/*
 * ComboBoxParentProcess()
 */

static Boolean
ComboBoxParentProcess(Widget		  wid,
		      XmParentProcessData event)
{
  XmComboBoxWidget     cb = (XmComboBoxWidget) wid;
  XmGrabShellWidget    gs = NULL;
  Boolean 	       cont = TRUE;
  int		       count = 0;
  Cardinal 	       n = 0;
  Arg		       args[2];

  if ((event->any.process_type == XmINPUT_ACTION) &&
      ((event->input_action.action == XmPARENT_ACTIVATE)  ||
       (event->input_action.action == XmPARENT_CANCEL)))
    {
      if (CB_Type(cb) != XmCOMBO_BOX)
	{
	  gs = (XmGrabShellWidget)CB_ListShell(cb);
	  if (gs && (CB_ShellState(cb) == POPPED_UP))
	    {
	      PopdownList((Widget)cb, event->input_action.event);
	      CBDisarm((Widget)cb, event->input_action.event, NULL, NULL);
	      cont = FALSE;
	    }
	}

      if (event->input_action.action == XmPARENT_ACTIVATE)
        {
	  /*
	   * If the event occurred in the text widget and the popup shell
	   * is up, call the defaultAction callbacks on the List.
	   * We can't call the action routine for List that will do this
	   * because it will pass the Activate back to us and we'll end
	   * up looping.  Only call them if there are items in the list -
	   * this is what list does in KbdActivate().
	   */

	  /* CR 9484: Always compute pos. */
	  XmString item = GetEditBoxValue((Widget) cb);
	  int pos = XmListItemPos(CB_List(cb), item);

	  n = 0;
	  XtSetArg(args[n], XmNitemCount, &count), n++;
	  assert(n <= XtNumber(args));
	  XtGetValues(CB_List(cb), args, n);

	  if (((CB_Type(cb) == XmCOMBO_BOX) || !cont) && count)
	    {
	      XmListCallbackStruct call_data;
	
	      bzero((char*) &call_data, sizeof(XmListCallbackStruct));
	      if (pos)
		{
		  call_data.item		    = XmStringCopy(item);
		  call_data.item_length             = XmStringLength(item);
		  call_data.item_position	    = pos;
		  call_data.selected_item_count     = 1;
		  call_data.selected_item_positions = &pos;
		  call_data.selected_items	    = &item;
		}
	      call_data.reason = XmCR_DEFAULT_ACTION;
	      call_data.event = event->input_action.event;

	      XtCallCallbacks(CB_List(cb), XmNdefaultActionCallback,
			      (XtPointer)&call_data);

	      XmStringFree(call_data.item);
	    }

	  XmStringFree(item);

	  /* CR 6552: Fixup the list selection. */
	  if (pos)
	    XmListSelectPos(CB_List(cb), pos, FALSE);
	  else
	    XmListDeselectAllItems(CB_List(cb));

	  /* Call the selection callbacks. */
	  CallSelectionCallbacks((Widget)cb, event->input_action.event);
	}
    }

  if (cont)
    return _XmParentProcess(XtParent(cb), event);
  else
    return True;
}

/* ------------- CALLBACKS ---------- */


/*
 * TextChangedCB()
 *	Callback function invoked when the edit box value is changed.
 */

/*ARGSUSED*/
static void
TextChangedCB(Widget    widget,	/* unused */
	      XtPointer client_data,
	      XtPointer call_data) /* unused */
{
  XmComboBoxWidget cb = (XmComboBoxWidget)client_data;

  /* Remember to generate a selection callback when focus moves. */
  CB_TextChanged(cb) = TRUE;
}

/*
 * ListSelectionCB()
 *	Callback function called when an item is selected from the list.
 */

/*ARGSUSED*/
static void
ListSelectionCB(Widget widget,	/* unused */
		XtPointer client_data,
		XtPointer call_data)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)client_data;
  XmString item;
  int top, vis_items;
  Cardinal n;
  Arg args[3];
  XmListCallbackStruct *cb_data = (XmListCallbackStruct *)call_data;

  if (!CB_EditBox(cb))
    {
      XmeWarning((Widget)cb, MISSINGKID);
      return;
    }

  /* If the EditBox does not contain the selected text, set it. */
  item = GetEditBoxValue((Widget) cb);
  if (! XmStringCompare(item, cb_data->item))
    SetEditBoxValue((Widget) cb, cb_data->item);
  XmStringFree(item);

  /* If the selected item is not viewable, scroll the list. */
  n = 0;
  XtSetArg(args[n], XmNtopItemPosition, &top), n++;
  XtSetArg(args[n], XmNvisibleItemCount, &vis_items), n++;
  assert(n <= XtNumber(args));
  XtGetValues(CB_List(cb), args, n);

  if ((cb_data->item_position < top) ||
      (cb_data->item_position >= (top + vis_items)))
    XmListSetBottomItem(CB_List(cb), cb_data->item);

  CallSelectionCallbacks((Widget)cb, cb_data->event);

  if (cb_data->event &&
      (cb_data->event->type == ButtonPress ||
       cb_data->event->type == ButtonRelease))
    {
      if (CB_Type(cb) != XmCOMBO_BOX)
	{
	  PopdownList((Widget)cb, cb_data->event);

	  /* CR 6147: If we drag straight from the arrow to a list */
	  /*	item we won't get a normal disarm callback. */
	  CBDisarm((Widget)cb, cb_data->event, NULL, NULL);
	}
    }
}

/*
 * ShellPopupCB()
 *	Called when the grab shell is posted.  Fixup focus.
 */

/*ARGSUSED*/
static void
ShellPopupCB(Widget    widget,	/* unused */
	     XtPointer client_data,
	     XtPointer call_data) /* unused */
{
  XmComboBoxWidget cb = (XmComboBoxWidget)client_data;

  CB_ShellState(cb) = POPPED_UP;

  (void) XmProcessTraversal(CB_List(cb), XmTRAVERSE_CURRENT);
}

/*
 * ShellPopdownCB()
 *	Called when the grab shell is unposted.
 */

/*ARGSUSED*/
static void
ShellPopdownCB(Widget    widget,    /* unused */
	       XtPointer client_data,
	       XtPointer call_data) /* unused */
{
  XmComboBoxWidget cb = (XmComboBoxWidget)client_data;
  XmDisplay disp = (XmDisplay) XmGetXmDisplay(XtDisplay(cb));
  Window old_focus;
  int old_revert;

  /* CR 9887: List may not have seen the BtnUp event... */
  XtCallActionProc(CB_List(cb), "ListKbdCancel", NULL, NULL, 0);

  /* Re-enable drag and drop. */
  disp->display.userGrabbed = False;

  CB_ShellState(cb) = POPPED_DOWN;
  cb->combo_box.scrolling = FALSE;

  /* In click-to-type mode we can't lose focus, and won't get a */
  /*	focus-in event when the grabshell pops down. */
  XGetInputFocus(XtDisplay(cb), &old_focus, &old_revert);
  if (old_revert != RevertToParent)
    {
      CBFocusOut((Widget)cb, NULL, NULL, NULL);

      /* CR 7122: Tell the edit box to stop blinking now. */
      if (CB_Type(cb) == XmDROP_DOWN_COMBO_BOX) 
	{
	  XEvent focus_event;

	  focus_event.xfocus.type = FocusOut;
	  focus_event.xfocus.send_event = True;
	  XtCallActionProc(CB_EditBox(cb), "focusOut", &focus_event, NULL, 0);
	}
    }
}

/*
 * FocusMovedCB()
 *	Called from VendorShell every time focus moves.
 */

/*ARGSUSED*/
static void
FocusMovedCB(Widget    widget,	/* unused */
	     XtPointer client_data,
	     XtPointer call_data)
{
  XmFocusMovedCallbackStruct *callback =
    (XmFocusMovedCallbackStruct *) call_data;
  XmComboBoxWidget cb = (XmComboBoxWidget) client_data;
  Boolean have_focus, getting_focus;

  /* Some other focus-moved callback has aborted this shift. */
  if (!callback->cont)
    return;

  have_focus = CB_Highlighted(cb);
  getting_focus = (((callback->new_focus == NULL) &&
		    (CB_ShellState(cb) != POPPED_DOWN)) ||
		   (callback->new_focus == (Widget)cb) ||
		   (callback->new_focus == CB_EditBox(cb)) ||
		   (callback->new_focus == CB_ScrolledW(cb)) ||
		   (callback->new_focus == CB_List(cb)) ||
		   ((cb->combo_box.hsb != NULL) &&
		    (callback->new_focus == cb->combo_box.hsb)) ||
		   ((cb->combo_box.vsb != NULL) &&
		    (callback->new_focus == cb->combo_box.vsb)));

  /* CR 9868: In XmPOINTER mode focus goes to weird places. */
  if (!getting_focus && (_XmGetFocusPolicy((Widget) cb) == XmPOINTER))
    {
      Window root, child;
      int root_x, root_y, win_x, win_y;
      unsigned int mod_mask;

      if (CB_ShellState(cb) == POPPED_UP)
	getting_focus = TRUE;
      else if ((callback->new_focus == XtParent(cb)) &&
	       XQueryPointer(XtDisplay(cb), XtWindow(cb), &root, &child, 
			     &root_x, &root_y, &win_x, &win_y, &mod_mask) &&
	       (win_x >= 0) && (win_x < XtWidth(cb)) &&
	       (win_y >= 0) && (win_y < XtHeight(cb)))
	getting_focus = TRUE;
    }

  if (have_focus && !getting_focus)
    {
      CBFocusOut((Widget)cb, callback->event, NULL, NULL);

      /* Is this necessary? */
      if (CB_ShellState(cb) == POPPED_UP)
	{
	  PopdownList((Widget)cb, callback->event);
	  CBDisarm((Widget)cb, callback->event, NULL, NULL);
	}
    }
  else if (!have_focus && getting_focus)
    {
      CBFocusIn((Widget)cb, callback->event, NULL, NULL);
    }
}

/* ------------- ACTION ROUTINES ---------- */


/*
 * CBArmAndDropDownList()
 *	Handle button down action in widget.  First determine if hit
 * is in an arrow, otherwise ignore the event.  If it is in an arrow,
 * then supply visual feedback and perform the appropriate action.
 */

/*ARGSUSED*/
static void
CBArmAndDropDownList(Widget widget,
		     XEvent *event,
		     String *params,		/* unused */
		     Cardinal *num_params)	/* unused */
{
  XmComboBoxWidget cb = FindComboBox(widget);
  XmGrabShellWidget gs = (XmGrabShellWidget)CB_ListShell(cb);

  /* Return if this is a replay of the unpost event. */
  if (gs && (event->xbutton.time == gs->grab_shell.unpost_time))
    return;

  /* Ignore the event if this is a replay */
  if (! _XmIsEventUnique(event)) 
    return;

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  /* CR 9833: Attempt to grab focus. */
  XmProcessTraversal((Widget)cb, XmTRAVERSE_CURRENT);

  if ((CB_Type(cb) != XmCOMBO_BOX) &&
      Hit((XButtonEvent *)event, CB_HitRect(cb)))
    {
      CB_ArrowPressed(cb) = TRUE;
      DrawArrow((Widget)cb, CB_ArrowPressed(cb));
      CBDropDownList((Widget)cb, event, NULL, NULL);
    }
}

/*
 * CBDisarm()
 *	Handle button up action and undo CBArmAndDropDownList.
 */

/*ARGSUSED*/
static void
CBDisarm(Widget widget,
	 XEvent *event,		/* unused */
	 String *params,	/* unused */
	 Cardinal *num_params)	/* unused */
{
  XmComboBoxWidget cb = FindComboBox(widget);

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  if (CB_Type(cb) != XmCOMBO_BOX)
    {
      if (CB_ArrowPressed(cb))
	{
	  CB_ArrowPressed(cb) = FALSE;
	  DrawArrow((Widget)cb, CB_ArrowPressed(cb));
	}
    }
}

/*
 * CBDropDownList()
 *	Action to post/unpost the drop down list.
 */

static void
CBDropDownList(Widget    widget,
	       XEvent   *event,
	       String   *params,
	       Cardinal *num_params)
{
  XmComboBoxWidget cb = FindComboBox(widget);

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  if ((CB_Type(cb) != XmCOMBO_BOX))
    {
      XmGrabShellWidget gs = (XmGrabShellWidget)CB_ListShell(cb);
      if (gs && (CB_ShellState(cb) == POPPED_DOWN))
	{
	  XmDisplay disp = (XmDisplay) XmGetDisplay(widget);
	  Arg args[3];
	  Cardinal n;
	  int tmp;
	  Position root_x, root_y, shell_x, shell_y;
	  Dimension shell_width;

	  XtTranslateCoords((Widget)cb, XtX(cb), XtY(cb), &root_x, &root_y);
	
	  shell_x = root_x - XtX(cb) + CB_HighlightThickness(cb) -
	    XtBorderWidth(CB_ListShell(cb)); 
	  shell_y = root_y + XtHeight(cb) - CB_HighlightThickness(cb) -
	    XtY(cb); 
	
	  /* Try to position the shell on the screen. */
	  tmp = WidthOfScreen(XtScreen(cb)) - XtWidth(CB_ListShell(cb));
	  tmp = MIN(tmp, shell_x);
	  shell_x = MAX(0, tmp);
	  tmp = HeightOfScreen(XtScreen(cb)) - XtHeight(CB_ListShell(cb));
	  tmp = MIN(tmp, shell_y);
	  shell_y = MAX(0, tmp);

	  /* CR 8446: The shell width may have changed unexpectedly. */
	  shell_width = XtWidth(cb) - 2 * CB_HighlightThickness(cb);

	  n = 0;
	  XtSetArg(args[n], XmNx, shell_x), n++;
	  XtSetArg(args[n], XmNy, shell_y), n++;
	  XtSetArg(args[n], XmNwidth, shell_width), n++;
	  assert(n <= XtNumber(args));
	  XtSetValues(CB_ListShell(cb), args, n);

	  CB_ShellState(cb) = POPPING_UP;
	  cb->combo_box.scrolling = FALSE;

	  /* Don't let drag and drop confuse things. */
	  disp->display.userGrabbed = True;

	  /* Record the post time */
	  gs->grab_shell.post_time = event->xbutton.time;

	  /* Record the event to prevent popdown on replay */
	  _XmRecordEvent(event);
	  _XmPopupSpringLoaded(CB_ListShell(cb));
	}
      else /* shell is popped up */
	{
	  PopdownList((Widget)cb, event);
	  CBDisarm((Widget)cb, event, params, num_params);
	}
    }
}

/*
 * CBFocusIn()
 *	Action routine to draw focus highlighting.
 */

/*ARGSUSED*/
static void
CBFocusIn(Widget    widget,
	  XEvent   *event,	/* unused */
	  String   *params,	/* unused */
	  Cardinal *num_params)	/* unused */
{
  XmComboBoxWidget cb = FindComboBox(widget);

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  HighlightBorder((Widget)cb);
}

/*
 * CBFocusOut()
 *	Action routine to erase focus highlighting.
 */

/*ARGSUSED*/
static void
CBFocusOut(Widget    widget,
	   XEvent   *event,
	   String   *params,	 /* unused */
	   Cardinal *num_params) /* unused */
{
  XmComboBoxWidget cb = FindComboBox(widget);

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  UnhighlightBorder((Widget)cb);

  if (CB_TextChanged(cb))
    CallSelectionCallbacks((Widget)cb, event);
}

/*
 * CBTextFocusOut()
 *	Action routine to fake the text field cursor into blinking.
 */

/*ARGSUSED*/
static void
CBTextFocusOut(Widget    widget,
	       XEvent   *event,
	       String   *params,
	       Cardinal *num_params)
{
  XmComboBoxWidget cb = FindComboBox(widget);

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  /* CR 7122: Suppress text focus-out events when the grab shell is */
  /*	posted, so that the insertion cursor will continue to blink. */
  if ((CB_Type(cb) != XmDROP_DOWN_COMBO_BOX) ||
      (CB_ShellState(cb) != POPPED_UP))
    {
      XtCallActionProc(CB_EditBox(cb), "focusOut", event, params,
		       (num_params ? *num_params : 0));
    }
}

/*
 * CBActivate()
 *	Action routine called when the list is activated.
 */

static void
CBActivate(Widget    widget,
	   XEvent   *event,
	   String   *params,
	   Cardinal *num_params)
{
  XmComboBoxWidget cb = FindComboBox(widget);
  XmParentInputActionRec p_event;

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  p_event.process_type = XmINPUT_ACTION;
  p_event.action       = XmPARENT_ACTIVATE;
  p_event.event        = event;
  p_event.params       = params;
  p_event.num_params   = num_params;

  ComboBoxParentProcess((Widget)cb, (XmParentProcessData) &p_event);
}

/*
 * CBCancel()
 *	Action invoked from the drop down list.
 */

static void
CBCancel(Widget    widget,
	 XEvent   *event,
	 String   *params,
	 Cardinal *num_params)
{
  XmComboBoxWidget cb = FindComboBox(widget);
  XmParentInputActionRec p_event;

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  p_event.process_type = XmINPUT_ACTION;
  p_event.action       = XmPARENT_CANCEL;
  p_event.event        = event;
  p_event.params       = params;
  p_event.num_params   = num_params;

  ComboBoxParentProcess((Widget)cb, (XmParentProcessData) &p_event);
}

/*
 * CBListAction()
 *	Generic action to perform operations on the list.
 */

static void
CBListAction(Widget    widget,
	     XEvent   *event,
	     String   *params,
	     Cardinal *num_params)
{
  /* This enum matches the order of the string constants in RepType.c */
  enum { UP, DOWN, PREVPAGE, NEXTPAGE, BEGINDATA, ENDDATA };

  XmComboBoxWidget cb = FindComboBox(widget);
  int direction;

  if (!cb)
    {
      XmeWarning((Widget)cb, WRONGWIDGET);
      return;
    }

  if (!num_params || (*num_params != 1) || !params)
    {
      XmeWarning((Widget) cb, WRONGPARAMS);
      return;
    }

  if (_XmConvertActionParamToRepTypeId
          ((Widget) cb, XmRID_COMBO_BOX_LIST_ACTION_ACTION_PARAMS,
	   params[0], False, &direction) == False)
    {
      /* Unknown value.  A warning should already have been printed. */
      return;
    }

  switch (direction)
    {
    case UP:
    case DOWN:
      {
	int *pos, count, num_items;
	Cardinal n;
	Arg args[3];

	/* Find the current number of items and selected position. */
	n = 0;
	XtSetArg(args[n], XmNitemCount, &num_items),	     n++;
	XtSetArg(args[n], XmNselectedPositions, &pos),	     n++;
	XtSetArg(args[n], XmNselectedPositionCount, &count), n++;
	assert(n <= XtNumber(args));
	XtGetValues(CB_List(cb), args, n);

	if (count)
	  {
	    switch (direction)
	      {
	      case UP:
		if (*pos >= 1)
		  XmListSelectPos(CB_List(cb), *pos - 1, TRUE);
		break;
	
	      case DOWN:
		if (*pos < num_items)
		  XmListSelectPos(CB_List(cb), *pos + 1, TRUE);
		else if (*pos == num_items)
		  XmListSelectPos(CB_List(cb), 1, TRUE);
		break;
	
	      default:
		assert(FALSE);
	      }
	  }
	else if (num_items)
	  {
	    XmListSelectPos(CB_List(cb), 1, TRUE);
	  }
      }
      break;

    case PREVPAGE:
      if ((CB_Type(cb) == XmCOMBO_BOX) ||
	  (CB_ShellState(cb) == POPPED_UP))
	XtCallActionProc(CB_List(cb), "ListPrevPage", event, NULL, 0);
      break;

    case NEXTPAGE:
      if ((CB_Type(cb) == XmCOMBO_BOX) ||
	  (CB_ShellState(cb) == POPPED_UP))
	XtCallActionProc(CB_List(cb), "ListNextPage", event, NULL, 0);
      break;

    case BEGINDATA:
      XtCallActionProc(CB_List(cb), "ListBeginData", event, NULL, 0);
      break;

    case ENDDATA:
      XtCallActionProc(CB_List(cb), "ListEndData", event, NULL, 0);
      break;

    default:
      assert(FALSE);
    }
}

/*
 * PopdownList()
 *	Internal utility to unpost the grabshell.
 */

static Boolean
PopdownList(Widget cb,
	    XEvent *event)
{
  Widget gs = CB_ListShell(cb);

  /* Popping down while in the process of popping up causes X errors. */
  if (gs && XmIsGrabShell(gs) && (CB_ShellState(cb) == POPPED_UP))
    {
      CB_ShellState(cb) = POPPING_DOWN;

      XtCallActionProc(gs, "GrabShellPopdown", event, NULL, 0);
      return TRUE;
    }

  return FALSE;
}

/* ------------- EVENT HANDLERS ---------- */


/*
 * PopupEH()
 *	An XtEventHandler for the popup shell (the drop-down list).
 */

/*ARGSUSED*/
static void
PopupEH(Widget    widget,	/* unused */
	XtPointer client_data,
	XEvent   *event,
	Boolean  *dispatch)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)client_data;

  switch (event->type)
    {
    case ButtonRelease:
      CBDisarm((Widget)cb, event, NULL, NULL);

      /* CR 9899: Only discard matched pairs of scrollbar button events. */
      /*	Should combo_box.scrolling be a counter??? */
      if (cb->combo_box.scrolling)
	*dispatch = cb->combo_box.scrolling = FALSE;
      break;

    case ButtonPress:
      /* Press & release in the scrollbar shouldn't popdown the list. */
      if ((cb->combo_box.vsb &&
	   XtIsRealized(cb->combo_box.vsb) &&
	   (event->xbutton.window == XtWindow(cb->combo_box.vsb))) ||
	  (cb->combo_box.hsb &&
	   XtIsRealized(cb->combo_box.hsb) &&
	   (event->xbutton.window == XtWindow(cb->combo_box.hsb))))
	cb->combo_box.scrolling = TRUE;
      break;

    case EnterNotify:
      if (CB_ArrowPressed(cb))
	XtCallActionProc(CB_List(cb), "ListBeginSelect", event, NULL, 0);
      break;

    default:
      /* This shouldn't happen. */
      break;
    }
}

/*
 * CR 6925: The following two event handlers are used to coordinate
 * grabs between the scrollbar and the grab shell in dropdown lists.
 * In this case the existing active grab started by the grab shell
 * will interfere with the passive grab started by X when the user
 * presses a button within the scrollbar.  
 *
 * To deal with the problem, SBBtnDownEH will do an XtGrabPointer
 * to transfer the grab to the scrollbar and SBBtnUpEH will cause
 * the grab to return to the grab shell.
 */

/*ARGSUSED*/
static void
SBBtnDownEH(Widget    w, 
	    XtPointer client_data, 
	    XEvent   *event, 
	    Boolean  *cont)	/* unused */
{
  XmGrabShellWidget shell = (XmGrabShellWidget) client_data;

  XtGrabPointer(w, False, Events | PointerMotionMask | ButtonMotionMask,
		GrabModeAsync, GrabModeAsync,
		None, shell->grab_shell.cursor, event->xbutton.time);
}

/*ARGSUSED*/
static void
SBBtnUpEH(Widget    w,		/* unused */
	  XtPointer client_data, 
	  XEvent   *event, 
	  Boolean  *cont)	/* unused */
{
  XmGrabShellWidget shell = (XmGrabShellWidget) client_data;

  /* Note that this regrab to the grab shell will need to be changed
   * if the kind of grab that the grabshell imposes changes.
   */
  XtGrabPointer((Widget) shell, shell->grab_shell.owner_events, 
		Events,
		shell->grab_shell.grab_style, GrabModeAsync,
		None, shell->grab_shell.cursor, event->xbutton.time);
  if (shell->grab_shell.grab_style == GrabModeSync)
    XAllowEvents(XtDisplay(shell), SyncPointer, event->xbutton.time);
}

/*
 * FindComboBox()
 *	An internal utility routine to traverse up the widget
 * hierarchy until a ComboBox is found.
 */

static XmComboBoxWidget
FindComboBox(Widget widget)
{
  Widget cb = widget;

  while (cb && !XmIsComboBox(cb))
    cb = XtParent(cb);

  return (XmComboBoxWidget) cb;
}

/*
 * CallSelectionCallbacks()
 *	Utility routine to invoke the ComboBox selection callback.
 */

static void
CallSelectionCallbacks(Widget  widget,
		       XEvent *event)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  XmComboBoxCallbackStruct call_data;
  XmString item;
  int pos;

  /* The user knows about this change. */
  CB_TextChanged(cb) = FALSE;

  item = GetEditBoxValue((Widget) cb);

  /* Implement bogus zero-based positions for DtComboBox compatibility. */
  pos = XmListItemPos(CB_List(cb), item);
  if ((CB_PositionMode(cb) == XmZERO_BASED) && (pos > 0))
    --pos;

  /* Call callback list */
  call_data.item_or_text  = item;
  call_data.item_position = pos;
  call_data.reason        = XmCR_SELECT;
  call_data.event         = event;
  XtCallCallbackList((Widget)cb, CB_SelectionCB(cb), (XtPointer)&call_data);

  XmStringFree(item);
}

/* ------------- DEFAULT RESOURCE VALUE CALLPROCS ---------- */

/*
 * XmRCallProc routine for checking font before setting it to NULL
 * if no value is specified for both XmNrenderTable and XmNfontList.
 * I'm usurping the text_changed field to act as a flag to indicate that
 * this function has been called twice on same widget -> implying that
 * resource needs to be set NULL, otherwise leave it alone.
 */

/*ARGSUSED*/
static void 
CheckSetRenderTable(Widget wid,
		    int offset,
		    XrmValue *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)wid;
  if (cb->combo_box.text_changed) /* Already been here once, so set resource = NULL */
	value->addr = NULL;
  else {
	cb->combo_box.text_changed = True;
	value->addr = (char*) &CB_RenderTable(cb);
  }
}


/* ------------- SYNTHETIC RESOURCE ACCESS METHODS ------------- */


/*
 * CBSetSelectedItem()
 *	A synthetic resource import procedure.  This procedure can get
 * called at create time BEFORE ComboBox's Initialize() has been
 * called and children have been created.  Make sure children have
 * been created before doing anything.
 */

/*ARGSUSED*/
static XmImportOperator
CBSetSelectedItem(Widget    widget,
		  int       offset, /* unused */
		  XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  XmString new_value = (XmString) *value;
  int pos = 0;

  if (!cb->composite.num_children)
    return XmSYNTHETIC_NONE;

  pos = XmListItemPos(CB_List(cb), new_value);
  if (pos > 0)
    {
      XmListSelectPos(CB_List(cb), pos, TRUE);
    }
  else
    {
      XmString item = GetEditBoxValue((Widget) cb);
      if (! XmStringCompare(item, new_value))
	{
	  XmListDeselectAllItems(CB_List(cb));
	  SetEditBoxValue((Widget) cb, new_value);
	}
      XmStringFree(item);
    }

  return XmSYNTHETIC_NONE;
}

/*
 * CBGetSelectedItem()
 *	A synthetic resource export procedure.
 */

/*ARGSUSED*/
static void
CBGetSelectedItem(Widget    widget,
		  int       offset, /* unused */
		  XtArgVal *value)
{
  *value = (XtArgVal) GetEditBoxValue(widget);
}

/*
 * CBSetSelectedPos()
 *	A synthetic resource import procedure.
 */

/*ARGSUSED*/
static XmImportOperator
CBSetSelectedPos(Widget    widget,
		 int       offset, /* unused */
		 XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  int *selPosns = NULL, curpos = 0;
  Cardinal n;
  Arg args[3];
  int new_pos;

  /* 
   * This procedure can get called at create time BEFORE ComboBox's
   * Initialize() has been called and children have been created.
   * Make sure children have been created before doing anything. 
   */
  if (!cb->composite.num_children)
    return XmSYNTHETIC_NONE;

  /* Get current (1-based) list selectedPos */
  n = 0;
  XtSetArg(args[n], XmNselectedPositions, &selPosns), n++;
  assert(n <= XtNumber(args));
  XtGetValues(CB_List(cb), args, n);
  if (selPosns)
    curpos = *selPosns;

  /* Implement bogus zero-based positions for DtComboBox compatibility. */
  new_pos = (int)*value;
  if (CB_PositionMode(cb) == XmZERO_BASED)
    new_pos++;

  if (curpos != new_pos)
    XmListSelectPos(CB_List(cb), new_pos, TRUE);

  return XmSYNTHETIC_NONE;
}

/*
 * CBGetSelectedPos()
 *	A synthetic resource export procedure.
 */

/*ARGSUSED*/
static void
CBGetSelectedPos(Widget    widget,
		 int       offset, /* unused */
		 XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg args[2];
  Cardinal nargs;
  int *pos, count;
  int result;

  /* Using XmListGetSelectedPos would copy the positions array. */
  nargs = 0;
  XtSetArg(args[nargs], XmNselectedPositions, &pos),		nargs++;
  XtSetArg(args[nargs], XmNselectedPositionCount, &count),	nargs++;
  assert(nargs <= XtNumber(args));
  XtGetValues(CB_List(cb), args, nargs);

  /* Implement bogus zero-based positions for DtComboBox compatibility. */
  result = (count > 0) ? *pos : 0;
  if ((CB_PositionMode(cb) == XmZERO_BASED) && (result > 0))
    --result;

  *value = result;
}

/*
 * CBGetColumns()
 *	A synthetic resource export procedure.
 */

/*ARGSUSED*/
static void
CBGetColumns(Widget    widget,
	     int       offset,	/* unused */
	     XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg args[1];
  Cardinal nargs;
  short columns = 0;

  /* Fetch the value from the child widget. */
  if (CB_EditBox(cb))
    {
      nargs = 0;
      XtSetArg(args[nargs], XmNcolumns, &columns),	nargs++;
      assert(nargs <= XtNumber(args));
      XtGetValues(CB_EditBox(cb), args, nargs);
    }

  *value = (XtArgVal)columns;
}

/*
 * CBGetItems()
 *	A synthetic resource export procedure.
 */

/*ARGSUSED*/
static void
CBGetItems(Widget    widget,
	   int       offset,	/* unused */
	   XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg args[1];
  Cardinal nargs;
  XmStringTable items = NULL;

  /* Fetch the value from the child widget. */
  if (CB_List(cb))
    {
      nargs = 0;
      XtSetArg(args[nargs], XmNitems, &items),	nargs++;
      assert(nargs <= XtNumber(args));
      XtGetValues(CB_List(cb), args, nargs);
    }

  *value = (XtArgVal)items;
}

/*
 * CBGetItemCount()
 *	A synthetic resource export procedure.
 */

/*ARGSUSED*/
static void
CBGetItemCount(Widget    widget,
	       int       offset, /* unused */
	       XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg args[1];
  Cardinal nargs;
  int count = 0;

  /* Fetch the value from the child widget. */
  if (CB_List(cb))
    {
      nargs = 0;
      XtSetArg(args[nargs], XmNitemCount, &count),	nargs++;
      assert(nargs <= XtNumber(args));
      XtGetValues(CB_List(cb), args, nargs);
    }

  *value = (XtArgVal)count;
}

/*
 * CBGetVisibleItemCount()
 *	A synthetic resource export procedure.
 */

/*ARGSUSED*/
static void
CBGetVisibleItemCount(Widget    widget,
		      int       offset, /* unused */
		      XtArgVal *value)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg args[1];
  Cardinal nargs;
  int viz_count = 0;

  /* Fetch the value from the child widget. */
  if (CB_List(cb))
    {
      nargs = 0;
      XtSetArg(args[nargs], XmNvisibleItemCount, &viz_count),	nargs++;
      assert(nargs <= XtNumber(args));
      XtGetValues(CB_List(cb), args, nargs);
    }

  *value = (XtArgVal)viz_count;
}

/* ------------- ADDITIONAL FUNCTIONS ---------- */


/*
 * HighlightBorder()
 */

static void
HighlightBorder(Widget w)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)w;

  CB_Highlighted(cb) = TRUE;

  if ((XtWidth(cb) == 0) ||
      (XtHeight(cb) == 0) ||
      (CB_HighlightThickness(cb) == 0))
    return;

  XmeDrawHighlight(XtDisplay(cb), XtWindow(cb), cb->manager.highlight_GC, 0, 0,
		   XtWidth(cb), XtHeight(cb), CB_HighlightThickness(cb));
}

/*
 * UnhighlightBorder()
 */

static void
UnhighlightBorder(Widget w)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)w;

  CB_Highlighted(cb) = FALSE;

  if ((XtWidth(w) == 0) ||
      (XtHeight(w) == 0) ||
      (CB_HighlightThickness(cb) == 0))
    return;

  XmeDrawHighlight(XtDisplay(cb), XtWindow(cb), cb->manager.background_GC,
		   0, 0, XtWidth(w), XtHeight(w), CB_HighlightThickness(cb));
}

/*
 * DoLayout()
 *	Computes layout and sizes of children given a size for their
 * parent.  Called from SetValues, Resize and ChangeManaged.
 */

static void
DoLayout(Widget widg)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widg;
  Dimension	   ebW = 0, ebH = 0, thickW = 0, thickH = 0;
  Dimension	   listW = 0, listH = 0 ;
  Dimension	   diff, htLeft = 0;
  XtWidgetGeometry geom, replygeom;
  XtGeometryResult result;

  /* Make sure all our children are around */
  if (!CB_EditBox(cb))
    XmeWarning(widg, MISSINGKID);
  else if (!XtIsManaged(CB_EditBox(cb)))
    {
      XmeWarning(widg, UNMANAGEDKID);
      return;
    }

  if (!CB_List(cb))
    XmeWarning(widg, MISSINGKID);
  else if (!XtIsManaged(CB_List(cb)))
    {
      XmeWarning(widg, UNMANAGEDKID);
      return;
    }

  GetThickness(widg, &thickW, &thickH);
  ebW = XtWidth(cb) - 2 * (thickW + XtBorderWidth(CB_EditBox(cb)));
  if (CB_Type(cb) != XmCOMBO_BOX)
    {
      /* These are the drop down types */
      Arg args[1];
      Cardinal nargs;

      ebH = XtHeight(cb) - 2 * (thickH + XtBorderWidth(CB_EditBox(cb)));

      if (CB_ArrowSize(cb) == XmINVALID_DIMENSION)
	CB_ArrowSize(cb) = GetDefaultArrowSize((Widget)cb);
      SetHitArea((Widget)cb);
      ebW -= (CB_ArrowSpacing(cb) + CB_ArrowSize(cb));

      /* Realize the shell so these values will be maintained. */
      if (!XtIsRealized(CB_ListShell(cb)))
	XtRealizeWidget(CB_ListShell(cb));

      /* CR 7676: These numbers don't make sense, but they work? */
      listW = XtWidth(cb) - 2 * CB_HighlightThickness(cb);
      nargs = 0;
      XtSetArg(args[nargs], XmNwidth, listW), nargs++;
      assert(nargs <= XtNumber(args));
      XtSetValues(CB_ListShell(cb), args, nargs);
    }
  else
    {
      Dimension new_width = XtWidth(CB_ScrolledW(cb));
      Dimension new_height = XtHeight(CB_ScrolledW(cb));

      htLeft = XtHeight(cb) -
	2 * (XtBorderWidth(CB_EditBox(cb)) + XtBorderWidth(CB_ScrolledW(cb)) +
	     CB_HighlightThickness(cb) + MGR_ShadowThickness(cb) +
	     CB_MarginHeight(cb));
      if (!cb->combo_box.ideal_ebheight)
	GetIdealTextSize((Widget)cb, NULL, &cb->combo_box.ideal_ebheight);

      /* If ideal edit box height fits, use it. */
      ebH = MIN(htLeft - MINLIST, cb->combo_box.ideal_ebheight);
      listW = XtWidth(cb) - 2 * (thickW + XtBorderWidth(CB_ScrolledW(cb)));
      listH = htLeft - ebH;

      /* Ask ScrolledW if this size is okay */
      geom.request_mode = 0;
      geom.request_mode |= CWWidth;
      geom.request_mode |= CWHeight;
      geom.width = listW;
      geom.height = listH;
      switch (XtQueryGeometry(CB_ScrolledW(cb), &geom, &replygeom))
	{
	case XtGeometryAlmost:
	  if (replygeom.request_mode & CWHeight)
	    {
	      /* see if EditBox will shrink. */
	      listH = replygeom.height;
	      diff = abs(geom.height - replygeom.height);
	      geom.request_mode = 0;
	      geom.request_mode |= CWHeight;
	      geom.height = ebH - diff;
	      result = XtQueryGeometry(CB_EditBox(cb), &geom, NULL);
	      if (result == XtGeometryYes)
		{
		  ebH = geom.height; /* will be resized below */
		  new_width = listW;
		  new_height = listH;
		}
	    }
	  break;

	case XtGeometryYes:
	case XtGeometryNo:
	default:
	  /* No compromises.  These are the new dimensions. */
	  new_width = listW;
	  new_height = listH;
	}

      XmeConfigureObject(CB_ScrolledW(cb),
			 thickW,
			 (thickH + ebH +
			  2 * XtBorderWidth(CB_EditBox(cb)) +
			  XtBorderWidth(CB_ScrolledW(cb))),
			 new_width, new_height,
			 XtBorderWidth(CB_ScrolledW(cb)));
    }

  {
    Position new_x, new_y;

    if ((CB_Type(cb) != XmCOMBO_BOX) && LayoutIsRtoLM(cb))
      {
	new_x = (thickW + CB_ArrowSize(cb) + CB_ArrowSpacing(cb));
	new_y = thickH ;
      }
    else
      {
	new_x = thickW ;
	new_y = thickH ;
      }

    XmeConfigureObject(CB_EditBox(cb), new_x, new_y,
		       ebW, ebH, XtBorderWidth(CB_EditBox(cb)));
  }
}

/*
 * ComputeSize()
 *	Determines the width and height of a ComboBox.
 */

static void
ComputeSize(Widget w,
	    Dimension editW,
	    Dimension editH,
	    Dimension *width,
	    Dimension *height)
{
  Dimension cbWidth, cbHeight;
  Dimension thickW, thickH;
  XmComboBoxWidget cb = (XmComboBoxWidget)w;
  int textWidth, textHeight;

  /* Size of ComboBox
   *	width  = (text width) + [arrow space & width] + (surrounding space)
   *	height = (text height) + (surrounding space) + [list height]
   */

  GetThickness(w, &thickW, &thickH);

  /* Compute size of textfield */
  if (!editW || !editH)
    {
      /* Ask for text child's preferred size */
      GetIdealTextSize((Widget)cb, &textWidth, &textHeight);
    }
  else
    {
      textWidth = editW;
      textHeight = editH;
    }
  cb->combo_box.ideal_ebwidth = textWidth;
  cb->combo_box.ideal_ebheight = textHeight;
  cbWidth = textWidth + 2 * (thickW + XtBorderWidth(CB_EditBox(cb)));
  cbHeight = textHeight + 2 * (thickH + XtBorderWidth(CB_EditBox(cb)));

  /* Adjust dimensions taking optional children into account. */
  if (CB_Type(cb) == XmCOMBO_BOX)
    {
      /* For a COMBO_BOX add in the height of the list. */
      XtWidgetGeometry pref;
      (void) XtQueryGeometry(CB_ScrolledW(cb), NULL, &pref);
      cbHeight += (pref.height + 2 * XtBorderWidth(CB_ScrolledW(cb)));
    }
  else
    {
      /* For other types consider the arrow dimensions. */
      if (CB_ArrowSize(cb) == XmINVALID_DIMENSION)
	CB_ArrowSize(cb) = (int) ((float) textHeight * DEFAULT_ARROW_SCALING);

      cbWidth += CB_ArrowSize(cb) + CB_ArrowSpacing(cb);
      if (CB_ArrowSize(cb) > textHeight)
	cbHeight += (CB_ArrowSize(cb) - textHeight);
    }

  /* Preserve existing sizes. */
  if (! *width)
    *width = MAX(cbWidth, 1);
  if (! *height)
    *height = MAX(cbHeight, 1);
}

/*
 * GetIdealTextSize()
 */

static void
GetIdealTextSize(Widget w,
		 int *width,
		 int *height)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)w;
  XtWidgetGeometry text_pref, list_pref;

  /* Ask for text child's preferred size */
  (void) XtQueryGeometry(CB_EditBox(cb), NULL, &text_pref);

  /* Ask for list child's preferred size */
  (void) XtQueryGeometry(CB_ScrolledW(cb), NULL, &list_pref);

  if (width)
    *width = MAX(text_pref.width, list_pref.width);
  if (height)
    *height = text_pref.height;
}

/*
 * GetDefaultArrowSize()
 */

static Dimension
GetDefaultArrowSize(Widget w)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)w;

  if (!cb->combo_box.ideal_ebheight)
    GetIdealTextSize((Widget)cb, NULL, &cb->combo_box.ideal_ebheight);

  return((int) ((float) cb->combo_box.ideal_ebheight * DEFAULT_ARROW_SCALING));
}

/*
 * SetHitArea()
 *	Sets hit area for arrow.
 */

static void
SetHitArea(Widget w)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)w;
  Dimension	   thickW = 0, thickH = 0, ebH;

  assert(CB_Type(cb) != XmCOMBO_BOX);

  GetThickness(w, &thickW, &thickH);
  ebH = XtHeight(cb) - 2 * (thickH + XtBorderWidth(CB_EditBox(cb)));

  if (CB_ArrowSize(cb) == XmINVALID_DIMENSION)
    CB_ArrowSize(cb) = GetDefaultArrowSize((Widget)cb);

  CB_HitRect(cb).width = CB_ArrowSize(cb);
  CB_HitRect(cb).height = ebH;
  if (LayoutIsRtoLM(cb))
    {
      CB_HitRect(cb).x = thickW;
      CB_HitRect(cb).y = thickH;
    }
  else
    {
      CB_HitRect(cb).x = XtWidth(cb) - thickW - CB_ArrowSize(cb);
      CB_HitRect(cb).y = thickH;
    }
}

/*
 * GetThickness()
 *	Gets "thickness" resources, i.e. shadow thickness, marginW, etc.
 */

static void
GetThickness(Widget widg,
	     Dimension *width,
	     Dimension *height)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widg;
 
  if (width)
    *width = cb->combo_box.margin_width + cb->combo_box.highlight_thickness + 
	cb->manager.shadow_thickness;
  if (height)
    *height = cb->combo_box.margin_height + 
	cb->combo_box.highlight_thickness + cb->manager.shadow_thickness;
}

/*
 * GetArrowGC()
 *	Create the GC for drawing sensitive arrows.
 */

static void
GetArrowGC(Widget widget)
{
  XmComboBoxWidget cb = (XmComboBoxWidget) widget;
  XGCValues	   values;
  XtGCMask	   mask;

  /* Only plain ComboBoxes have arrows. */
  if (CB_Type(cb) != XmCOMBO_BOX)
    {
      mask = 0;
      values.foreground = XtBackground(widget), mask |= GCForeground;
      values.graphics_exposures = False, 	mask |= GCGraphicsExposures;
      cb->combo_box.arrow_GC = XtGetGC(widget, mask, &values);
    }
  else
    {
      cb->combo_box.arrow_GC = NULL;
    }
}

/*
 * DrawArrow()
 */

static void
DrawArrow(Widget widget,
	  Boolean pressed)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  int newbox, size, max_height, excess;
  int x, y, w, h;

  assert(CB_Type(cb) != XmCOMBO_BOX);

  if (CB_ArrowSize(cb) == XmINVALID_DIMENSION)
    CB_ArrowSize(cb) = GetDefaultArrowSize((Widget) cb);

  /* CR 6912: Reduce size to fit the actual space available. */
  max_height = XtHeight(cb) - 
    2 * (CB_MarginHeight(cb) + CB_HighlightThickness(cb) +
	 cb->manager.shadow_thickness  + XtBorderWidth(CB_EditBox(cb)));
  if (CB_ArrowSize(cb) > max_height)
    {
      excess = CB_ArrowSize(cb) - max_height;
      size = max_height;
    }
  else
    {
      excess = 0;
      size = CB_ArrowSize(cb);
    }
  newbox = (int) (size * SQRT3_OVER_2);

  /* CR 6890: Center the arrow within the box. */
  x = CB_HitRect(cb).x + (size - newbox + excess) / 2;
  y = CB_HitRect(cb).y + (CB_HitRect(cb).height - size - 1) / 2;
  w = h = newbox;

  XmeDrawArrow(XtDisplay(widget), XtWindow(widget),
	       (pressed ?
		cb->manager.bottom_shadow_GC : cb->manager.top_shadow_GC),
	       (pressed ?
		cb->manager.top_shadow_GC : cb->manager.bottom_shadow_GC),
	       (XtIsSensitive(widget) ?
		cb->combo_box.arrow_GC : cb->manager.background_GC),
	       x, y, w, h, cb->combo_box.arrow_shadow_width, XmARROW_DOWN);

  y += newbox;
  h = size - newbox;

  XmeDrawShadows(XtDisplay(widget), XtWindow(widget),
		 cb->manager.top_shadow_GC, cb->manager.bottom_shadow_GC,
		 x, y, w, h, cb->combo_box.arrow_shadow_width, XmSHADOW_OUT);
}

/*
 * DrawShadows()
 */

static void
DrawShadows(Widget widget)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  int offset = CB_HighlightThickness(cb);

  XmeDrawShadows(XtDisplay(widget), XtWindow(widget),
		 cb->manager.top_shadow_GC,
		 cb->manager.bottom_shadow_GC,
		 offset, offset,
		 XtWidth(widget) - 2 * offset,
		 XtHeight(widget) - 2 * offset,
		 MGR_ShadowThickness(cb),
		 XmSHADOW_OUT);
}

/*
 * CreateChildren()
 *	Called by the ComboBox Widget's Initialize proc. to create
 * the children that make up a ComboBox.
 */

static void
CreateChildren(Widget    widget,
	       ArgList   arglist,
	       Cardinal *num_args)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg		   loc_args[10];
  Cardinal	   n_args;

  /*
   * N.B.:  Do not change order of creation!  The CB_EditBox() macro
   *	and code in CreateScrolledList() both rely on this order.
   */

  /* Create the editbox. */
  CreateEditBox(widget, TEXT_CHILD_NAME, widget, arglist, num_args);
  XtAddCallback(CB_EditBox(cb), XmNvalueChangedCallback,
		TextChangedCB, (XtPointer) cb);

  /* Create the popup shell if appropriate. */
  if (CB_Type(cb) != XmCOMBO_BOX)
    {
      CB_ListShell(cb) =
	CreatePulldown(widget, SHELL_CHILD_NAME, widget, arglist, num_args);

      XtAddCallback(CB_ListShell(cb), XmNpopupCallback, ShellPopupCB,
		    (XtPointer) cb);
      XtAddCallback(CB_ListShell(cb), XmNpopdownCallback, ShellPopdownCB,
		    (XtPointer) cb);

      /* Add Event Handler for pointer events in the popup */
      XtAddEventHandler(CB_ListShell(cb), POPUP_EVENT_MASK, FALSE, 
			PopupEH, (XtPointer)cb);
    }

  /* Create the list. */
  CB_List(cb) =
    CreateScrolledList(CB_ListShell(cb) ? CB_ListShell(cb) : widget, 
		       LIST_CHILD_NAME, widget, arglist, num_args);
  XtAddCallback(CB_List(cb), XmNbrowseSelectionCallback,
		(XtCallbackProc)ListSelectionCB, (XtPointer)cb);

  /* CR 9868: Direct events properly in pointer focus mode. */
  if (_XmGetFocusPolicy((Widget) cb) == XmPOINTER)
    {
      XtSetKeyboardFocus((Widget) cb, CB_EditBox(cb));
      if (CB_ListShell(cb))
	XtSetKeyboardFocus(CB_ListShell(cb), CB_List(cb));
    }

  /* Setup all the accelerators and translations. */
  switch (CB_Type(cb))
    {
    case XmCOMBO_BOX:
      /* The standard ComboBox accelerators will handle this case. */
      break;

    case XmDROP_DOWN_LIST:
      XtOverrideTranslations(CB_List(cb), parsed_list_translations);
      break;

    case XmDROP_DOWN_COMBO_BOX:
      n_args = 0;
      XtSetArg(loc_args[n_args], XmNaccelerators,
	       parsed_list_accelerators), n_args++;
      assert(n_args <= XtNumber(loc_args));
      XtSetValues(CB_EditBox(cb), loc_args, n_args);
      XtInstallAccelerators(CB_List(cb), CB_EditBox(cb));
      XtOverrideTranslations(CB_EditBox(cb), parsed_text_focus_translations);
      break;

    default:
      assert(False);
    }

  /* Install the standard ComboBox accelerators everywhere. */
  XtInstallAccelerators(CB_List(cb), (Widget) cb);
  XtInstallAccelerators(CB_EditBox(cb), (Widget) cb);

  /* Add event handlers to the scrollbar in the scrolled list widget */
  if (CB_Type(cb) != XmCOMBO_BOX) 
    {
      Widget sb;

      sb = cb->combo_box.vsb;
      if (sb != (Widget) NULL) 
	{
	  XtInsertEventHandler(sb, ButtonPressMask, False, SBBtnDownEH,
			       (XtPointer) CB_ListShell(cb), XtListHead);
	  XtInsertEventHandler(sb, ButtonReleaseMask, False, SBBtnUpEH,
			       (XtPointer) CB_ListShell(cb), XtListHead);
	}

      sb = cb->combo_box.hsb;
      if (sb != (Widget) NULL) 
	{
	  XtInsertEventHandler(sb, ButtonPressMask, False, SBBtnDownEH,
			       (XtPointer) CB_ListShell(cb), XtListHead);
	  XtInsertEventHandler(sb, ButtonReleaseMask, False, SBBtnUpEH,
			       (XtPointer) CB_ListShell(cb), XtListHead);
	}
    }
}

/*
 * CreateEditBox()
 *	Creates the EditBox child of the ComboBox.
 */

static Widget
CreateEditBox(Widget    parent,
	      String    name,
	      Widget    w,
	      ArgList   arglist,
	      Cardinal *num_args)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)w;
  Widget   	   text_widget;
  Arg      	   loc_args[15];
  Cardinal 	   nloc;
  ArgList  	   merged_args;
  char		   *item = NULL;

  /*
   * The combo-box itself was already created with this argument
   * list, so there is no need to separate "optional" and "required"
   * resources.  Our instance field values will be identical if we
   * override any duplicates.
   */
  nloc = 0;

  if (CBS_Columns(cb) != XmUNSPECIFIED_COLUMNS)
    {
      XtSetArg(loc_args[nloc], XmNcolumns, CBS_Columns(cb)), nloc++;
    }

  if (CBS_SelectedItem(cb))
    {
      item = _XmStringGetTextConcat(CBS_SelectedItem(cb));
      if (item)
	XtSetArg(loc_args[nloc], XmNvalue, item), nloc++;
    }

  XtSetArg(loc_args[nloc], XmNrenderTable, CB_RenderTable(cb)), nloc++;
  XtSetArg(loc_args[nloc], XmNnavigationType, XmNONE), nloc++;
  XtSetArg(loc_args[nloc], XmNhighlightThickness, 0), nloc++;
  XtSetArg(loc_args[nloc], XmNborderWidth, 0), nloc++;
  if (CB_Type(cb) == XmDROP_DOWN_LIST)
    {
      XtSetArg(loc_args[nloc], XmNeditable, FALSE), nloc++;
      XtSetArg(loc_args[nloc], XmNcursorPositionVisible, FALSE), nloc++;
      XtSetArg(loc_args[nloc], XmNshadowThickness, 0), nloc++;
    }
  else
    {
      XtSetArg(loc_args[nloc], XmNeditable, TRUE), nloc++;
      XtSetArg(loc_args[nloc], XmNeditMode, XmSINGLE_LINE_EDIT), nloc++;
      XtSetArg(loc_args[nloc], XmNcursorPositionVisible, TRUE), nloc++;
    }
  assert(nloc <= XtNumber(loc_args));

  /* Create the edit box. */
  merged_args = XtMergeArgLists(arglist, *num_args, loc_args, nloc);
  text_widget = XmCreateTextField(parent, name, merged_args, *num_args + nloc);
  XtFree((char *)merged_args);
  if (item)
    XtFree(item);

  XtManageChild(text_widget);
  assert(cb->composite.children[0] == text_widget);
  CB_EditBox(cb) = text_widget;

  return text_widget;
}

/*
 * CreatePulldown()
 *	Creates the pulldown shell for the list if the ComboBox is
 * a XmDROP_DOWN_COMBO_BOX or a XmDROP_DOWN_LIST.
 */

/*ARGSUSED*/
static Widget
CreatePulldown(Widget    parent,
	       String    name,
	       Widget    w,	/* unused */
	       ArgList   arglist,
	       Cardinal *num_args)
{
  Widget   shell;
  Arg      args[4];
  ArgList  merged_args;
  Cardinal n;

  n = 0;
  XtSetArg(args[n], XmNlayoutDirection, LayoutM(parent)), n++;
  XtSetArg(args[n], XmNownerEvents, True), n++;
  XtSetArg(args[n], XmNgrabStyle, GrabModeSync), n++;
  assert(n <= XtNumber(args));
  merged_args = XtMergeArgLists(arglist, *num_args, args, n);
  shell = XtCreatePopupShell(name, xmGrabShellWidgetClass, parent,
			     merged_args, n + *num_args);
  XtFree((char*)merged_args);

  return shell;
}

/*
 * CreateScrolledList()
 *	Creates the Scrolled List child of the ComboBox.  If the
 * ComboBox is a XmDROP_DOWN_COMBO_BOX or a XmDROP_DOWN_LIST it
 * creates the list in a popup shell.
 */

static Widget
CreateScrolledList(Widget    parent,
		   String    name,
		   Widget    w,
		   ArgList   arglist,
		   Cardinal *num_args)
{
  Cardinal n;
  Arg loc_args[16];
  Widget list;
  XmComboBoxWidget cb = (XmComboBoxWidget)w;
  ArgList merged_args;
  int pos, nitems = 0;
  Boolean setpos = FALSE;
  XmString *items;

  n = 0;

  if (CBS_Items(cb) != XmUNSPECIFIED_ITEMS)
    {
      XtSetArg(loc_args[n], XmNitems, CBS_Items(cb)), n++;
    }

  if (CBS_ItemCount(cb) != XmUNSPECIFIED_COUNT)
    {
      XtSetArg(loc_args[n], XmNitemCount, CBS_ItemCount(cb)), n++;
    }

  if (CBS_VisibleItemCount(cb) != XmUNSPECIFIED_COUNT)
    {
      XtSetArg(loc_args[n], XmNvisibleItemCount, CBS_VisibleItemCount(cb)), n++;
    }

  if (CBS_SelectedItem(cb))
    {
      XtSetArg(loc_args[n], XmNselectedItems, &CBS_SelectedItem(cb)), n++;
      XtSetArg(loc_args[n], XmNselectedItemCount, 1), n++;
    }
  else
    {
      if (CBS_SelectedPosition(cb) == XmINVALID_POSITION)
	pos = 1;  /* Issue 138 */
      else if (CB_PositionMode(cb) == XmZERO_BASED)
	pos = CBS_SelectedPosition(cb) + 1;
      else
	pos = CBS_SelectedPosition(cb);
      XtSetArg(loc_args[n], XmNselectedPositions, &pos), n++;
      XtSetArg(loc_args[n], XmNselectedPositionCount, 1), n++;
      setpos = TRUE;
    }

  XtSetArg(loc_args[n], XmNrenderTable, CB_RenderTable(cb)), n++;
  if (CB_Type(cb) == XmCOMBO_BOX)
    XtSetArg(loc_args[n], XmNtraversalOn, FALSE), n++;
  XtSetArg(loc_args[n], XmNhighlightThickness,
	   ((CB_Type(cb) == XmDROP_DOWN_LIST) ? 2 : 0)), n++;
  XtSetArg(loc_args[n], XmNborderWidth, 0), n++;
  XtSetArg(loc_args[n], XmNnavigationType, XmNONE), n++;
  XtSetArg(loc_args[n], XmNselectionPolicy, XmBROWSE_SELECT), n++;
  XtSetArg(loc_args[n], XmNlistSizePolicy, XmVARIABLE), n++;
  XtSetArg(loc_args[n], XmNspacing, 0), n++;
  XtSetArg(loc_args[n], XmNvisualPolicy, XmVARIABLE), n++;
  assert(n <= XtNumber(loc_args));
  merged_args = XtMergeArgLists(arglist, *num_args, loc_args, n);

  list = XmCreateScrolledList(parent, name, merged_args, n + *num_args);
  XtManageChild(list);
  XtFree((char *)merged_args);
  CB_ScrolledW(cb) = XtParent(list);

  n = 0;
  XtSetArg(loc_args[n], XmNhorizontalScrollBar, &cb->combo_box.hsb), n++;
  XtSetArg(loc_args[n], XmNverticalScrollBar, &cb->combo_box.vsb), n++;
  assert(n <= XtNumber(loc_args));
  XtGetValues(CB_ScrolledW(cb), loc_args, n);

  n = 0;
  XtSetArg(loc_args[n], XmNshadowThickness, 0), n++;
  assert(n <= XtNumber(loc_args));
  XtSetValues(list, loc_args, n);

  if (setpos)
    {
      n = 0;
      XtSetArg(loc_args[n], XmNitems, &items), n++;
      XtSetArg(loc_args[n], XmNitemCount, &nitems), n++;
      assert(n <= XtNumber(loc_args));
      XtGetValues(list, loc_args, n);

      /* CR 7064: Don't try to set the edit box value if no items exist. */
      if (nitems && (nitems >= pos))
	{
	  if (pos > 0)
	    pos--;

	  SetEditBoxValue((Widget) cb, items[pos]);
	}
    }

  return list;
}

/*
 * Hit()
 *	Decide whether a button event happened within a particular XRectangle.
 */

static Boolean
Hit(XButtonEvent* event,
    XRectangle r)
{
  if (event == NULL)
    return False;
  else
    return ((r.x <= event->x) && (event->x <= (r.x + r.width)) &&
	    (r.y <= event->y) && (event->y <= (r.y + r.height)));
}

/*
 * GetEditBoxValue()
 *	Retrieve the XmString value of the edit box.
 */

static XmString
GetEditBoxValue(Widget cb)
{
  Widget	       edit_box = CB_EditBox(cb);
  XmAccessTextualTrait textTrait;

  textTrait = (XmAccessTextualTrait)
    XmeTraitGet((XtPointer) XtClass(edit_box), XmQTaccessTextual);

  if (textTrait)
    return (XmString) textTrait->getValue(edit_box, XmFORMAT_XmSTRING);
  else
    return NULL;
}

/*
 * SetEditBoxValue()
 *	Set the XmString value of the edit box.
 */

static void
SetEditBoxValue(Widget   cb,
		XmString value)
{
  Widget 	       edit_box = CB_EditBox(cb);
  XmAccessTextualTrait textTrait;

  textTrait = (XmAccessTextualTrait)
    XmeTraitGet((XtPointer) XtClass(edit_box), XmQTaccessTextual);

  textTrait->setValue(edit_box, value, XmFORMAT_XmSTRING);
}

/* ------------- CONVENIENCE FUNCTIONS ---------- */


Widget
XmCreateComboBox(Widget   parent,
		 char    *name,
		 ArgList  args,
		 Cardinal num_args)
{
  return XtCreateWidget(name, xmComboBoxWidgetClass, parent, args, num_args);
}

Widget
XmCreateDropDownComboBox(Widget   parent,
			 char    *name,
			 ArgList  args,
			 Cardinal num_args)
{
  Arg loc_args[5];
  Cardinal n;
  ArgList merged_args;
  Widget result;

  n = 0;
  XtSetArg(loc_args[n], XmNcomboBoxType, XmDROP_DOWN_COMBO_BOX), n++;
  assert(n <= XtNumber(loc_args));

  merged_args = XtMergeArgLists(args, num_args, loc_args, n);
  result = XtCreateWidget(name, xmComboBoxWidgetClass, parent,
			  merged_args, n + num_args);
  XtFree((char *)merged_args);

  return result;
}

Widget
XmCreateDropDownList(Widget   parent,
		     char    *name,
		     ArgList  args,
		     Cardinal num_args)
{
  Arg loc_args[5];
  Cardinal n;
  ArgList merged_args;
  Widget result;

  n = 0;
  XtSetArg(loc_args[n], XmNcomboBoxType, XmDROP_DOWN_LIST), n++;
  assert(n <= XtNumber(loc_args));

  merged_args = XtMergeArgLists(args, num_args, loc_args, n);
  result = XtCreateWidget(name, xmComboBoxWidgetClass, parent,
			  merged_args, n + num_args);
  XtFree((char *)merged_args);

  return result;
}

/*
 * XmComboBoxAddItem
 *	Convenience function added for CDE compatibility.  Add an item
 * to the ComboBox list.
 */

void
XmComboBoxAddItem(Widget   widget,
		  XmString item,
		  int      pos,
		  Boolean  unique)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;

  _XmWidgetToAppContext(widget);
  _XmAppLock(app);

  if (!XmIsComboBox(widget))
    {
      XmeWarning(widget, NOTACOMBOBOX);
      _XmAppUnlock(app);
      return;
    }
  else if (!CB_List(cb)) {
    _XmAppUnlock(app);
    return;
  }

  /* If requested, scan for duplicate items. */
  if (unique && item && XmListItemExists(CB_List(cb), item)) {
    _XmAppUnlock(app);
    return;
  }

  /* Add the item to the list and update our selected_position. */
  XmListAddItemUnselected(CB_List(cb), item, pos);
  XmComboBoxUpdate(widget);
  _XmAppUnlock(app);
}

/*
 * XmComboBoxDeletePos
 *	Convenience function added for CDE compatibility.  Delete the
 * list item at the indicated position.
 */

void
XmComboBoxDeletePos(Widget widget,
		    int    pos)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  int selpos;
  int nitems;

  _XmWidgetToAppContext(widget);
  _XmAppLock(app);

  if (!XmIsComboBox(widget))
    {
      XmeWarning(widget, NOTACOMBOBOX);
      _XmAppUnlock(app);
      return;
    }
  else if (!CB_List(cb)) {
    _XmAppUnlock(app);
    return;
  }

  /* Validate the position. */
  {
    Arg args[10];
    Cardinal nargs;
    int *positions;
    int count;

    nargs = 0;
    XtSetArg(args[nargs], XmNitemCount, &nitems),		nargs++;
    XtSetArg(args[nargs], XmNselectedPositions, &positions),	nargs++;
    XtSetArg(args[nargs], XmNselectedPositionCount, &count),	nargs++;
    assert(nargs <= XtNumber(args));
    XtGetValues(CB_List(widget), args, nargs);

    /* Ensure that we're using a one-based position. */
    selpos = (count > 0) ? *positions : 0;
  }

  /* DtComboBoxDeletePos rejects pos == 0, but we allow it. */
  if ((pos < 0) || (pos > nitems) || (nitems <= 0))
    {
      XmeWarning(widget, DELETEBADPOS);
      _XmAppUnlock(app);
      return;
    }

  /* Delete this item. */
  XmListDeletePos(CB_List(cb), pos);

  /* If our selected item just got deleted select seomthing else. */
  if ((pos ? pos : nitems) == selpos)
    {
      if (nitems > 1)
	{
	  /* Invoke the callbacks now. */
	  XmListSelectPos(CB_List(cb), selpos, True);
	}
      else
	{
	  /* The list is now empty. */
	  CB_TextChanged(cb) = FALSE;
	  XmComboBoxUpdate(widget);
	  CB_TextChanged(cb) = FALSE;
	}
    }

  _XmAppUnlock(app);
}

/*
 * XmComboBoxSelectItem
 *	Convenience function added for CDE compatibility.  Select the
 * indicated item.  Do not invoke the callbacks or scroll the list.
 */

void 
XmComboBoxSelectItem(Widget   widget,
		     XmString item)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  int pos;

  _XmWidgetToAppContext(widget);
  _XmAppLock(app);

  if (!XmIsComboBox(widget))
    {
      XmeWarning(widget, NOTACOMBOBOX);
      _XmAppUnlock(app);
      return;
    }
  else if (!CB_List(cb)) {
    _XmAppUnlock(app);
    return;
  }
     

  /* Calculate the position of this item. */
  pos = XmListItemPos(CB_List(cb), item);

  if (pos > 0)
    {
      /* Select the indicated item. */
      XmListDeselectAllItems(CB_List(cb));
      XmListSelectPos(CB_List(cb), pos, FALSE);

      /* Discard user changes to the edit box and suppress all callbacks. */
      CB_TextChanged(cb) = FALSE;
      XmComboBoxUpdate(widget);
      CB_TextChanged(cb) = FALSE;
    }
  else
    XmeWarning(widget, SELECTBADITEM);

  _XmAppUnlock(app);
}

/*
 * XmComboBoxSetItem
 *	Convenience function added for CDE compatibility.  Select the
 * indicated item and force it to be visible.  Do not invoke the callbacks.
 */

void 
XmComboBoxSetItem(Widget   widget,
		  XmString item)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  int pos;

  _XmWidgetToAppContext(widget);
  _XmAppLock(app);

  if (!XmIsComboBox(widget))
    {
      XmeWarning(widget, NOTACOMBOBOX);
      _XmAppUnlock(app);
      return;
    }
  else if (!CB_List(cb)) {
    _XmAppUnlock(app);
    return;
  }

  /* Calculate the position of this item. */
  pos = XmListItemPos(CB_List(cb), item);

  if (pos > 0)
    {
      /* Scroll the item to the top and select it. */
      XmListSetPos(CB_List(cb), pos);
      XmListSelectPos(CB_List(cb), pos, FALSE);

      /* Discard user changes to the edit box and suppress all callbacks. */
      CB_TextChanged(cb) = FALSE;
      XmComboBoxUpdate(widget);
      CB_TextChanged(cb) = FALSE;
    }
  else
    XmeWarning(widget, SETBADITEM);

  _XmAppUnlock(app);
}

/*
 * XmComboBoxUpdate()
 *	Resynchronize internal data structures after an application
 * updates our children directly.
 */

void
XmComboBoxUpdate(Widget widget)
{
  XmComboBoxWidget cb = (XmComboBoxWidget)widget;
  Arg args[10];
  Cardinal nargs;
  XmString *items;
  int icount;
  int *pos;
  int pcount;

  _XmWidgetToAppContext(widget);
  _XmAppLock(app);

  if (!XmIsComboBox(widget))
    {
      XmeWarning(widget, NOTACOMBOBOX);
      _XmAppUnlock(app);
      return;
    }
  else if (!CB_List(cb)) {
    _XmAppUnlock(app);
    return;
  }

  /* CR 8445: If no text entry is in progress echo the new list selection. */
  if (!CB_TextChanged(cb))
    {
      /* The selected item and position of the list may have changed. */
      nargs = 0;
      XtSetArg(args[nargs], XmNselectedPositions, &pos), 	nargs++;
      XtSetArg(args[nargs], XmNselectedPositionCount, &pcount), nargs++;
      XtSetArg(args[nargs], XmNitems, &items),			nargs++;
      XtSetArg(args[nargs], XmNitemCount, &icount),		nargs++;
      assert(nargs <= XtNumber(args));
      XtGetValues(CB_List(cb), args, nargs);

      if ((pcount > 0) && (icount > 0))
	SetEditBoxValue((Widget) cb, items[pos[0] - 1]);
    }
    _XmAppUnlock(app);
}
