/*
 *
 *  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) 1991-93   Silicon Graphics, Inc.
 *
 _______________________________________________________________________
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 |
 |   $Revision: 1.7 $
 |
 |   Classes:
 |	MyMaterialPalette
 |
 |   Author(s):	Alain Dumesny
 |
 |
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 _______________________________________________________________________
 */

#include <cstdio>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <cstdlib>

#include <X11/StringDefs.h>
#include <X11/keysym.h>
#include <Xm/Form.h>
#include <Xm/PushB.h>
#include <Xm/LabelG.h>
#include <Xm/RowColumn.h>
#include <Xm/PushBG.h>
#include <Xm/SelectioB.h>
#include <Xm/Text.h>
#include <Xm/MessageB.h>
#include <Xm/CascadeBG.h>

#include <Inventor/SoInput.h>
#include <Inventor/SoDB.h>
#include <Inventor/SoPath.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSwitch.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoWriteAction.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/devices/SoXtInputFocus.h>
#include <Inventor/Xt/SoXtClipboard.h>
#include <Inventor/Xt/SoXtRenderArea.h>
#include <Inventor/errors/SoDebugError.h>

#include "MySimpleMaterialEditor.h"
#include "MyMaterialPalette.h"


/*
 * Defines
 */

#define SCREEN(w) XScreenNumberOfScreen( XtScreen(w) )

// time in msec between consecutive clicks to consider 
// the second click being a double click.
#define DOUBLE_CLICK_TIME 300

// name of the directory where custom palettes will be saved
// (under home directory)
#define customPalDir ".materials"

#define OVERLAY_SLOT 1

// those are used to set a flag which will tell use what needs to
// be done in a futur time (like once a dialog disapears).
enum ThingsToDo {
    SWITCH_PALETTE, 
    BRING_NEW_DIALOG, 
    CREATE_NEW_PALETTE, 
    SAVE_AS_PALETTE, 
};

enum MenuEntryID {
    FILE_MENU  = 0,   // start at 0 since we use an array
    FILE_NEW, 
    FILE_SAVE, 
    FILE_SAVE_AS, 
    FILE_RESET, 
    FILE_DELETE, 
    
    EDIT_MENU, 
    EDIT_CUT, 
    EDIT_COPY, 
    EDIT_PASTE, 
    EDIT_DELETE, 
    
    // list of needed widget to build the palette menu on the fly
    MENU_BAR, 
    PALETTE_BUTTON, 
    PALETTE_MENU, 
    
    MAT_LABEL, 
    MENU_LENGTH,    // this must be the last entry
};

struct MaterialNameStruct {
    char *name;
    char *oldName;
};

struct PaletteStruct {
    char    *name;
    SbBool  user;
    SbBool  system;
};

struct MenuButtonItemStruct {
    char    *name;
    int	    id;
    char    *accelerator; // e.g. "Alt <Key> p" or "Ctrl <Key> u"
    char    *accelText;   // text that appears in the menu item
};

struct MenuStruct {
    char    *name;
    int	    id;
    struct MenuButtonItemStruct *subMenu;
    int	    subItemCount;
};


/*
 * static vars
 */


static MenuButtonItemStruct fileData[] = {
    {"New...",	    FILE_NEW,	    "Alt <Key> n", "Alt+n" }, 
    {"Save",	    FILE_SAVE,	    "Alt <Key> s", "Alt+s" }, 
    {"Save As...",  FILE_SAVE_AS,   "Alt Shift <Key> s", "Alt+S" }, 
    {"Reset",	    FILE_RESET,	    "Alt <Key> r", "Alt+r" }, 
    {"Delete",	    FILE_DELETE,    "Alt <Key> d", "Alt+d" }, 
};

static MenuButtonItemStruct editData[] = {
    {"Cut",	    EDIT_CUT,	    "Alt <Key> x", "Alt+x" },
    {"Copy",	    EDIT_COPY,	    "Alt <Key> c", "Alt+c" },
    {"Paste",	    EDIT_PASTE,	    "Alt <Key> v", "Alt+v" },
    {"Delete",	    EDIT_DELETE,    "<Key> BackSpace", "BckSp" },
};

static MenuStruct pulldownData[] = {
//  {name, 	    id,	    	    subMenu,    subItemCount}
    {"File",	    FILE_MENU,	    fileData,	XtNumber(fileData)}, 
    {"Edit",	    EDIT_MENU,	    editData,	XtNumber(editData)}, 
};

static char *editorTitle = "Material Palette";
static char *defaultDir = IVMATERIALSDIR;


static char *geometryBuffer = "\
#Inventor V2.0 ascii\n\
\
Separator { \
    OrthographicCamera { \
	position 3.5 3.5 5 \
	nearDistance 1.0 \
	farDistance 10.0 \
	height 6.2 \
    } \
    LightModel { model BASE_COLOR } \
    BaseColor { rgb [.4 .4 .4] } \
    Coordinate3 { point [ \
	0 1 0, 7 1 0, 0 2 0, 7 2 0, 0 3 0, 7 3 0, \
	0 4 0, 7 4 0, 0 5 0, 7 5 0, 0 6 0, 7 6 0, \
	1 0 0, 1 7 0, 2 0 0, 2 7 0, 3 0 0, 3 7 0, \
	4 0 0, 4 7 0, 5 0 0, 5 7 0, 6 0 0, 6 7 0] } \
    DrawStyle { lineWidth 2 } \
    LineSet { numVertices [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] } \
    LightModel { model PHONG } \
    DirectionalLight { direction .556 -.551 -.623 intensity .7 } \
    DirectionalLight { direction -.556 -.551 -.623 intensity .7 } \
    Complexity { value .4 } \
    Translation { translation 1 6 0 } \
    Array { \
	numElements1 6 \
	numElements2 6 \
	separation1 1 0 0 \
	separation2 0 -1 0 \
	Switch { \
	    whichChild -2 \
	    Material{} Material{} Material{} Material{} Material{} Material{} \
	    Material{} Material{} Material{} Material{} Material{} Material{} \
	    Material{} Material{} Material{} Material{} Material{} Material{} \
	    Material{} Material{} Material{} Material{} Material{} Material{} \
	    Material{} Material{} Material{} Material{} Material{} Material{} \
	    Material{} Material{} Material{} Material{} Material{} Material{} \
	} \
	Sphere { radius .43 } \
    } \
} ";

static char *overlayGeometryBuffer = "\
#Inventor V2.0 ascii\n\
\
Separator { \
    LightModel { model BASE_COLOR } \
    ColorIndex { index 1 } \
    Coordinate3 { point [ -.5 -.5 0, .5 -.5 0, .5 .5 0, -.5 .5 0, -.5 -.5 0 ] } \
    Separator { \
	Translation {} \
	DrawStyle { lineWidth 3 } \
	LineSet { numVertices 5 } \
    } \
    Translation {} \
    LineSet { numVertices 5 } \
} ";




//
//  returns true if the passed file is a subdirectory in the current directory.
//  (call chdir() before calling this).
//
static SbBool
isDirectory(char *file)
{
    struct stat buf;
    
    if (stat(file, &buf) == 0)
	if ((buf.st_mode & S_IFMT) == S_IFDIR)
	    return TRUE;
    
    return FALSE;
}

//
// return PaletteStruct pointer which contains the given name string
// in the given PbPlist of structs.
//
static PaletteStruct *
findPalette(char *str, SbPList *list)
{
    PaletteStruct *pal = NULL;
    
    for (int i=0; i<list->getLength(); i++) {
	pal = (PaletteStruct *) (*list)[i];
	if (strcmp(str, pal->name) == 0)
	    return pal;
	else
	    pal = NULL;
    }
    return pal;
}


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

////////////////////////////////////////////////////////////////////////
//
// SoEXTENDER constructor - the subclass tells us whether to build or not
//
MyMaterialPalette::MyMaterialPalette(
    Widget parent,
    const char *name, 
    SbBool buildInsideParent, 
    const char *dir,
    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(dir, buildNow);
}

////////////////////////////////////////////////////////////////////////
//
// Called by the constructors
//
// private
//
void
MyMaterialPalette::constructorCommon(const char *dir, SbBool buildNow)
//
//////////////////////////////////////////////////////////////////////
{
    int i;
    
    setClassName("MyMaterialPalette");
    paletteDir = (dir != NULL) ? strdup(dir) : strdup(defaultDir);
    paletteChanged = FALSE;
    selectedItem = currentItem = -1;
    curPalette = -1;
    prevTime = 0;
    clipboard = NULL;
    
    // widget vars
    widgetList = new Widget[MENU_LENGTH];
    for (i=0; i<MENU_LENGTH; i++)
	widgetList[i] = NULL;
    
    // alocate needed stuff
    matEditor = NULL;
    
    
    // allocate the material name list
    mtlNames = new MaterialNameStruct[36];
    for (i=0; i<36; i++)
	mtlNames[i].name = mtlNames[i].oldName = NULL;
        
    // Build the widget tree, and let SoXtComponent know about our base widget.
    if (buildNow) {
	Widget w = buildWidget(getParentWidget());
	setBaseWidget(w);
    }
}

////////////////////////////////////////////////////////////////////////
//
//    Destructor.
//
MyMaterialPalette::~MyMaterialPalette()
//
////////////////////////////////////////////////////////////////////////
{
    int i;
    
    // delete allocated stuff
    delete focus;
    delete ra;
    delete matEditor;
    delete paletteDir;
    delete clipboard;
    
    // delete palette names
    PaletteStruct *pal;
    for (i=0; i<paletteList.getLength(); i++) {
	pal = (PaletteStruct *) paletteList[i];
	free(pal->name);
	delete pal;
    }
    paletteList.truncate(0);
    
    // delete material names
    for (i=0; i<36; i++) {
	if (mtlNames[i].name != NULL) free(mtlNames[i].name);
	if (mtlNames[i].oldName != NULL) free(mtlNames[i].oldName);
    }
    delete [] mtlNames;
    
    // delete widget stuff
    delete [] widgetList;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	deselect the currently selected item in the palette.
//
// Use: public
void
MyMaterialPalette::deselectCurrentItem()
//
////////////////////////////////////////////////////////////////////////
{
    if (selectedItem == -1)
	return;
    
    // deselect the current item and update things that depend on it
    selectedItem = -1;
    updateOverlayFeedback();
    updateMaterialName();
    updateEditMenu();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	sets the window title based on the current palette and save status
//
// Use: private
void
MyMaterialPalette::updateWindowTitle()
//
////////////////////////////////////////////////////////////////////////
{
    char str[150];
    sprintf(str, "%s: %s", editorTitle, ((PaletteStruct *)paletteList[curPalette])->name);
    if (paletteChanged)
	strcat(str, "*");
    setTitle(str);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	Buils the palette layout (render area + motif buttons).
//
// Use: protected
Widget
MyMaterialPalette::buildWidget(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
    // force the window to resize in fixed increments from the
    // window minimum size. This will guarantee that the RA has
    // a 1/1 aspect ratio.
    //
    // ??? this is necessary for the fast window/region picking
    // ??? we are doing during mouse motion.
    //
    if ( XtIsShell(parent) )
	XtVaSetValues(parent, 
	    XmNbaseWidth, 210, 
	    XmNbaseHeight, 263, 
	    XmNminAspectX, 1, 
	    XmNmaxAspectX, 1, 
	    XmNminAspectY, 1, 
	    XmNmaxAspectY, 1, 
	    NULL);
    
    int	n;
    Arg	args[12];
    
    // create a top level form to hold everything together
    Widget form = XmCreateForm(parent, "matPalForm", NULL, 0);
    
    
    //
    // create all the parts
    //
    
    // Render area
    ra = new SoXtRenderArea(form);
    ra->setSize(SbVec2s(250, 250));
    ra->setTransparencyType(SoGLRenderAction::BLEND); // spheres are last
    ra->setEventCallback(MyMaterialPalette::raEventCB, this);
//    ra->setBackgroundColor(SbColor(.6, .6, .6));
    SbColor col(.9, .2, .2);
    ra->setOverlayColorMap(1, 1, &col);
    
    Widget raWidget = ra->getWidget();
    createSceneGraph();
    
#if 0
    // make renderArea single buffered on Starter graphics (less than 24 bits)
    // which is ok since the scene is mostly static.
    int32_t bitnum = getgdesc(GD_BITS_NORM_DBL_RED) + getgdesc(GD_BITS_NORM_DBL_GREEN) 
    	    	+ getgdesc(GD_BITS_NORM_DBL_BLUE);
    if (bitnum < 12)
	ra->setDoubleBuffer(FALSE);
#endif
    
    // add leave window events
    focus = new SoXtInputFocus(LeaveWindowMask);
    ra->registerDevice(focus);
    
    // build the menu
    getPaletteNamesAndLoad();
    Widget menu = buildMenu(form);

    widgetList[MAT_LABEL] = XmCreateLabelGadget(form, "matLabel", NULL, 0);
    
    //
    // make sure things are updated
    //
    updateMaterialName();
    updateWindowTitle();
    updateFileMenu();
    updateEditMenu();
    
    //
    // layout !
    //
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment,     	XmATTACH_FORM); n++;
    XtSetValues(menu, args, n);
    
    n = 0;
    XtSetArg(args[n], XmNleftAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomOffset,     	7); n++;
    XtSetArg(args[n], XmNalignment,     	XmALIGNMENT_CENTER); n++;
    XtSetValues(widgetList[MAT_LABEL], args, n);
    
    n = 0;
    XtSetArg(args[n], XmNtopAttachment,     	XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget,     	menu); n++;
    XtSetArg(args[n], XmNtopOffset,     	5); n++;
    XtSetArg(args[n], XmNleftAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftOffset,     	5); n++;
    XtSetArg(args[n], XmNrightAttachment,     	XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightOffset,     	5); n++;
    XtSetArg(args[n], XmNbottomAttachment,     	XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNbottomWidget,     	widgetList[MAT_LABEL]); n++;
    XtSetArg(args[n], XmNbottomOffset,     	5); n++;
    XtSetValues(raWidget, args, n);
    
    // manage children
    XtManageChild(menu);
    XtManageChild(widgetList[MAT_LABEL]);
    XtManageChild(raWidget);
    
    return form;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	creates the menu bar
//
// Use: private
Widget
MyMaterialPalette::buildMenu(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
    int	    n, id;
    Arg	    args[8];
    
    // create top bar menu
    Widget menu = XmCreateMenuBar(parent, "menuBar", NULL, 0);
    widgetList[MENU_BAR] = menu;
    
    Arg popupargs[4];
    int popupn = 0;
#ifdef MENUS_IN_POPUP
    Widget shell = SoXt::getShellWidget(parent);
    SoXt::getPopupArgs(XtDisplay(menu), SCREEN(menu), popupargs, &popupn);
#endif
    
    int itemCount = XtNumber(pulldownData);
    Widget *buttons = new Widget[itemCount+1]; // for palette
    
    int i;
    for (i=0; i < itemCount; i++) {
	// Make Topbar menu button
	Widget subMenu = XmCreatePulldownMenu(menu, "subMenu", popupargs, popupn);
	widgetList[pulldownData[i].id] = subMenu;
	
#ifdef MENUS_IN_POPUP
	// register callbacks to load/unload the pulldown colormap when the
	// pulldown menu is posted.
	SoXt::registerColormapLoad(subMenu, shell);
#endif
	
    	XtSetArg(args[0], XmNsubMenuId, subMenu);
	buttons[i] = XmCreateCascadeButtonGadget(menu, 
	    pulldownData[i].name, args, 1);
	
	// make submenu buttons
	int subItemCount = pulldownData[i].subItemCount;
	Widget *subButtons = new Widget[subItemCount];
	
	for (int j=0; j<subItemCount; j++) {
	    
	    n = 0;
	    XtSetArg(args[n], XmNuserData, this); n++;
	    
	    // check for keyboard accelerator
	    XmString xmstr = NULL;
	    char *accel = pulldownData[i].subMenu[j].accelerator;
	    char *accelText = pulldownData[i].subMenu[j].accelText;
	    if (accel != NULL) {
		XtSetArg(args[n], XmNaccelerator, accel); n++;
		
		if (accelText != NULL) {
		    xmstr = XmStringCreateSimple(accelText);
		    XtSetArg(args[n], XmNacceleratorText, xmstr); n++;
		}
	    }
	    
	    id = pulldownData[i].subMenu[j].id;
	    widgetList[id] = subButtons[j] = XmCreatePushButtonGadget(subMenu, 
		pulldownData[i].subMenu[j].name, args, n);
	    
	    if (xmstr != NULL)
		XmStringFree(xmstr);
	    
	    XtAddCallback(subButtons[j], XmNactivateCallback,
		(XtCallbackProc) MyMaterialPalette::menuCB,
		(XtPointer) (unsigned long) id);
	}
	XtManageChildren(subButtons, subItemCount);
	delete [] subButtons;
    }
    
    // create palette menu (the palette popup menu gets created and deleted
    // on the fly so do it separately).
    widgetList[PALETTE_BUTTON] = buttons[i] = XmCreateCascadeButtonGadget(menu,
	"Palettes", NULL, 0);
    widgetList[PALETTE_MENU] = NULL;
    buildPaletteSubMenu();
    
    XtManageChildren(buttons, itemCount+1); // because of palette
    delete [] buttons;
    
    return menu;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	adds an entry to the palette menu.
//
// Use: private
Widget
MyMaterialPalette::buildPaletteMenuEntry(int id)
//
////////////////////////////////////////////////////////////////////////
{
    PaletteStruct *pal = (PaletteStruct *) paletteList[id];
    char accel[20];
    char accelText[20];
    XmString xmstr = NULL;
    Arg	args[4];
    int n = 0;
    
    XtSetArg(args[n], XmNuserData, this); n++;
    if (id < 10) {
	sprintf(accel, "Alt <Key> %d", id);
	sprintf(accelText, "Alt+%d", id);
	xmstr = XmStringCreateSimple(accelText);
	
	XtSetArg(args[n], XmNaccelerator, accel); n++;
	XtSetArg(args[n], XmNacceleratorText, xmstr); n++;
    }
    
    Widget w = XmCreatePushButtonGadget(widgetList[PALETTE_MENU], pal->name, args, n);
    XtAddCallback(w, XmNactivateCallback,
	(XtCallbackProc) MyMaterialPalette::paletteMenuCB,
	(XtPointer) (unsigned long) id);
    
    if (xmstr != NULL)
	XmStringFree(xmstr);
    
    return w;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	Buils the palette popup menu.
//
// Use: private
void
MyMaterialPalette::buildPaletteSubMenu()
//
////////////////////////////////////////////////////////////////////////
{
    // rebuild the palette popup
    // ??? we cannot delete the old popup menu or things will brake. 
    // ??? Is it automatically deleted for us ?
    Arg args[8];
    int argnum = 0;
#ifdef MENUS_IN_POPUP
    SoXt::getPopupArgs(XtDisplay(widgetList[MENU_BAR]), SCREEN(widgetList[MENU_BAR]), args, &argnum);
#endif
    Widget subMenu = widgetList[PALETTE_MENU] = 
	XmCreatePulldownMenu(widgetList[MENU_BAR], "subMenu", args, argnum);
    
#ifdef MENUS_IN_POPUP
    // register callbacks to load/unload the pulldown colormap when the
    // pulldown menu is posted.
    SoXt::registerColormapLoad(subMenu, SoXt::getShellWidget(widgetList[MENU_BAR]));
#endif
    
    XtSetArg(args[0], XmNsubMenuId, widgetList[PALETTE_MENU]);
    XtSetValues(widgetList[PALETTE_BUTTON], args, 1);
    
    // build all of the entries
    Widget *entries = new Widget[paletteList.getLength()];
    for (int i=0; i<paletteList.getLength(); i++)
	entries[i] = buildPaletteMenuEntry(i);
    
    XtManageChildren(entries, paletteList.getLength());
    delete [] entries;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	Called whenever a menu entry is selected
//
// Use: private
void
MyMaterialPalette::menuCB(Widget w, int id, XmAnyCallbackStruct *cb)
//
////////////////////////////////////////////////////////////////////////
{
    Time eventTime = cb->event->xbutton.time;
    
    // get the class pointer
    MyMaterialPalette *p;
    XtVaGetValues(w, XmNuserData, &p, NULL);
    
    switch(id) {
    case FILE_NEW:
	if (p->paletteChanged) {
	    p->whatToDoNext = BRING_NEW_DIALOG;
	    p->createSaveDialog();
	}
	else {
	    p->whatToDoNext = CREATE_NEW_PALETTE;
	    p->createPromptDialog("New Palette Dialog", "New Palette Name:");
	}
	break;
	
    case FILE_SAVE:
	p->savePalette();
	break;
	
    case FILE_SAVE_AS:
	p->whatToDoNext = SAVE_AS_PALETTE;
	p->createPromptDialog("Save As Dialog", "Save As:");
	break;
	
    case FILE_RESET:
	p->createDeleteDialog("Reset Palette Dialog", 
	    "Reset to default palette ?", "(all changes will be lost)");
	break;
	
    case FILE_DELETE:
	p->createDeleteDialog("Delete Palette Dialog", 
	    "Delete current palette ?", "(all materials will be lost)");
	break;
    
    case EDIT_CUT:
    case EDIT_COPY:
	// copy material
	if (p->clipboard == NULL)
	    p->clipboard = new SoXtClipboard(p->getWidget());
	p->clipboard->copy(p->itemSwitch->getChild(p->selectedItem), eventTime);
	
	// now delete material
	if (id == EDIT_CUT)
	    p->deleteCurrentMaterial();
	break;
	
    case EDIT_PASTE:
	if (p->clipboard == NULL)
	    p->clipboard = new SoXtClipboard(p->getWidget());
	p->clipboard->paste(eventTime, MyMaterialPalette::pasteDone, p);
	break;
	
    case EDIT_DELETE:
	p->deleteCurrentMaterial();
	break;
    }
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Creates the scene graph which consists of repeated spheres with
//  materials.
//
// Use: private
void
MyMaterialPalette::createSceneGraph()
//
////////////////////////////////////////////////////////////////////////
{
    SoInput in;
    SoNode *node;
    SbBool ok;
    SoSearchAction sa;
    SoFullPath *fullPath;
    
    //
    // create the regular scene graph
    //
    
    // read the geometry buffer in
    in.setBuffer((void *)geometryBuffer, (size_t) strlen(geometryBuffer));
    ok = SoDB::read(&in, node);
    if (!ok || node == NULL) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::createSceneGraph",
			    "couldn't read geometry");
	exit(1);
#endif
    }
    ra->setSceneGraph(node);
    
    // search for the switch node which contains all the materials
    sa.setType(SoSwitch::getClassTypeId(), FALSE);
    sa.apply(node);
    if ((fullPath = (SoFullPath *)sa.getPath()) == NULL) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::createSceneGraph",
			    "couldn't find switch node");
	exit(1);
#endif
    }
    itemSwitch = (SoSwitch *) fullPath->getTail();
    
    
    //
    // now create the overlay scene graph
    //
    
    // read the overlay geometry buffer
    in.setBuffer((void *)overlayGeometryBuffer, (size_t) strlen(overlayGeometryBuffer));
    ok = SoDB::read(&in, node);
    if (!ok || node == NULL) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::createSceneGraph",
			    "couldn't read overlay geometry");
	exit(1);
#endif
    }
    ra->setOverlaySceneGraph(node);
    
    // search for the camera in the regular scene to also use it for the overlay
    sa.setType(SoOrthographicCamera::getClassTypeId(), FALSE);
    sa.apply(ra->getSceneGraph());
    if ((fullPath = (SoFullPath *) sa.getPath()) == NULL) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::createSceneGraph",
			    "couldn't find camera");
	exit(1);
#endif
    }
    ((SoGroup *) ra->getOverlaySceneGraph())->insertChild(fullPath->getTail(), 
							  0);
    
    // search for the 2 translation nodes for the lineSet feedback
    sa.setInterest(SoSearchAction::ALL);
    sa.setType(SoTranslation::getClassTypeId(), FALSE);
    sa.apply(ra->getOverlaySceneGraph());
    if (sa.getPaths().getLength() != 2) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::createSceneGraph",
			    "couldn't find 2 translations");
	exit(1);
#endif
    }
    overlayTrans1 =(SoTranslation *)((SoFullPath *)sa.getPaths()[0])->getTail();
    overlayTrans2 =(SoTranslation *)((SoFullPath *)sa.getPaths()[1])->getTail();
    updateOverlayFeedback();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Get the list of directories (palettes) from the env var, or the
//  paletteDir variable (constructor). This also searches for palettes
//  saved under the home directory, which would overide the system
//  installed palettes.
//
// Use: private
void
MyMaterialPalette::getPaletteNamesAndLoad()
//
////////////////////////////////////////////////////////////////////////
{
    struct dirent *direntry;
    DIR  *dirp;
    char *f;
    char currentDir[MAXPATHLEN];
    
    // ??? this is only called once at startup, so it doesn't
    // ??? need to free the old paletteList.
    
    //
    // look for palettes under the default installed place
    //
    
    // see if SO_MATERIAL_DIR is set, if so use it...
    char *envDir = getenv("SO_MATERIAL_DIR");
    if (envDir != NULL) {
	delete paletteDir;
        paletteDir = strdup(envDir);
    }
    
    if (dirp = opendir(paletteDir)) {
	getcwd(currentDir, MAXPATHLEN-1);
        chdir(paletteDir);
        while (direntry = readdir(dirp)) {
	    f = direntry->d_name;
	    // hide '.' files
	    if (f[0] != '.' && isDirectory(f)) {
		 PaletteStruct *pal = new PaletteStruct;
		 pal->name = strdup(f);
		 pal->system = TRUE;
		 pal->user = FALSE;
		 paletteList.append(pal);
	    }
        }
        closedir(dirp);
	chdir(currentDir); // back to our working directory
    }
    
    //
    // Now look for palettes under the user's home directory, which
    // would overide the installed palettes.
    //
    
    char customDir[MAXPATHLEN];
    sprintf(customDir, "%s/%s", getenv("HOME"), customPalDir);
    if (dirp = opendir(customDir)) {
	getcwd(currentDir, MAXPATHLEN-1);
	chdir(customDir);
	while (direntry = readdir(dirp)) {
	    f = direntry->d_name;
	    // hide '.' files
	    if (f[0] != '.' && isDirectory(f)) {
		
		// check if palette name is already in the list
		// (user overide case), else create a new entry
		//
		PaletteStruct *pal = findPalette(f, &paletteList);
		if (pal != NULL)
		    pal->user = TRUE;
		else {
		     pal = new PaletteStruct;
		     pal->name = strdup(f);
		     pal->user = TRUE;
		     pal->system = FALSE;
		     paletteList.append(pal);
		}
	    }
	}
	closedir(dirp);
	chdir(currentDir); // back to our working directory
    }
    
    //
    // make sure we have at least one palette, else create and empty default
    // palette for things to work.
    //
    curPalette = 0;
    if (paletteList.getLength() == 0) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::getPaletteNamesAndLoad",
	"cannot find palettes in directory %s or home directory.  Try setting the environment variable SO_MATERIAL_DIR to a directory which has material files in it.", paletteDir);
#endif
	
	// create an empty default palette, withough loading anything
	// since we started with a blank palette.
	PaletteStruct *pal = new PaletteStruct;
	pal->name = strdup("default");
	pal->user = TRUE;
	pal->system = FALSE;
	paletteList.append(pal);
	
	// set the material empty name, since we are not loading any
	// palette and this is only done once.
	for (int i=0; i< 36; i++) {
	    char str[50];
	    sprintf(str, "no_name_%d", i);
	    mtlNames[i].name = strdup(str);
	}
    }
    else
	// load the first palette
	loadPaletteItems();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Given the current palette, loads all of the materials in the
//  current palette directory.
//
// Use: private
void
MyMaterialPalette::loadPaletteItems()
//
////////////////////////////////////////////////////////////////////////
{
    char palDir[MAXPATHLEN];
    char currentDir[MAXPATHLEN];
    DIR  *dirp;
    int	 i, num = 0;
    
    // delete the existing name strings of materials
    for (i=0; i<36; i++) {
	if (mtlNames[i].name != NULL) {
	    free(mtlNames[i].name);
	    mtlNames[i].name = NULL;
	}
	if (mtlNames[i].oldName != NULL) {
	    free(mtlNames[i].oldName);
	    mtlNames[i].oldName = NULL;
	}
    }
    
    // go to the palette directory (user saved palettes overide system
    // installed palettes).
    PaletteStruct *pal = (PaletteStruct *) paletteList[curPalette];
    if (pal->user)
	sprintf(palDir, "%s/%s/%s", getenv("HOME"), customPalDir, pal->name);
    else
	sprintf(palDir, "%s/%s", paletteDir, pal->name);
    
    if ((dirp = opendir(palDir)) != NULL) {
	// cd to palette directory
	getcwd(currentDir, MAXPATHLEN-1);
	chdir(palDir);
	
	// loop throught the files, and open any material files
	char *f;
	struct dirent *direntry;
	SoNode *mat;
	
	while ((direntry = readdir(dirp)) && num < 35) {
	    f = direntry->d_name;
	    if (mat = getMaterialFromFile(f)) {
		itemSwitch->replaceChild(num, mat);
		mtlNames[num].name = strdup(f);
		num++;
	    }
	}
	
	closedir(dirp);
	chdir(currentDir); // back to our working directory
    }
    
    // now clear the reminder materials in the palette (if any)
    for (i=num; i< 36; i++) {
	char str[50];
	sprintf(str, "no_name_%d", i);
	itemSwitch->replaceChild(i, new SoMaterial);
	mtlNames[i].name = strdup(str);
    }
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	returns a material from the given file name (or NULL if the file 
//  isn't valid or contain a material). The file will be opened from the cwd
//  (call chdir() before calling this).
//
// Use: private
SoNode *
MyMaterialPalette::getMaterialFromFile(char *file)
//
////////////////////////////////////////////////////////////////////////
{
    // ignore '.' files
    if (file[0] == '.')
	return NULL;
    
    // ignore directories
    if (isDirectory(file))
	return NULL;
    
    // open the file and read the material in (assume 1 material per file)
    SoNode *node;
    SoInput in;
    if ( ! in.openFile(file))
	return NULL;
    if (! SoDB::read(&in, node) || node == NULL)
	return NULL;
    
    // now check to make sure node is a material
    if (node->isOfType(SoMaterial::getClassTypeId()))
	return node;
    else {
	// nuke that node...
	node->ref();
	node->unref();
    }
    return NULL;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Called whenever an event occurs in the render area.
//
// Use: private
SbBool
MyMaterialPalette::handleEvent(XAnyEvent *xe)
//
////////////////////////////////////////////////////////////////////////
{
    switch(xe->type) {
	case ButtonPress:
	{
	    XButtonEvent *be = (XButtonEvent *)xe;
	    if (be->button != Button1)
		break;
	    
	    // it is possible to get a mouse down event withought
	    // previously receiving a mouse motion event (when the 
	    // editor window is closed on top of the palette and the 
	    // mouse doesn't move at all). In that case, find which
	    // item we are clicking on...
	    if (currentItem == -1)
		findCurrentItem(be->x, be->y);
	    
	    //
	    // check for double click. If the time difference between
	    // successive mouse down is less than 3/10th sec, then 
	    // consider it a double click.
	    // ??? do we need to worry about time warping ?
	    //
	    if ((be->time - prevTime) < DOUBLE_CLICK_TIME) {
		// allocate the editor on the fly....
		if (matEditor == NULL) {
		    matEditor = new MySimpleMaterialEditor(
			    SoXt::getShellWidget(getWidget()),
			    NULL, FALSE, TRUE);
		    matEditor->addCallback(MyMaterialPalette::matEditorCB, this);
		    matEditor->setTitle("Material Palette Editor");
		    matEditor->setIconTitle("Mat Pal Editor");
		}
		
		// update the editor if it wasn't already on the screen
		// (would have otherwise been updated by the first click)
		if (! matEditor->isVisible()) {
		    matEditor->setMaterial((SoMaterial *) itemSwitch->getChild(selectedItem));
		    matEditor->setMaterialName(mtlNames[selectedItem].name);
		}
		matEditor->show();
	    }
	    else {	// single click (or first click of the double click)
		// select the current material
		if (selectedItem != currentItem) {
		    
		    int oldVal = selectedItem;
		    selectedItem = currentItem;
		    if (oldVal == -1)
			updateEditMenu();
		    updateOverlayFeedback();
		    
		    callbackList.invokeCallbacks(itemSwitch->getChild(selectedItem));
		}
		
		// update the material editor if it is on the screen
		if (matEditor != NULL && matEditor->isVisible()) {
		    matEditor->setMaterial((SoMaterial *) itemSwitch->getChild(selectedItem));
		    matEditor->setMaterialName(mtlNames[selectedItem].name);
		}
	    }
	    
	    prevTime = be->time;
	}
	break;
	
	case MotionNotify:
	{
	    XMotionEvent *me = (XMotionEvent *)xe;
	    findCurrentItem(me->x, me->y);
	}
	break;
	
	case LeaveNotify:
	    currentItem = -1;
	    updateMaterialName();
	    updateOverlayFeedback();
	    break;
#if 0
	case KeyPress:
	    if (XLookupKeysym((XKeyEvent *)xe, 0) == XK_Escape)
		exit(0);
#endif
    }

    // always handle the events...
    return TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	figure out which tile the mouse is over, and make that tile
// the current item (not the seleted item) - this is called when the
// mouse moves over our window.
//
// Use: private
void
MyMaterialPalette::findCurrentItem(int x, int y)
//
////////////////////////////////////////////////////////////////////////
{
    // note: this picking by region will only work
    // if the RA widget is square (which it is since
    // we are forcing the window to resize in even increments).
    
    SbVec2s raSize = ra->getSize();
    int xpos = int (floorf( 6 * x / float(raSize[0]) ));
    int ypos = int (floorf( 6 * y / float(raSize[1]) ));
    int whichItem = xpos + 6 * ypos;
    if (whichItem != currentItem) {
	currentItem = whichItem;
	updateMaterialName();
	updateOverlayFeedback();
    }
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	changes the overlay feedback based on current state (what mat 
//  is selected and what mat the mouse is over).
//
// Use: private
void
MyMaterialPalette::updateOverlayFeedback()
//
////////////////////////////////////////////////////////////////////////
{
    int row, col;
    
    if (selectedItem != -1) {
	row = (int) (selectedItem / 6);
	col = selectedItem - 6 * row;
	overlayTrans1->translation.setValue(col+1, 6-row, 0);
    }
    else
	overlayTrans1->translation.setValue(-10, -10, 0);
    
    if (currentItem != -1 && currentItem != selectedItem) {
	row = (int) (currentItem / 6);
	col = currentItem - 6 * row;
	overlayTrans2->translation.setValue(col+1, 6-row, 0);
    }
    else
	overlayTrans2->translation.setValue(-10, -10, 0);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	updates the material name label widget, based on the current
//  material.
//
// Use: private
void
MyMaterialPalette::updateMaterialName()
//
////////////////////////////////////////////////////////////////////////
{
    char *str = (currentItem < 0) ? ((selectedItem < 0) ? (char *) " " : 
	mtlNames[selectedItem].name) : mtlNames[currentItem].name;
    XmString xmstr = XmStringCreateSimple(str);
    XtVaSetValues(widgetList[MAT_LABEL], XmNlabelString, xmstr, NULL);
    XmStringFree(xmstr);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Called to enable/disable the cut/copy/paste/delete entries
//
// Use: private
void
MyMaterialPalette::updateEditMenu()
//
////////////////////////////////////////////////////////////////////////
{
    if (widgetList[EDIT_MENU] == NULL)
	return;
    
    Arg args[1];
    XtSetArg(args[0], XmNsensitive, selectedItem >= 0);
    XtSetValues(widgetList[EDIT_CUT], args, 1);
    XtSetValues(widgetList[EDIT_COPY], args, 1);
    XtSetValues(widgetList[EDIT_PASTE], args, 1);
    XtSetValues(widgetList[EDIT_DELETE], args, 1);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Called to enable/disable the "File" menu entries
//
// Use: private
void
MyMaterialPalette::updateFileMenu()
//
////////////////////////////////////////////////////////////////////////
{
    if (widgetList[FILE_MENU] == NULL)
	return;
    
    PaletteStruct *pal = (PaletteStruct *) paletteList[curPalette];
    
    XtVaSetValues(widgetList[FILE_RESET], XmNsensitive, 
	pal->user && pal->system, NULL);
    XtVaSetValues(widgetList[FILE_DELETE], XmNsensitive, 
	pal->user && !pal->system, NULL);
}

////////////////////////////////////////////////////////////////////////
//
// show the component
//
// usage: virtual public
//
void
MyMaterialPalette::show()
//
////////////////////////////////////////////////////////////////////////
{
    SoXtComponent::show();
    
    // now also show the material editor (if it was shown)
    if (matEditor != NULL && matEditor->getWidget() != NULL)
	matEditor->show();
}

////////////////////////////////////////////////////////////////////////
//
// hide the component
//
// usage: virtual public
//
void
MyMaterialPalette::hide()
//
////////////////////////////////////////////////////////////////////////
{
    SoXtComponent::hide();
    
    // now also hide the material editor
    if (matEditor != NULL)
	matEditor->hide();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	This creates a new palette with the given name (called when the
//  user wants a new palette using the 'new' button).
//
// Use: private
void
MyMaterialPalette::createNewPalette(char *palName)
//
////////////////////////////////////////////////////////////////////////
{
    //
    // create that empty palette directory (under the user's home
    // directory).
    //
    char dirName[MAXPATHLEN];
    struct stat buf;
    sprintf(dirName, "%s/%s/", getenv("HOME"), customPalDir);
    if (stat(dirName, &buf) != 0)
	mkdir(dirName, 0x1ff);
    strcat(dirName, palName);
    if (mkdir(dirName, 0x1ff) != 0) {
#ifdef DEBUG
	SoDebugError::post("MyMaterialPalette::createNewPalette",
			    "couldn't create directory %s", dirName);
#endif
	return;
    }
    
    //
    // add the palette to the popup menu
    //
    PaletteStruct *pal = new PaletteStruct;
    pal->name = strdup(palName);
    pal->user = TRUE;
    pal->system = FALSE;
    paletteList.append(pal);
    int id = paletteList.getLength() - 1;
    Widget entry = buildPaletteMenuEntry(id);
    XtManageChild(entry);
    
    //
    // now make this the current palette (by making it like the
    // user picked it from the popup menu).
    //
    paletteMenuCB(entry, id, NULL);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Creates a dialog to ask the user if he/she wishes to save the
//  current palette (called when a new palette is about to be installed,
//  but the old one hasn't been saved and has changed).
//
// Use: private
void
MyMaterialPalette::createSaveDialog()
//
////////////////////////////////////////////////////////////////////////
{
    Arg args[5];
    XmString xmstr = XmStringCreateSimple("Warning: current palette was modified.");
    xmstr = XmStringConcat(xmstr, XmStringSeparatorCreate());
    xmstr = XmStringConcat(xmstr, XmStringCreateSimple("Save changes ?"));
    
    int n = 0;
    XtSetArg(args[n], XmNautoUnmanage, FALSE); n++;
    XtSetArg(args[n], XtNtitle, "Save Palette Dialog"); n++;
    XtSetArg(args[n], XmNmessageString, xmstr); n++;
    Widget dialog = XmCreateWarningDialog(getWidget(), "SaveDialog", args, n);
    XmStringFree(xmstr);
    
    XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_SEPARATOR));
    
    // register callback to destroy (and not just unmap) the dialog
    XtAddCallback(dialog, XmNokCallback, 
	(XtCallbackProc) MyMaterialPalette::saveDialogCB, (XtPointer) this);
    XtAddCallback(dialog, XmNcancelCallback, 
	(XtCallbackProc) MyMaterialPalette::saveDialogCB, (XtPointer) this);
    
    XtManageChild(dialog);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	creates a dialog which lets the user type a name.
//
// Use: private
void
MyMaterialPalette::createPromptDialog(char *title, char *str)
//
////////////////////////////////////////////////////////////////////////
{
    Arg args[5];
    XmString xmstr = XmStringCreateSimple(str);
    
    int n = 0;
    XtSetArg(args[n], XmNautoUnmanage, FALSE); n++;
    XtSetArg(args[n], XtNtitle, title); n++;
    XtSetArg(args[n], XmNselectionLabelString, xmstr); n++;
    Widget dialog = XmCreatePromptDialog(getWidget(), "promptDialog", args, n);
    XmStringFree(xmstr);
    
    XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SEPARATOR));
    
    // register callback to destroy (and not just unmap) the dialog
    // and retreive the text (ok push button case).
    XtAddCallback(dialog, XmNokCallback, 
	(XtCallbackProc) MyMaterialPalette::promptDialogCB, (XtPointer) this);
    XtAddCallback(dialog, XmNcancelCallback, 
	(XtCallbackProc) MyMaterialPalette::promptDialogCB, (XtPointer) this);
    
    XtManageChild(dialog);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	create a dialog which lets the user change it's mind about deleting
//  the current material palette.
//
// Use: private
void
MyMaterialPalette::createDeleteDialog(char *title, char *str1, char *str2)
//
////////////////////////////////////////////////////////////////////////
{
    Arg args[5];
    XmString xmstr = XmStringCreateSimple(str1);
    xmstr = XmStringConcat(xmstr, XmStringSeparatorCreate());
    xmstr = XmStringConcat(xmstr, XmStringCreateSimple(str2));
    
    int n = 0;
    XtSetArg(args[n], XmNautoUnmanage, FALSE); n++;
    XtSetArg(args[n], XtNtitle, title); n++;
    XtSetArg(args[n], XmNmessageString, xmstr); n++;
    Widget dialog = XmCreateWarningDialog(getWidget(), "DeleteDialog", args, n);
    XmStringFree(xmstr);
    
    XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_SEPARATOR));
    
    // register callback to destroy (and not just unmap) the dialog
    XtAddCallback(dialog, XmNokCallback, 
	(XtCallbackProc) MyMaterialPalette::deleteDialogCB, (XtPointer) this);
    XtAddCallback(dialog, XmNcancelCallback, 
	(XtCallbackProc) MyMaterialPalette::deleteDialogCB, (XtPointer) this);
    
    XtManageChild(dialog);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Saves the current palette to the given palette name, and switch
//  to it (i.e. create a new palette of the given name withought changing
//  the materials, and write it out).
//
// Use: private
void
MyMaterialPalette::savePaletteAs(char *palName)
//
////////////////////////////////////////////////////////////////////////
{
    //
    // add the palette to the popup menu
    //
    PaletteStruct *pal = new PaletteStruct;
    pal->name = strdup(palName);
    pal->user = TRUE;
    pal->system = FALSE;
    paletteList.append(pal);
    int id = paletteList.getLength() - 1;
    XtManageChild(buildPaletteMenuEntry(id));
    
    //
    // make this the current palette and save it out
    //
    curPalette = id;
    savePalette();
    updateWindowTitle();
    updateFileMenu();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	This saves the current palette to the user's home directory.
//
// Use: private
void
MyMaterialPalette::savePalette()
//
////////////////////////////////////////////////////////////////////////
{
    int i;
    char currentDir[MAXPATHLEN];
    getcwd(currentDir, MAXPATHLEN-1);
    
    // go to the materials directory
    chdir(getenv("HOME"));
    if (chdir(customPalDir) != 0) {
	// create that directory and cd to it
	mkdir(customPalDir, 0x1ff);
	chdir(customPalDir);
    }
    
    // now go to the palette directory (or create it)
    char *palName = ((PaletteStruct *) paletteList[curPalette])->name;
    if (chdir(palName) != 0) {
	mkdir(palName, 0x1ff);
	chdir(palName);
    }
    
    // delete any of the old material files if any 
    // (material that changed name since last time)
    for (i=0; i<36; i++) {
	if (mtlNames[i].oldName != NULL) {
	    unlink(mtlNames[i].oldName);
	    free(mtlNames[i].oldName);
	    mtlNames[i].oldName = NULL;
	}
    }
    
    // now save all of the materials 
    // (even the empty one, this way we remember the order the palette has)
    SoWriteAction writeAct;
    SoOutput *out = writeAct.getOutput();
    for (i=0; i < 36; i++) {
	out->openFile( mtlNames[i].name );
	writeAct.apply( itemSwitch->getChild(i) );
	out->closeFile();
    }
    
    chdir(currentDir); // back to our working directory
    
    // clear the changed flag
    if (paletteChanged) {
	paletteChanged = FALSE;
	updateWindowTitle();
    }
    ((PaletteStruct *) paletteList[curPalette])->user = TRUE;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	switches to the new palette (defined by "nextPalette" var). This
//  is called (maybe indirectly) when a new palette is choosen from the
//  palette popup menu.
//
// Use: private
void
MyMaterialPalette::switchPalette()
//
////////////////////////////////////////////////////////////////////////
{
    // assign new palette id
    curPalette = nextPalette;
    
    // get the new materials
    loadPaletteItems();
    paletteChanged = FALSE;
    
    // update the material name (nothing selected)
    deselectCurrentItem();
    updateWindowTitle();
    updateFileMenu();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	deletes the current material (reset to default value and name)
//
// Use: private
void
MyMaterialPalette::deleteCurrentMaterial()
//
////////////////////////////////////////////////////////////////////////
{
    // replace material with default empty one
    itemSwitch->replaceChild(selectedItem, new SoMaterial);
    
    // save original name for file removal and assign new default name
    if (mtlNames[selectedItem].oldName == NULL)
	mtlNames[selectedItem].oldName = mtlNames[selectedItem].name;
    else
	free(mtlNames[selectedItem].name);
    char str[50];
    sprintf(str, "no_name_%d", selectedItem);
    mtlNames[selectedItem].name = strdup(str);
    
    // update label and editor
    updateMaterialName();
    if (matEditor != NULL && matEditor->isVisible()) {
	matEditor->setMaterial((SoMaterial *) itemSwitch->getChild(selectedItem));
	matEditor->setMaterialName(mtlNames[selectedItem].name);
    }
    
    if (!paletteChanged) {
	paletteChanged = TRUE;
	updateWindowTitle();
    }
    
    callbackList.invokeCallbacks(itemSwitch->getChild(selectedItem));
}

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

const char *
MyMaterialPalette::getDefaultTitle() const
{ return "Material Palette Gizmo"; }

const char *
MyMaterialPalette::getDefaultIconTitle() const
{ return "Mat Palette"; }




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





SbBool
MyMaterialPalette::raEventCB(void *p, XAnyEvent *xe)
{ return (((MyMaterialPalette *)p)->handleEvent(xe)); }

//
// called when the save dialog "ok"/"Cancel" buttons gets pressed.
//
void
MyMaterialPalette::saveDialogCB(Widget dialog, MyMaterialPalette *p, 
				    XmAnyCallbackStruct *cb)
{
    // save the palette and destroy the dialog
    if (cb->reason == XmCR_OK)
	p->savePalette();
    
    XtDestroyWidget(dialog);
    
    // now check what needs to be done, now that the dialog is gone
    switch(p->whatToDoNext) {
	case BRING_NEW_DIALOG:
	    p->whatToDoNext = CREATE_NEW_PALETTE;
	    p->createPromptDialog("New Palette Dialog", "New Palette Name:");
	    break;
	    
	case SWITCH_PALETTE:
	    p->switchPalette();
	    break;
    }
}

//
// called when the delete dialog "ok"/"Cancel" buttons gets pressed. This
// will delete the palette in the user's home directory.
//
void
MyMaterialPalette::deleteDialogCB(Widget dialog, MyMaterialPalette *p, 
				    XmAnyCallbackStruct *cb)
{
    // remove the user's palette
    if (cb->reason == XmCR_OK) {
	
	PaletteStruct *pal = (PaletteStruct *) p->paletteList[p->curPalette];
	
	// remove the palette directory in user's home
	char palDir[MAXPATHLEN];
	sprintf(palDir, "%s/%s/%s", getenv("HOME"), customPalDir, pal->name);
	unlink(palDir);
	
	// check if palette was also in the installed place (reset vs delete)
	if (pal->system)
	    pal->user = FALSE;
	else {
	    // remove the palette from the list, making sure we have
	    // at least one palette entry
	    free(pal->name);
	    if (p->paletteList.getLength() == 1) {
		// create an empty default palette
		pal->name = strdup("default");
	    }
	    else {
		delete pal;
		p->paletteList.remove( p->curPalette );
		
		// check what palette will be next
		if (p->curPalette == p->paletteList.getLength())
		    p->curPalette--;
	    }
	    
	    // rebuild the new menu ( ??? have to rebuild, since we can't
	    // remove entries)
	    p->buildPaletteSubMenu();
	}
	
	// finaly load the new palette
	p->nextPalette = p->curPalette;
	p->switchPalette();
    }
    
    XtDestroyWidget(dialog);
}

//
// called whenever the "ok"/"Cancel" buttons within the generic prompt
// dialog get pressed.
//
void
MyMaterialPalette::promptDialogCB(Widget dialog, MyMaterialPalette *p, 
				    XmAnyCallbackStruct *cb)
{   
    if (cb->reason == XmCR_OK) {
	
	// retreive text
	Widget field = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
	char *str = XmTextGetString(field);
	
	// do the right thing
	if (str[0] != '\0') {
	    switch(p->whatToDoNext) {
		case CREATE_NEW_PALETTE:
		    p->createNewPalette(str);
		    break;
		
		case SAVE_AS_PALETTE:
		    p->savePaletteAs(str);
		    break;
	    }
	}
	
	XtFree(str);
    }
    
    XtDestroyWidget(dialog);
}

//
// called whenever the material editor apply button gets pressed.
// reteive the material and material name, and invoke the
// callbaks.
//
void
MyMaterialPalette::matEditorCB(void *pt, MySimpleMaterialEditor *ed)
{
    MyMaterialPalette *p = (MyMaterialPalette *)pt;
    
    // no material currently selected
    if (p->selectedItem < 0)
	return;
    
    // copy the material over
    SoMaterial *mat1 = (SoMaterial *) p->itemSwitch->getChild(p->selectedItem);
    const SoMaterial *mat2 = ed->getMaterial();
    mat1->ambientColor = mat2->ambientColor[0];
    mat1->diffuseColor = mat2->diffuseColor[0];
    mat1->specularColor = mat2->specularColor[0];
    mat1->emissiveColor = mat2->emissiveColor[0];
    mat1->shininess = mat2->shininess[0];
    mat1->transparency = mat2->transparency[0];
    
    // copy the material name over and update label (if name has changed)
    const char *str = ed->getMaterialName();
    if (str != NULL && strcmp(str, p->mtlNames[p->selectedItem].name) != 0) {
	
	// save the old material name file for later removal
	if (p->mtlNames[p->selectedItem].oldName == NULL)
	    p->mtlNames[p->selectedItem].oldName = p->mtlNames[p->selectedItem].name;
	else
	    free(p->mtlNames[p->selectedItem].name);
	
	p->mtlNames[p->selectedItem].name = strdup(str);
	p->updateMaterialName();
    }
    
    if (! p->paletteChanged) {
	p->paletteChanged = TRUE;
	p->updateWindowTitle();
    }
    
    // finally invoke the callbacks
    p->callbackList.invokeCallbacks((void *) mat1);
}

//
//  Called whenever a new item menu is selected from the palette
//  popup menu.
//
void
MyMaterialPalette::paletteMenuCB(Widget w, int num, void *)
{
    // get the class pointer
    MyMaterialPalette *p;
    XtVaGetValues(w, XmNuserData, &p, NULL);
    
    // return if the same palette is choosen
    if (p->curPalette == num)
	return;
    
    // save the new palette id for later uses
    p->nextPalette = num;
    
    // check to make sure the old palette hasn't changed withought
    // being saved.
    if (p->paletteChanged) {
	p->createSaveDialog();
	// this will call switchPalette() once the dialog disapears
	p->whatToDoNext = SWITCH_PALETTE;
    }
    else
	p->switchPalette();
}

//
// called whenever the X server is done doing the paste
//
void
MyMaterialPalette::pasteDone(void *pt, SoPathList *pathList)
{
    MyMaterialPalette *p = (MyMaterialPalette *)pt;
    
    SoSearchAction sa;
    SoFullPath *fullPath = NULL;
    
    //
    // search for first material in that pasted scene
    //
    sa.setType(SoMaterial::getClassTypeId());
    for (int i=0; i < pathList->getLength(); i++) {
	sa.apply( (*pathList)[i] );
	if ( (fullPath = (SoFullPath *) sa.getPath()) != NULL) {
	    
	    // assign new material, update editor and invoke callback
	    SoMaterial *newMat = (SoMaterial *) fullPath->getTail();
	    p->itemSwitch->replaceChild(p->selectedItem, newMat);
	    if (p->matEditor != NULL && p->matEditor->isVisible())
		p->matEditor->setMaterial(newMat);
	    if (! p->paletteChanged) {
		p->paletteChanged = TRUE;
		p->updateWindowTitle();
	    }
	    p->callbackList.invokeCallbacks(newMat);
	    
	    break;
	}
    }
    
    // ??? We delete the callback data when done with it.
    delete pathList;
}

#undef SCREEN
