/*
 *
 *  Copyright (C) 2000 Silicon Graphics, Inc.  All Rights Reserved. 
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  Further, this software is distributed without any warranty that it is
 *  free of the rightful claim of any third person regarding infringement
 *  or the like.  Any license provided herein, whether implied or
 *  otherwise, applies only to this software file.  Patent licenses, if
 *  any, provided herein do not apply to combinations of this program with
 *  other software, or any other product whatsoever.
 * 
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 *  Mountain View, CA  94043, or:
 * 
 *  http://www.sgi.com 
 * 
 *  For further information regarding this notice, see: 
 * 
 *  http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */

/*
 * Copyright (C) 1990,91   Silicon Graphics, Inc.
 *
 _______________________________________________________________________
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 |
 |   $Revision: 1.5 $
 |
 |   Classes:
 |	_SoXtColorEditor
 |
 |   Author(s)	: Alain Dumesny
 |
 |
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 _______________________________________________________________________
 */

#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/SoLists.h>
#include <Inventor/SoPath.h>
#include <Inventor/sensors/SoNodeSensor.h>
#include <Inventor/fields/SoMFColor.h>
#include <Inventor/fields/SoSFColor.h>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtResource.h>
#include <Inventor/Xt/SoXtClipboard.h>
#include <Inventor/errors/SoDebugError.h>
#include "_SoXtColorPatch.h"
#include "_SoXtColorEditor.h"
#include "_SoXtColorSlider.h"
#include "_SoXtColorWheel.h"


#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/PushBG.h>
#include <Xm/CascadeB.h>
#include <Xm/CascadeBG.h>
#include <Xm/BulletinB.h>
#include <Xm/Separator.h>
#include <Xm/SeparatoG.h>
#include <Xm/ToggleB.h>
#include <Xm/ToggleBG.h>

#include <cstdlib>

/*
 * Defines
 */
#define SCREEN(w) XScreenNumberOfScreen( XtScreen(w) )

// default window sizes, and layout positions
#define DEFAULT_WIDTH 			280
#define DEFAULT_HEIGHT 			170
#define TOP_REGION_SIZE	    	    	4.1   // size of the upper half in sliders number
#define BUTTONS_FORM_RIGHT_POSITION	50
// pixels offset used for placing things
#define OFFSET	    	    	    	5

// ID list for all parts of the color editor
enum {
    R_SLIDER_ID = 0,	// convenient to have it start a 0
    G_SLIDER_ID, 
    B_SLIDER_ID, 
    H_SLIDER_ID, 
    S_SLIDER_ID, 
    V_SLIDER_ID,
    COLOR_WHEEL_ID, 
    SAVE_ID, 
    SWAP_ID, 
    RESTORE_ID,
    ACCEPT_ID,
    CONTINUOUS_ID,
    MANUAL_ID,
    NONE_SLIDER_ID, 
    INTENSITY_SLIDER_ID, 
    RGB_SLIDERS_ID, 
    HSV_SLIDERS_ID, 
    RGB_V_SLIDERS_ID, 
    RGB_HSV_SLIDERS_ID, 
    WYSIWYG_ID, 
    COPY_ID, 
    PASTE_ID, 
    HELP_ID, 
    NUM_IDS	// this must be last
};

// the menu items which toggle
enum {
    CONTINUOUS_TOGGLE = 0, 	// convenient to start at 0
    ACCEPT_TOGGLE, 
    WYSIWYG_TOGGLE, 
    NONE_TOGGLE, 
    INTENSITY_TOGGLE, 
    RGB_TOGGLE, 
    HSV_TOGGLE, 
    RGB_V_TOGGLE, 
    RGB_HSV_TOGGLE, 
    NUM_TOGGLES  	// this must be last
};

#define TOGGLE_ON(BUTTON) \
    XmToggleButtonSetState((Widget) BUTTON, TRUE, FALSE)
#define TOGGLE_OFF(BUTTON) \
    XmToggleButtonSetState((Widget) BUTTON, FALSE, FALSE)

//
// struct used for internal callbacks
//
typedef struct _ColorEditorCBData {
    short   id;
    class _SoXtColorEditor    *classPtr;
} ColorEditorCBData;



/*
 * Globals vars
 */

// strings used in motif buttons/menus
static char *slider_labels[] = { "R", "G", "B", "H", "S", "V"};
static char *button_names[] = { "right", "switch", "left"};
static char *edit_menu[] = { "Continuous", "Manual", 
    	    	    	    "sep", "WYSIWYG",
			    "sep", "Copy", "Paste", 
			    "sep", "Help"};
static char *slider_menu[] = { "None", "Value", "RGB", "HSV", 
    	    	"RGB V", "RGB HSV"};

// arrow pointing to the right
#define right_width 24
#define right_height 12
static char right_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xc0, 0x01,
   0x00, 0xc0, 0x07, 0xf0, 0xff, 0x1f, 0xf0, 0xff, 0x1f, 0x00, 0xc0, 0x07,
   0x00, 0xc0, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

// switch arrow
#define switch_width 24
#define switch_height 12
static char switch_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x02, 0x70, 0x00, 0x0e,
   0x7c, 0x00, 0x3e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0x00, 0x3e,
   0x70, 0x00, 0x0e, 0x40, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

// arrow pointing to the left
#define left_width 24
#define left_height 12
static char left_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x80, 0x03, 0x00,
   0xe0, 0x03, 0x00, 0xf8, 0xff, 0x0f, 0xf8, 0xff, 0x0f, 0xe0, 0x03, 0x00,
   0x80, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};


static char *thisClassName = "_SoXtColorEditor";


////////////////////////////////////////////////////////////////////////
//
// Public constructor - build the widget right now
//
_SoXtColorEditor::_SoXtColorEditor(
    Widget parent,
    const char *name, 
    SbBool buildInsideParent)
	: SoXtComponent(parent, name, buildInsideParent)
//
////////////////////////////////////////////////////////////////////////
{
    // In this case, this component is what the app wants, so buildNow = TRUE
    constructorCommon(TRUE);
}

////////////////////////////////////////////////////////////////////////
//
// SoEXTENDER constructor - the subclass tells us whether to build or not
//
_SoXtColorEditor::_SoXtColorEditor(
    Widget parent,
    const char *name, 
    SbBool buildInsideParent, 
    SbBool buildNow)
	: SoXtComponent(parent, name, buildInsideParent)
//
////////////////////////////////////////////////////////////////////////
{
    // In this case, this component may be what the app wants, 
    // or it may want a subclass of this component. Pass along buildNow
    // as it was passed to us.
    constructorCommon(buildNow);
}

////////////////////////////////////////////////////////////////////////
//
// Called by the constructors
//
// private
//
void
_SoXtColorEditor::constructorCommon(SbBool buildNow)
//
//////////////////////////////////////////////////////////////////////
{	
    int i;
    
    // init local vars
    setClassName(thisClassName);
    addVisibilityChangeCallback(visibilityChangeCB, this);
    WYSIWYGmode = FALSE;
    whichSliders = INTENSITY;
    baseRGB[0] = baseRGB[2] = 1.0;
    baseRGB[1] = 0.0;
    baseRGB.getHSVValue(baseHSV);
    acceptButton = slidersForm = NULL;
    mgrWidget = NULL;
    updateFreq = CONTINUOUS;
    
    // copy/paste support
    clipboard = NULL;
    
    // default size
    setSize( SbVec2s(DEFAULT_WIDTH, DEFAULT_HEIGHT) );
    
    // color field vars
    attached = FALSE;
    colorSF = NULL;
    colorMF = NULL;
    editNode = NULL;
    colorSensor = new SoNodeSensor(_SoXtColorEditor::fieldChangedCB, this);
    
    // init callbacks data Ids
    dataId = (ColorEditorCBData *) malloc(sizeof(ColorEditorCBData) * NUM_IDS);
    for (i=0; i<NUM_IDS; i++) {
	dataId[i].id = i;	    // since Ids start at 0
	dataId[i].classPtr = this;
    }
    
    // user callbacks
    callbackList = new SoCallbackList;
    ignoreCallback = FALSE;
    
    // NULL out UI components. We'll create them in buildWidget().
    wheel = NULL;
    current = NULL;
    previous = NULL;
    for (i=0; i<6; i++)
	sliders[i] = NULL;
    
    // Build the widget tree, and let SoXtComponent know about our base widget.
    if (buildNow) {
	Widget w = buildWidget(getParentWidget());
	setBaseWidget(w);
    }
}


////////////////////////////////////////////////////////////////////////
//
//    Destructor.
//

_SoXtColorEditor::~_SoXtColorEditor()
//
////////////////////////////////////////////////////////////////////////
{
    unregisterWidget(mgrWidget);
    
    // detaches itself and remove sensor
    if ( isAttached() )
    	detach();
    
    // delete everything
    free(dataId);
    delete clipboard;
    delete callbackList;
    delete wheel;
    delete current;
    delete previous;
    for (int i=0; i<6; i++)
	delete sliders[i];
}


////////////////////////////////////////////////////////////////////////
//
//    This routine builds all the widgets, and do the layout using motif.
//
// usage: private
//
Widget
_SoXtColorEditor::buildWidget(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
    Widget  menubar;
    int	    n;
    Arg     args[12];
    
    //
    // create a top level form to hold everything together
    //
    
    SbVec2s size = getSize();
    n = 0;
    if ((size[0] != 0) && (size[1] != 0)) {
	XtSetArg(args[n], XtNwidth, size[0]); n++;
	XtSetArg(args[n], XtNheight, size[1]); n++;
    }
    
    // create the top level widget, then register it with a class name
    mgrWidget = XtCreateWidget(getWidgetName(), xmFormWidgetClass, parent, args, n);
    registerWidget(mgrWidget);
    
    //
    // build top level components
    //
    menubar = buildPulldownMenu(mgrWidget);
    buttonsForm = buildControls(mgrWidget);

    //
    // allocate color wheel
    //
    wheel = new _SoXtColorWheel(mgrWidget);
    wheel->setBaseColor(baseHSV);
    wheel->addValueChangedCallback(&_SoXtColorEditor::wheelCallback, this);
    wheelForm = wheel->getWidget();

    slidersForm = buildSlidersForm(mgrWidget);
    
    //
    // layout !
    //
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition,     BUTTONS_FORM_RIGHT_POSITION); n++;
    XtSetArg(args[n], XmNbottomAttachment,  XmATTACH_NONE); n++;
    XtSetValues(menubar, args, n);
    
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget,         menubar); n++;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition,     BUTTONS_FORM_RIGHT_POSITION); n++;
    // Note: bottom attachment changes dynamically
    XtSetValues(buttonsForm, args, n);
    
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, 	    buttonsForm); n++;
    // Note: bottom attachment changes dynamically
    XtSetValues(wheelForm, args, n);
    
    n = 0;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightOffset,       OFFSET); n++;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftOffset,        OFFSET); n++;
    XtSetArg(args[n], XmNbottomAttachment,  XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomOffset,      OFFSET); n++;
    // Note: top attachment changes dynamically
    XtSetValues(slidersForm, args, n);
    
    doDynamicTopLevelLayout();
    
    // manage all widgets
    XtManageChild(menubar);
    XtManageChild(buttonsForm);
    XtManageChild(wheelForm);
    
    
    //
    // get default values from X resource!
    //
    SoXtResource xr(mgrWidget);
    char *val;
    SbBool b;
    
    if (xr.getResource("wysiwyg", "Wysiwyg", b))
	 setWYSIWYG(b);
    
    //??? we could get the quark for each string, then get the quark for val,
    //??? and compare quarks - it might be faster.
    // strcasecmp is case insensitive
    if (xr.getResource("colorSliders", "ColorSliders", val)) {
	 if      (strcasecmp(val, "none") == 0)  
	     setCurrentSliders(NONE);
	 else if (strcasecmp(val, "intensity") == 0)  
	     setCurrentSliders(INTENSITY);
	 else if (strcasecmp(val, "rgb") == 0)  
	     setCurrentSliders(RGB);
	 else if (strcasecmp(val, "hsv") == 0)  
	     setCurrentSliders(HSV);
	 else if (strcasecmp(val, "rgb_v") == 0)  
	     setCurrentSliders(RGB_V);
	 else if (strcasecmp(val, "rgb_hsv") == 0)  
	     setCurrentSliders(RGB_HSV);
    }
    
    //??? should the base class do the check for continuous and manual?
    if (xr.getResource("updateFrequency", "UpdateFrequency", val)) {
	 if      (strcasecmp(val, "continuous") == 0)  
	     setUpdateFrequency(CONTINUOUS);
	 else if (strcasecmp(val, "manual") == 0)  
	     setUpdateFrequency(AFTER_ACCEPT);
    }
    
    return mgrWidget;
}


////////////////////////////////////////////////////////////////////////
//
//    builds the pulldown menu
//
// usage: private

Widget
_SoXtColorEditor::buildPulldownMenu(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
    Widget  menuw[2], sub1w[15], sub2w[15];
    int	    num1 = 0, num2 = 0;
    int	    i, n;
    Arg     args[2];
    
    //
    // create the pulldown menu
    //
    Widget menubar = XmCreateMenuBar(parent, "menuBar", NULL, 0);
    
    // NOTE: menu items must be created in this order!
    menuItems.truncate(0);
    

    Arg popupargs[4];
    int popupn = 0;
#ifdef MENUS_IN_POPUP
    SoXt::getPopupArgs(XtDisplay(menubar), SCREEN(menubar), popupargs, &popupn);
#endif
    
    
    //
    // SUBMENU 1
    //
    Widget sub_menu1 = XmCreatePulldownMenu(menubar, "sub_menu1", popupargs, popupn);
#ifdef MENUS_IN_POPUP
    // register callbacks to load/unload the pulldown colormap
    SoXt::registerColormapLoad(sub_menu1, SoXt::getShellWidget(parent));
#endif
    n = 0;
    XtSetArg(args[n], XmNsubMenuId, sub_menu1); n++;
    menuw[0] = XtCreateWidget("Edit", xmCascadeButtonGadgetClass, 
	menubar, args, n);
    
    // CONTINUOUS_ID,
    // MANUAL_ID,
    n = 0;
    XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
    for (i=0; i<2; i++) {
	sub1w[num1] = XtCreateWidget(edit_menu[num1],
	    xmToggleButtonGadgetClass, sub_menu1, args, n);
	XtAddCallback(sub1w[num1], XmNvalueChangedCallback, 
	    (XtCallbackProc) &_SoXtColorEditor::editMenuCallback, 
	    (XtPointer) &dataId[CONTINUOUS_ID+i]);
	menuItems.append(sub1w[num1]);
	num1++;
    }
    
    // SEPARATOR
    sub1w[num1] = XtCreateWidget(edit_menu[num1], xmSeparatorGadgetClass,
	sub_menu1, NULL, 0);
    num1++;
    
    // WYSIWYG_ID
    sub1w[num1] = XtCreateWidget(edit_menu[num1], xmToggleButtonGadgetClass,
	    sub_menu1, NULL, 0);
    XtAddCallback(sub1w[num1], XmNvalueChangedCallback, 
	(XtCallbackProc) &_SoXtColorEditor::editMenuCallback, 
	(XtPointer) &dataId[WYSIWYG_ID]);
    menuItems.append(sub1w[num1]);
    num1++;
    
    // SEPARATOR
    sub1w[num1] = XtCreateWidget(edit_menu[num1],
	xmSeparatorGadgetClass, sub_menu1, NULL, 0);
    num1++;
    
    // COPY_ID,
    // PASTE_ID,
    for (i=0; i<2; i++) {
	sub1w[num1] = XtCreateWidget(edit_menu[num1],
	    xmPushButtonGadgetClass, sub_menu1, NULL, 0);
	XtAddCallback(sub1w[num1], XmNactivateCallback, 
	    (XtCallbackProc) &_SoXtColorEditor::editMenuCallback, 
	    (XtPointer) &dataId[COPY_ID+i]);
	num1++;
	// we do not append these to menuItems; since they are push buttons
	// and not toggle buttons, we don't need to save them for future updates
    }
    
    // SEPARATOR
    sub1w[num1] = XtCreateWidget(edit_menu[num1],
	xmSeparatorGadgetClass, sub_menu1, NULL, 0);
    num1++;
    
    // HELP_ID
    sub1w[num1] = XtCreateWidget(edit_menu[num1],
	xmPushButtonGadgetClass, sub_menu1, NULL, 0);
    XtAddCallback(sub1w[num1], XmNactivateCallback, 
	(XtCallbackProc) &_SoXtColorEditor::editMenuCallback, 
	(XtPointer) &dataId[HELP_ID]);
    num1++;
    
    //
    // SUBMENU 2
    //
    Widget sub_menu2 = XmCreatePulldownMenu(menubar, "sub_menu2", popupargs, popupn);
#ifdef MENUS_IN_POPUP
    // register callbacks to load/unload the pulldown colormap
    SoXt::registerColormapLoad(sub_menu2, SoXt::getShellWidget(parent));
#endif
    n = 0;
    XtSetArg(args[n], XmNsubMenuId, sub_menu2); n++;
    menuw[1] = XtCreateWidget("Sliders", xmCascadeButtonGadgetClass, 
		menubar, args, n);
    
    // NONE
    // INTENSITY
    // RGB
    // HSV
    // RGB_V
    // RGB_HSV
    n = 0;
    XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
    for (i=0; i<6; i++) {
	sub2w[num2] = XtCreateWidget(slider_menu[num2],
	    xmToggleButtonGadgetClass, sub_menu2, args, n);
	XtAddCallback(sub2w[num2], XmNvalueChangedCallback, 
	    (XtCallbackProc) &_SoXtColorEditor::sliderMenuCallback, 
	    (XtPointer) &dataId[NONE_SLIDER_ID+i]);
	menuItems.append(sub2w[num2]);
	num2++;
    }
    
    
    // menu display callback so we can change the menu item
    XtAddCallback(sub_menu1, XmNmapCallback,
	(XtCallbackProc) _SoXtColorEditor::menuDisplay, (XtPointer) this);
    XtAddCallback(sub_menu2, XmNmapCallback,
	(XtCallbackProc) _SoXtColorEditor::menuDisplay, (XtPointer) this);
    
    
    // manage all children
    XtManageChildren(sub1w, num1);
    XtManageChildren(sub2w, num2);
    XtManageChildren(menuw, 2);
    
    return menubar;
}


////////////////////////////////////////////////////////////////////////
//
//    builds the color swatches, arrow buttons and accept button.
//
// usage: private

Widget
_SoXtColorEditor::buildControls(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
    Widget  curW, prevW, buttonw[3], patchButForm;
    int	    i, n;
    Arg     args[12];
    
    //
    // build the buttons form
    //
    buttonsForm = XtCreateWidget("buttonsForm", xmFormWidgetClass, parent, NULL, 0);
    
    // build swatches
    current = new _SoXtColorPatch(buttonsForm, "Current");
    current->setColor(baseRGB);
    curW = current->getWidget();
    previous = new _SoXtColorPatch(buttonsForm, "Previous");
    previous->setColor(baseRGB);    
    prevW = previous->getWidget();
    
    //
    // create the arrow buttons (use a form to lay them inside)
    //
    patchButForm = XtCreateWidget("patchButForm", xmFormWidgetClass, 
	buttonsForm, NULL, 0);
    n = 0;
    XtSetArg(args[n], XmNhighlightThickness, 0); n++;
    // ??? bug 228368 prevents the pixmap from also highlighting
#ifdef __sgi
    XtSetArg(args[n], SgNpixmapLocateHighlight, True); n++;
#endif // __sgi
    for (i=0; i<3; i++) {
	buttonw[i] = XtCreateWidget(button_names[i], xmPushButtonGadgetClass,
	    patchButForm, args, n);
	XtAddCallback(buttonw[i], XmNactivateCallback, 
		    (XtCallbackProc) &_SoXtColorEditor::buttonsCallback, 
		    (XtPointer) &dataId[SAVE_ID+i]);
    }
    
    //
    // create the pixmaps for the arrow buttons
    //
    Pixmap	pixmaps[3][2];
    Display	*display = XtDisplay(parent);
    Drawable	d = RootWindow(display, SCREEN(parent));
    Pixel	fg, bg, hbg;
    
    // get the color of the push buttons
    XtVaGetValues(XtParent(buttonw[0]), XmNforeground, &fg, XmNbackground, &bg, NULL);
#ifdef __sgi
    hbg = SgGetLocatePixel(XtParent(buttonw[0]), bg);
#else
#define hbg bg
#endif // __sgi
    
    // create the pixmaps from the bitmap data (depth 1).
    // Two sets of pixmaps are created for when the button is
    // up and down.
    int depth;
    XtVaGetValues( SoXt::getShellWidget(parent), XtNdepth, &depth, NULL );
    pixmaps[0][0] = XCreatePixmapFromBitmapData(display, d, 
	right_bits, right_width, right_height, fg, bg, depth);
    pixmaps[0][1] = XCreatePixmapFromBitmapData(display, d, 
	right_bits, right_width, right_height, fg, hbg, depth);
    pixmaps[1][0] = XCreatePixmapFromBitmapData(display, d, 
	switch_bits, switch_width, switch_height, fg, bg, depth);
    pixmaps[1][1] = XCreatePixmapFromBitmapData(display, d, 
	switch_bits, switch_width, switch_height, fg, hbg, depth);
    pixmaps[2][0] = XCreatePixmapFromBitmapData(display, d, 
	left_bits, left_width, left_height, fg, bg, depth);
    pixmaps[2][1] = XCreatePixmapFromBitmapData(display, d, 
	left_bits, left_width, left_height, fg, hbg, depth);
    
    // assign the pixmaps to the push buttons
    XtSetArg(args[0], XmNlabelType, XmPIXMAP);
    for (i=0; i<3; i++) {
	XtSetArg(args[1], XmNlabelPixmap, pixmaps[i][0]);
#ifdef __sgi
	XtSetArg(args[2], SgNlocatePixmap, pixmaps[i][1]);
	XtSetValues(buttonw[i], args, 3);
#else
	XtSetValues(buttonw[i], args, 2);
#endif // __sgi
    }
    
    //
    // build the accept button
    //
    n = 0;
    XtSetArg(args[n], XmNhighlightThickness, 0); n++;
    acceptButton = XtCreateWidget("Accept", xmPushButtonGadgetClass, 
		buttonsForm, args, n);
    XtAddCallback(acceptButton, XmNactivateCallback, 
	(XtCallbackProc) &_SoXtColorEditor::buttonsCallback, 
	(XtPointer) &dataId[ACCEPT_ID]);
    
    //
    // layout !
    //
    n = 0;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNleftPosition, 	    10); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition,     49); n++;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNtopPosition, 	    5); n++;
    XtSetArg(args[n], XmNbottomAttachment,  XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNbottomPosition,    45); n++;
    XtSetValues(curW, args, n);
    XtSetArg(args[1], XmNleftPosition, 	    51);
    XtSetArg(args[3], XmNrightPosition,     90);
    XtSetValues(prevW, args, n);
    
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment,  XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_POSITION); n++;
    XtSetArg(args[3], XmNleftPosition, 	    0); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_POSITION); n++;
    XtSetArg(args[5], XmNrightPosition,     30); n++;
    XtSetValues(buttonw[0], args, n);
    XtSetArg(args[3], XmNleftPosition, 	    31);
    XtSetArg(args[5], XmNrightPosition,     69);
    XtSetValues(buttonw[1], args, n);
    XtSetArg(args[3], XmNleftPosition, 	    70);
    XtSetArg(args[5], XmNrightPosition,     100);
    XtSetValues(buttonw[2], args, n);
    
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, 	    curW); n++;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_OPPOSITE_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, 	    curW); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_OPPOSITE_WIDGET); n++;
    XtSetArg(args[n], XmNrightWidget, 	    prevW); n++;
    XtSetArg(args[n], XmNbottomAttachment,  XmATTACH_NONE); n++;
    XtSetValues(patchButForm, args, n);
    
    int offset = (whichSliders == NONE) ? 0 : OFFSET ;
    n = 0;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNleftPosition, 	    30); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition,     70); n++;
    XtSetArg(args[n], XmNtopAttachment,     XmATTACH_NONE); n++;
    XtSetArg(args[n], XmNbottomAttachment,  XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomOffset,      offset); n++;
    XtSetValues(acceptButton, args, n);
    
    
    // manage all children
    XtManageChild(curW);
    XtManageChild(prevW);
    XtManageChildren(buttonw, 3);
    XtManageChild(patchButForm);
    if (updateFreq == AFTER_ACCEPT)
    	XtManageChild(acceptButton);
    
    return buttonsForm;
}


////////////////////////////////////////////////////////////////////////
//
//    builds the sliders form
//
// usage: private

Widget
_SoXtColorEditor::buildSlidersForm(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
    int	    n;
    Arg     args[12];
    
    //
    // build the sliders form
    //
    n = 0;
    XtSetArg(args[n], XmNfractionBase, 1000); n++;
    slidersForm = XtCreateWidget("slidersForm", xmFormWidgetClass, 
	parent, args, n);
    
    // build sliders
    sliders[0] = new _SoXtColorSlider(slidersForm, NULL, TRUE, 
			_SoXtColorSlider::RED_SLIDER);
    sliders[1] = new _SoXtColorSlider(slidersForm, NULL, TRUE, 
			_SoXtColorSlider::GREEN_SLIDER);
    sliders[2] = new _SoXtColorSlider(slidersForm, NULL, TRUE, 
			_SoXtColorSlider::BLUE_SLIDER);
    sliders[3] = new _SoXtColorSlider(slidersForm, NULL, TRUE, 
			_SoXtColorSlider::HUE_SLIDER);
    sliders[4] = new _SoXtColorSlider(slidersForm, NULL, TRUE, 
			_SoXtColorSlider::SATURATION_SLIDER);
    sliders[5] = new _SoXtColorSlider(slidersForm, NULL, TRUE, 
			_SoXtColorSlider::VALUE_SLIDER);

    int i;
    for (i=0; i<3; i++)
	sliders[i]->setBaseColor(baseRGB.getValue());
    for (i=3; i<6; i++)
	sliders[i]->setBaseColor(baseHSV);

    n = 0;
    XtSetArg(args[n], XmNleftAttachment,    XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment,   XmATTACH_FORM); n++;
    for (i=0; i<6; i++) {
        sliders[i]->setLabel(slider_labels[i]);
	sliders[i]->addValueChangedCallback(
	    &_SoXtColorEditor::sliderCallback, 
	    &dataId[R_SLIDER_ID+i]);
	XtSetValues(sliders[i]->getWidget(), args, n);
    }

    
    //
    // layout !
    //
    doSliderLayout();
    
    return slidersForm;
}


////////////////////////////////////////////////////////////////////////
//
//    This routine attaches itself to a single color field.
//
// usage: public

void
_SoXtColorEditor::attach(SoSFColor *sf, SoBase *node)
//
////////////////////////////////////////////////////////////////////////
{
    if ( isAttached() )
    	detach();
    
    if (sf != NULL && node != NULL) {
	setColor(sf->getValue());
	colorSF = sf;
	editNode = node;
	editNode->ref();
	colorSensor->attach((SoNode *) editNode);
	attached = TRUE;
    }
}


////////////////////////////////////////////////////////////////////////
//
//    This routine attaches itself to a multiple value color field.
//
// usage: public

void
_SoXtColorEditor::attach(SoMFColor *mf, int ind, SoBase *node)
//
////////////////////////////////////////////////////////////////////////
{
    if ( isAttached() )
    	detach();
    
    if (mf != NULL && ind >= 0 && node != NULL) {
	setColor((*mf)[ind]);
	colorMF = mf;
	index = ind;
	editNode = node;
	editNode->ref();
	colorSensor->attach((SoNode *) editNode);
	attached = TRUE;
    }
}


////////////////////////////////////////////////////////////////////////
//
//    This routine detaches itself from the color field.
//
// usage: public

void
_SoXtColorEditor::detach()
//
////////////////////////////////////////////////////////////////////////
{
    if ( ! isAttached() )
	return;
    
    colorSensor->detach();
    editNode->unref();
    editNode = NULL;
    colorSF = NULL;
    colorMF = NULL;
    attached = FALSE;
}


////////////////////////////////////////////////////////////////////////
//
//    This routine sets the current color.
//
// usage: public
//
void
_SoXtColorEditor::setColor(const SbColor &color)
//
////////////////////////////////////////////////////////////////////////
{
    if (color == baseRGB)
    	return;
    
    // save color
    baseRGB = color;
    baseRGB.getHSVValue(baseHSV);
    
    ignoreCallback = TRUE;
    
    // now send the colors to the sliders/color wheel
    int i;
    for (i=0; i<3; i++)
    	sliders[i]->setBaseColor(baseRGB.getValue());
    for (i=3; i<6; i++)
    	sliders[i]->setBaseColor(baseHSV);
    wheel->setBaseColor(baseHSV);
    current->setColor(baseRGB);
    
    ignoreCallback = FALSE;
    
    if (updateFreq == CONTINUOUS)
	doUpdates();
}


////////////////////////////////////////////////////////////////////////
//
//    This routine sets the WYSIWYG mode.
//
// usage: public

void
_SoXtColorEditor::setWYSIWYG(SbBool flag)
//
////////////////////////////////////////////////////////////////////////
{
    if (WYSIWYGmode == flag)
    	return;
    
    WYSIWYGmode = flag;
    
    // now update the sliders and color wheel
    for (int i=0; i<6; i++)
	sliders[i]->setWYSIWYG(WYSIWYGmode);
    wheel->setWYSIWYG(WYSIWYGmode);
}


////////////////////////////////////////////////////////////////////////
//
//    This routine sets the update frequency.
//
// usage: virtual public
//
void
_SoXtColorEditor::setUpdateFrequency(_SoXtColorEditor::UpdateFrequency freq)
//
////////////////////////////////////////////////////////////////////////
{
    if (updateFreq == freq)
	return;
    
    updateFreq = freq;
    
    // show/hide the accept button
    if (acceptButton != NULL) {
	if (updateFreq == CONTINUOUS)
	    XtUnmanageChild(acceptButton);
	else
	    XtManageChild(acceptButton);
    }
    
    // update the attached node if we switch to continous
    if (updateFreq == CONTINUOUS)
    	doUpdates();
}


////////////////////////////////////////////////////////////////////////
//
//    This routine specifies which sliders are being displayed.
//
// usage: public

void
_SoXtColorEditor::setCurrentSliders(_SoXtColorEditor::Sliders id)
//
////////////////////////////////////////////////////////////////////////
{
    int i, prevNum, curNum;
    
    if (whichSliders == id)
    	return;
    
    prevNum = numberOfSliders(whichSliders);
    curNum = numberOfSliders(id);
    
    // check to make sure widget has been built, otherwise just change the
    // default window size.
    if (mgrWidget == NULL) {
	// set new height
	SbVec2s size = getSize();
	float r = (TOP_REGION_SIZE + curNum) / float(TOP_REGION_SIZE + prevNum);
	size[1] = short(size[1] * r);
	setSize(size);
    	whichSliders = id;
	return;
    }
    
    //
    // hide the current set of sliders
    //
    switch(whichSliders) {
    	case NONE:
	    break;
    	case INTENSITY:
	    sliders[5]->hide();
	    break;
	case RGB:
	    for (i=0; i<3; i++)
		sliders[i]->hide();
	    break;
	case HSV:
	    for (i=3; i<6; i++)
		sliders[i]->hide();
	    break;
	case RGB_V:
	    for (i=0; i<3; i++)
		sliders[i]->hide();
	    sliders[5]->hide();
	    break;
	case RGB_HSV:
	    for (i=0; i<6; i++)
		sliders[i]->hide();
	    break;
    }
    
    //
    // check if window needs to be resized
    //
    Widget parent = XtParent(mgrWidget);
    if (XtIsShell(parent) && curNum != prevNum) {
    	
	// get current window height and find new height
	SbVec2s size = getSize();
	float r = (TOP_REGION_SIZE + curNum) / float(TOP_REGION_SIZE + prevNum);
	size[1] = short(size[1] * r);
	SoXt::setWidgetSize(parent, size);
    }
    
    // finally do new layout
    whichSliders = id;
    doDynamicTopLevelLayout();
    doSliderLayout();
}


////////////////////////////////////////////////////////////////////////
//
//    routine to lay sliders out within the slider form.
//
// usage: protected

void
_SoXtColorEditor::doSliderLayout()
//
////////////////////////////////////////////////////////////////////////
{
    int i, n;
    Arg args[4];
    
    ignoreCallback = TRUE;
    
    switch(whichSliders) {
    	case NONE:
	    break;
	
    	case INTENSITY:
	    n = 0;
	    XtSetArg(args[n], XmNtopAttachment,     	XmATTACH_FORM); n++;
	    XtSetArg(args[n], XmNbottomAttachment,  	XmATTACH_POSITION); n++;
	    XtSetArg(args[n], XmNbottomPosition,    	990); n++;
	    XtSetValues(sliders[5]->getWidget(), args, n);
	    sliders[5]->setBaseColor(baseHSV);
	    sliders[5]->show();
	    break;
	    
	case RGB:
	    for (i=0; i<3; i++) {
		n = 0;
		XtSetArg(args[n], XmNtopAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNtopPosition,   	int((i*1000)/3.0)); n++;
		XtSetArg(args[n], XmNbottomAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNbottomPosition, 	int(((i+1)*1000)/3.0) - 10); n++;
		XtSetValues(sliders[i]->getWidget(), args, n);
	        sliders[i]->setBaseColor(baseRGB.getValue());
		sliders[i]->show();
	    }
	    break;
	    
	case HSV:
	    for (i=3; i<6; i++) {
		n = 0;
		XtSetArg(args[n], XmNtopAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNtopPosition,   	int(((i-3)*1000)/3.0)); n++;
		XtSetArg(args[n], XmNbottomAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNbottomPosition, 	int(((i-2)*1000)/3.0) - 10); n++;
		XtSetValues(sliders[i]->getWidget(), args, n);
	        sliders[i]->setBaseColor(baseHSV);
		sliders[i]->show();
	    }
	    break;
	    
	case RGB_V:
	    for (i=0; i<4; i++) {
		n = 0;
		XtSetArg(args[n], XmNtopAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNtopPosition,   	i*250); n++;
		XtSetArg(args[n], XmNbottomAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNbottomPosition, 	(i+1)*250 - 10); n++;
		if (i==3) {
		    XtSetValues(sliders[5]->getWidget(), args, n);
	            sliders[5]->setBaseColor(baseHSV);
		    sliders[5]->show();
		}
		else {
		    XtSetValues(sliders[i]->getWidget(), args, n);
	            sliders[i]->setBaseColor(baseRGB.getValue());
		    sliders[i]->show();
		}
	    }
	    break;
	    
	case RGB_HSV:
	    for (i=0; i<6; i++) {
		n = 0;
		XtSetArg(args[n], XmNtopAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNtopPosition,   	int((i*1000)/6.0)); n++;
		XtSetArg(args[n], XmNbottomAttachment, 	XmATTACH_POSITION); n++;
		XtSetArg(args[n], XmNbottomPosition, 	int(((i+1)*1000)/6.0) - 10); n++;
		XtSetValues(sliders[i]->getWidget(), args, n);
		if (i > 2)
	            sliders[i]->setBaseColor(baseHSV);
		else 
	            sliders[i]->setBaseColor(baseRGB.getValue());
		sliders[i]->show();
	    }
	    break;
    }
    
    ignoreCallback = FALSE;
}


////////////////////////////////////////////////////////////////////////
//
// routine which does the top level forms layout (slider form, button form
// and colorWheel) which changes dynamically as the number of sliders changes.
//
// usage: private

void
_SoXtColorEditor::doDynamicTopLevelLayout()
//
////////////////////////////////////////////////////////////////////////
{
    int n, num = numberOfSliders(whichSliders);
    Arg args[4];
    
    if (num) {
    	// calculate the sliders form top position based on how many sliders there are
	float top = 100 * TOP_REGION_SIZE / float(TOP_REGION_SIZE + num);
	n = 0;
	XtSetArg(args[n], XmNtopAttachment, 	XmATTACH_POSITION); n++;
	XtSetArg(args[n], XmNtopPosition,   	int(top)); n++;
	XtSetValues(slidersForm, args, n);
	
	if (! XtIsManaged(slidersForm))
	    XtManageChild(slidersForm);
	
	n = 0;
	XtSetArg(args[n], XmNbottomAttachment, 	XmATTACH_WIDGET); n++;
	XtSetArg(args[n], XmNbottomWidget,  	slidersForm); n++;
	XtSetArg(args[n], XmNbottomOffset,  	OFFSET); n++;
	XtSetValues(buttonsForm, args, n);
	XtSetValues(wheelForm, args, n);
	
	n = 0;
	XtSetArg(args[n], XmNbottomOffset,      0); n++;
	XtSetValues(acceptButton, args, n);
    }
    else {
	// no sliders so don't use the slidersForm at all
	n = 0;
	XtSetArg(args[n], XmNbottomAttachment, 	XmATTACH_FORM); n++;
	XtSetArg(args[n], XmNbottomOffset,  	0); n++;
	XtSetValues(buttonsForm, args, n);
	XtSetValues(wheelForm, args, n);
	
	if (XtIsManaged(slidersForm))
	    XtUnmanageChild(slidersForm);
	
	n = 0;
	XtSetArg(args[n], XmNbottomOffset,      OFFSET); n++;
	XtSetValues(acceptButton, args, n);
    }
}

////////////////////////////////////////////////////////////////////////
//
//    This routine is called (by fieldChangedCB) when the color field 
//    has changed value.
//
// usage: protected

void
_SoXtColorEditor::fieldChanged()
//
////////////////////////////////////////////////////////////////////////
{
    if (colorSF != NULL)
    	setColor(colorSF->getValue());
    else
    	setColor((*colorMF)[index]);
}

////////////////////////////////////////////////////////////////////////
//
//    Called when the color wheel changes the current color.
//
// usage: private
//
void
_SoXtColorEditor::wheelChanged(const float hsv[3])
//
////////////////////////////////////////////////////////////////////////
{
    int i;
    
    // wheel can only change hue and saturation
    baseHSV[0] = hsv[0];
    baseHSV[1] = hsv[1];
    baseRGB.setHSVValue(baseHSV);
    
    ignoreCallback = TRUE;
    
    switch(whichSliders) {
    	case NONE:
	    break;
	case INTENSITY:
	    sliders[5]->setBaseColor(baseHSV);
	    break;
	case RGB:
	case RGB_V:
	    for (i=0; i<3; i++)
	    	sliders[i]->setBaseColor(baseRGB.getValue());
	    if (whichSliders == RGB_V)
	    	sliders[5]->setBaseColor(baseHSV);
	    break;
	case HSV:
	    for (i=3; i<6; i++)
	    	sliders[i]->setBaseColor(baseHSV);
	    break;
	case RGB_HSV:
	    for (i=0; i<3; i++)
	    	sliders[i]->setBaseColor(baseRGB.getValue());
	    for (i=3; i<6; i++)
	    	sliders[i]->setBaseColor(baseHSV);
	    break;
    }
    current->setColor(baseRGB);
    
    ignoreCallback = FALSE;
    
    if (updateFreq == CONTINUOUS)
	doUpdates();
}


////////////////////////////////////////////////////////////////////////
//
//    This routine is called when the sliders changes the current color. 
//
// usage: private
//
void
_SoXtColorEditor::sliderChanged(short id, float value)
//
////////////////////////////////////////////////////////////////////////
{
    int i;
    
    ignoreCallback = TRUE;
    
    switch(id) {
	case R_SLIDER_ID:
	case G_SLIDER_ID:
	case B_SLIDER_ID:
	    baseRGB[id - R_SLIDER_ID] = value;
	    baseRGB.getHSVValue(baseHSV);
	    
	    for (i=0; i<3; i++)
		if (i != id) 
		    sliders[i]->setBaseColor(baseRGB.getValue());
    	    
	    if (whichSliders == RGB_V)
		sliders[5]->setBaseColor(baseHSV);
	    else if (whichSliders == RGB_HSV)
	    	for (i=3; i<6; i++)
		    sliders[i]->setBaseColor(baseHSV);
	    wheel->setBaseColor(baseHSV);
	    current->setColor(baseRGB);
	    break;
	    
	case H_SLIDER_ID:
	case S_SLIDER_ID:
	case V_SLIDER_ID:
	    baseHSV[id - H_SLIDER_ID] = value;
	    baseRGB.setHSVValue(baseHSV);
	    
	    switch (whichSliders) {
	    	case HSV:
		case RGB_HSV:
		    for (i=3; i<6; i++)
			if (i != id) sliders[i]->setBaseColor(baseHSV);
		    if (whichSliders == RGB_HSV)
			for (i=0; i<3; i++)
			    sliders[i]->setBaseColor(baseRGB.getValue());
		    break;
		case RGB_V:
		    for (i=0; i<3; i++)
			sliders[i]->setBaseColor(baseRGB.getValue());
		    break;
		case INTENSITY:
		    break;
		case RGB: // not possible cases
		case NONE:
#ifdef DEBUG
		    SoDebugError::post("_SoXtColorEditor::sliderChanged",
			"inconsitant state %d", id);
#endif
		    break;
	    }
	    wheel->setBaseColor(baseHSV);
	    current->setColor(baseRGB);
	    break;
	 
	default:
#ifdef DEBUG   
	    SoDebugError::post("_SoXtColorEditor::sliderChanged",
		    "bad id %d",id);
#endif
	    break;
    }
    
    ignoreCallback = FALSE;
    
    if (updateFreq == CONTINUOUS)
	doUpdates();
}


////////////////////////////////////////////////////////////////////////
//
//    This routine is called when the motif buttons are being pressed.
//
// usage: protected

void
_SoXtColorEditor::buttonPressed(short id)
//
////////////////////////////////////////////////////////////////////////
{
    SbColor col;
    
    switch(id) {
	case SAVE_ID:
	    previous->setColor(baseRGB);
	    break;
	    
	case SWAP_ID:
	case RESTORE_ID:
	    col = previous->getColor();
	    
	    if (id == SWAP_ID)
		previous->setColor(baseRGB);
	    
	    // assign new color
	    setColor(col);
	    
	    if (updateFreq != AFTER_ACCEPT)
	    	doUpdates();
	    break;
	    
	case ACCEPT_ID:
	    doUpdates();
	    break;
    }
}


////////////////////////////////////////////////////////////////////////
//
//    Do the updates - if node is attached, update it; if callback exists,
//  call it.
//
// usage: private
//
void
_SoXtColorEditor::doUpdates()
//
////////////////////////////////////////////////////////////////////////
{
    // check for field update
    if (attached) {
	if (colorSF != NULL) {
	    colorSF->setValue(baseRGB);
	    if ( colorSF->isIgnored() )
		colorSF->setIgnored( FALSE );
	}
	else {
	    colorMF->set1Value(index, baseRGB);
	    if ( colorMF->isIgnored() )
		colorMF->setIgnored( FALSE );
	}
    }
    
    // check for callback
    void *hackage = (void *) &baseRGB;
    callbackList->invokeCallbacks(hackage);
}

////////////////////////////////////////////////////////////////////////
//
// convenience routine which returns the number of sliders, given 
// _SoXtColorEditorSliders id.
//
// usage: private

int
_SoXtColorEditor::numberOfSliders(_SoXtColorEditor::Sliders id)
//
////////////////////////////////////////////////////////////////////////
{
    switch(id) {
	default:        // required to eliminate compiler warning.
	case NONE:
	    return 0;
	case INTENSITY:
	    return 1;
	case RGB:
	case HSV:
	    return 3;
	case RGB_V:
	    return 4;
	case RGB_HSV:
	    return 6;
    }
}


////////////////////////////////////////////////////////////////////////
//
//  Copy the current color onto the clipboard.
//
//  Use: private
//
void
_SoXtColorEditor::copy(Time eventTime)
//
////////////////////////////////////////////////////////////////////////
{
#ifdef DEBUG
    if (mgrWidget == NULL) {
	SoDebugError::post("_SoXtColorEditor::copy", "widget is NULL\n");
	return;
    }
#endif

    if (clipboard == NULL)
    	clipboard = new SoXtClipboard(mgrWidget);
    
    // copy the current color using a BaseColor node
    SoBaseColor *color = new SoBaseColor;
    color->ref();
    color->rgb.setValue(baseRGB);
    clipboard->copy(color, eventTime);
    color->unref();
}

////////////////////////////////////////////////////////////////////////
//
//  Retrieve the selection from the X server and paste it when it
//  arrives (in our pasteDone callback).
//
//  Use: private
//
void
_SoXtColorEditor::paste(Time eventTime)
//
////////////////////////////////////////////////////////////////////////
{
#ifdef DEBUG
    if (mgrWidget == NULL) {
	SoDebugError::post("_SoXtColorEditor::paste",
		"widget is NULL\n");
	return;
    }
#endif

    if (clipboard == NULL)
    	clipboard = new SoXtClipboard(mgrWidget);

    clipboard->paste(eventTime, _SoXtColorEditor::pasteDoneCB, this);
}

////////////////////////////////////////////////////////////////////////
//
//  The X server has finished getting the selection data, and the
//  paste is complete. Look through the paste data for a base color node.
//
//  Use: private
//
void
_SoXtColorEditor::pasteDone(SoPathList *pathList)
//
////////////////////////////////////////////////////////////////////////
{
    SoSearchAction sa;
    SoFullPath *fullPath = NULL;
    
    
    //
    // search for first base color node in that pasted scene
    //
    sa.setType(SoBaseColor::getClassTypeId());
    for (int i=0; i < pathList->getLength(); i++) {
	sa.apply( (*pathList)[i] );
	if ( (fullPath = (SoFullPath *) sa.getPath()) != NULL) {
	    
	    // assign new color
	    SoBaseColor *newColor = (SoBaseColor *) fullPath->getTail();
	    setColor((newColor->rgb)[0]);
	    
	    break;
	}
    }
    
    //
    // else search for the first material and extract a color from it
    // (the diffuse color, which is better than doing nothing)
    //
    if (fullPath == NULL) {
	sa.setType(SoMaterial::getClassTypeId());
	for (int i=0; i < pathList->getLength(); i++) {
	    sa.apply( (*pathList)[i] );
	    if ( (fullPath = (SoFullPath *) sa.getPath()) != NULL) {
		
		SoMaterial *mat = (SoMaterial *) fullPath->getTail();
		setColor(mat->diffuseColor[0].getValue());
		
		break;
	    }
	}
    }
    
    // ??? We delete the callback data when done with it.
    delete pathList;
}

////////////////////////////////////////////////////////////////////////
//
//  Called by Xt when a menu is about to be displayed.
//  This gives us a chance to update any items in the menu.
//
//  Use: static private
//
void
_SoXtColorEditor::menuDisplay(Widget, _SoXtColorEditor *editor, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
    // turn them all off
    for (int i = 0; i < NUM_TOGGLES; i++)
	TOGGLE_OFF(editor->menuItems[i]);
    
    // set default toggles
    switch (editor->updateFreq) {
	case CONTINUOUS:    TOGGLE_ON(editor->menuItems[CONTINUOUS_TOGGLE]);	break;
	case AFTER_ACCEPT:  TOGGLE_ON(editor->menuItems[ACCEPT_TOGGLE]);  	break;
    }
    
    if (editor->WYSIWYGmode)
	TOGGLE_ON(editor->menuItems[WYSIWYG_TOGGLE]);
    
    switch (editor->whichSliders) {
	case NONE:  	TOGGLE_ON(editor->menuItems[NONE_TOGGLE]);	break;
	case INTENSITY: TOGGLE_ON(editor->menuItems[INTENSITY_TOGGLE]);	break;
	case RGB:   	TOGGLE_ON(editor->menuItems[RGB_TOGGLE]); 	break;
	case HSV:   	TOGGLE_ON(editor->menuItems[HSV_TOGGLE]); 	break;
	case RGB_V:  	TOGGLE_ON(editor->menuItems[RGB_V_TOGGLE]);	break;
	case RGB_HSV:	TOGGLE_ON(editor->menuItems[RGB_HSV_TOGGLE]);	break;
    }
}

//
// redefine those generic virtual functions
//
const char *
_SoXtColorEditor::getDefaultWidgetName() const
{ return thisClassName; }

const char *
_SoXtColorEditor::getDefaultTitle() const
{ return "Color Editor"; }

const char *
_SoXtColorEditor::getDefaultIconTitle() const
{ return "Color Editor"; }




//
////////////////////////////////////////////////////////////////////////
// static callbacks stubs
////////////////////////////////////////////////////////////////////////
//


//
// called whenever the component becomes visibble or not
//
void
_SoXtColorEditor::visibilityChangeCB(void *pt, SbBool visible)
//
{
    _SoXtColorEditor *p = (_SoXtColorEditor *)pt;
    
    if (visible) {
	// attach sensor to top node for redrawing purpose
	if ( p->editNode != NULL && p->colorSensor->getAttachedNode() == NULL)
	    p->colorSensor->attach((SoNode *) p->editNode);
    }
    else
	// detach sensor
	p->colorSensor->detach();
}

void 
_SoXtColorEditor::wheelCallback(void *p, const float hsv[3])
{
    _SoXtColorEditor *c = (_SoXtColorEditor *)p;
    
    if (c->ignoreCallback)
    	return;
    c->wheelChanged(hsv);
}

void 
_SoXtColorEditor::sliderCallback(void *p, float value)
{
    ColorEditorCBData *d = (ColorEditorCBData *)p;
    
    if (d->classPtr->ignoreCallback)
    	return;
    d->classPtr->sliderChanged(d->id, value);
}

void 
_SoXtColorEditor::buttonsCallback(Widget, ColorEditorCBData *d, XtPointer)
{ d->classPtr->buttonPressed(d->id); }

void
_SoXtColorEditor::fieldChangedCB(void *pt, SoSensor *)
{
    _SoXtColorEditor *p = (_SoXtColorEditor *)pt;
    
    if (!p->isVisible())
	return;
    p->fieldChanged(); 
}

void 
_SoXtColorEditor::editMenuCallback(
    Widget, ColorEditorCBData *d, XmAnyCallbackStruct *cb)
{
    Time eventTime = cb->event->xbutton.time;

    switch(d->id) {
	case CONTINUOUS_ID: d->classPtr->setUpdateFrequency(CONTINUOUS);    break;
	case MANUAL_ID:	    d->classPtr->setUpdateFrequency(AFTER_ACCEPT);  break;
	case WYSIWYG_ID:    d->classPtr->setWYSIWYG( // toggle
	    	    	    	    ! d->classPtr->WYSIWYGmode); break;
    	case COPY_ID:	    d->classPtr->copy(eventTime); break;
    	case PASTE_ID:	    d->classPtr->paste(eventTime); break;
	case HELP_ID:	    d->classPtr->openHelpCard("SoXtColorEditor.help"); break;
    }
}

void 
_SoXtColorEditor::sliderMenuCallback(Widget, ColorEditorCBData *d, XtPointer)
{
    switch(d->id) {
    	case NONE_SLIDER_ID:  	    d->classPtr->setCurrentSliders(NONE);    	    break;
    	case INTENSITY_SLIDER_ID:   d->classPtr->setCurrentSliders(INTENSITY);    break;
	case RGB_SLIDERS_ID:	    d->classPtr->setCurrentSliders(RGB);	    break;
	case HSV_SLIDERS_ID:	    d->classPtr->setCurrentSliders(HSV);	    break;
	case RGB_V_SLIDERS_ID:	    d->classPtr->setCurrentSliders(RGB_V);	    break;
	case RGB_HSV_SLIDERS_ID:    d->classPtr->setCurrentSliders(RGB_HSV);      break;
    }
}

void 
_SoXtColorEditor::pasteDoneCB(void *userData, SoPathList *pathList)
{
    ((_SoXtColorEditor *) userData)->pasteDone(pathList);
}
		  

#undef SCREEN
