/*
 *
 *  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/
 *
 */

/*------------------------------------------------------------
 *  This is an example from The Inventor Mentor
 *  chapter 10, example 3.
 *
 *  The scene graph has 4 objects which may be
 *  selected by picking with the left mouse button
 *  (use shift key to extend the selection to more
 *  than one object).
 *  
 *  Hitting the up arrow key will increase the size of
 *  each selected object; hitting down arrow will decrease
 *  the size of each selected object.
 *
 *  This also demonstrates selecting objects from a Motif
 *  list, and calling select/deselect functions on the
 *  SoSelection node to change the selection. Use the Shift
 *  key to extend the selection (i.e. pick more than one
 *  item in the motif list.)
 *-----------------------------------------------------------*/

#include <cstdlib>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/List.h>

#include <Inventor/Sb.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtRenderArea.h>
#include <Inventor/actions/SoBoxHighlightRenderAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoCylinder.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoEventCallback.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoSelection.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTransform.h>

// Function prototypes
void mySelectionCB(void *, SoPath *);
void myKeyPressCB(void *, SoEventCallback *);
void myScaleSelection(SoSelection *, float);
Widget createList(Display *, SoSelection *);
void myListPickCB(Widget, char *, XmListCallbackStruct *);

// Global data
SbViewportRegion viewportRegion;
Widget motifList;
SoTransform *cubeTransform, *sphereTransform,
            *coneTransform, *cylTransform;

///////////////////////////////////////////////////////////////
// CODE FOR The Inventor Mentor STARTS HERE  

enum objects {
	CUBE,
	SPHERE,
	CONE,
	CYL,
	NUM_OBJECTS
};

static char *objectNames[] = {
	"Cube",
	"Sphere",
	"Cone",
	"Cylinder"
};

// CODE FOR The Inventor Mentor ENDS HERE  
///////////////////////////////////////////////////////////////

int
main(int , char **argv)
{
   // Print out usage message
   printf("Left mouse button        - selects object\n");
   printf("<shift>Left mouse button - selects multiple objects\n");
   printf("Up and Down arrows       - scale selected objects\n");

   // Initialize Inventor and Xt
   Widget myWindow = SoXt::init(argv[0]);
   if (myWindow == NULL) exit(1);

   // Create and set up the selection node
   SoSelection *selectionRoot = new SoSelection;
   selectionRoot->ref();
   selectionRoot->policy = SoSelection::SHIFT;
   selectionRoot->addSelectionCallback(
            mySelectionCB, (void *) TRUE);
   selectionRoot->addDeselectionCallback(
            mySelectionCB, (void *) FALSE);
   
   // Add a camera and some light
   SoPerspectiveCamera *myCamera = new SoPerspectiveCamera;
   selectionRoot->addChild(myCamera);
   selectionRoot->addChild(new SoDirectionalLight);

   // Add an event callback so we can receive key press events
   SoEventCallback *myEventCB = new SoEventCallback;
   myEventCB->addEventCallback(
            SoKeyboardEvent::getClassTypeId(), 
            myKeyPressCB, selectionRoot);
   selectionRoot->addChild(myEventCB);

   // Add some geometry to the scene
   // a red cube
   SoSeparator *cubeRoot = new SoSeparator; 
   SoMaterial *cubeMaterial = new SoMaterial;
   cubeTransform = new SoTransform;
   SoCube *cube = new SoCube;
   cubeRoot->addChild(cubeTransform);
   cubeRoot->addChild(cubeMaterial);
   cubeRoot->addChild(cube);
   cubeTransform->translation.setValue(-2, 2, 0);
   cubeMaterial->diffuseColor.setValue(.8, 0, 0);
   selectionRoot->addChild(cubeRoot);

   // a blue sphere
   SoSeparator *sphereRoot = new SoSeparator; 
   SoMaterial *sphereMaterial = new SoMaterial;
   sphereTransform = new SoTransform;
   SoSphere *sphere = new SoSphere;
   sphereRoot->addChild(sphereTransform);
   sphereRoot->addChild(sphereMaterial);
   sphereRoot->addChild(sphere);
   sphereTransform->translation.setValue(2, 2, 0);
   sphereMaterial->diffuseColor.setValue(0, 0, .8);
   selectionRoot->addChild(sphereRoot);

   // a green cone
   SoSeparator *coneRoot = new SoSeparator;
   SoMaterial *coneMaterial = new SoMaterial;
   coneTransform = new SoTransform;
   SoCone *cone = new SoCone;
   coneRoot->addChild(coneTransform);
   coneRoot->addChild(coneMaterial);
   coneRoot->addChild(cone);
   coneTransform->translation.setValue(2, -2, 0);
   coneMaterial->diffuseColor.setValue(0, .8, 0);
   selectionRoot->addChild(coneRoot);

   // a magenta cylinder
   SoSeparator *cylRoot = new SoSeparator;
   SoMaterial *cylMaterial = new SoMaterial;
   cylTransform = new SoTransform;
   SoCylinder *cyl = new SoCylinder;
   cylRoot->addChild(cylTransform);
   cylRoot->addChild(cylMaterial);
   cylRoot->addChild(cyl);
   cylTransform->translation.setValue(-2, -2, 0);
   cylMaterial->diffuseColor.setValue(.8, 0, .8);
   selectionRoot->addChild(cylRoot);

///////////////////////////////////////////////////////////////
// CODE FOR The Inventor Mentor STARTS HERE  (part 2)

   cube->setName(objectNames[CUBE]);
   sphere->setName(objectNames[SPHERE]);
   cone->setName(objectNames[CONE]);
   cyl->setName(objectNames[CYL]);

// CODE FOR The Inventor Mentor ENDS HERE  
///////////////////////////////////////////////////////////////

   // Create a render area for viewing the scene
   SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);
   myRenderArea->setGLRenderAction(new SoBoxHighlightRenderAction());
   myRenderArea->redrawOnSelectionChange(selectionRoot);
   myRenderArea->setSceneGraph(selectionRoot);
   myRenderArea->setTitle("Motif Selection List");

   // Make the camera see the whole scene
   viewportRegion = myRenderArea->getViewportRegion();
   myCamera->viewAll(selectionRoot, viewportRegion, 2.0);

   // Create a Motif list for selecting objects without picking
   Widget objectList = createList(
            XtDisplay(myWindow), selectionRoot);

   // Show our application window, and loop forever...
   myRenderArea->show();
   SoXt::show(myWindow);
   SoXt::show(objectList);
   SoXt::mainLoop();
}


// Create the object list widget
Widget
createList(Display *display, SoSelection *selection)
{
   Widget shell;
   Arg    args[4];
   int    i, n;

   // Create a new shell window for the list
   n = 0;
   XtSetArg(args[n], XmNtitle, "Selection");
   n++;
   shell = XtAppCreateShell("example", "Inventor",
      topLevelShellWidgetClass, display, args, n);

///////////////////////////////////////////////////////////////
// CODE FOR The Inventor Mentor STARTS HERE  (part 3)

   // Create a table of object names
   XmString *table = new XmString[NUM_OBJECTS];
   for (i=0; i<NUM_OBJECTS; i++) {
       table[i] = XmStringCreate(objectNames[i], 
				 XmSTRING_DEFAULT_CHARSET);
   }

   // Create the list widget
   n = 0;
   XtSetArg(args[n], XmNitems, table);
   n++;
   XtSetArg(args[n], XmNitemCount, NUM_OBJECTS);
   n++;
   XtSetArg(args[n], XmNselectionPolicy, XmEXTENDED_SELECT);
   n++;

   motifList = XmCreateScrolledList(shell, "funcList", args, n);
   XtAddCallback(motifList, XmNextendedSelectionCallback,
      (XtCallbackProc) myListPickCB, (XtPointer) selection);

// CODE FOR The Inventor Mentor ENDS HERE  
///////////////////////////////////////////////////////////////

   // Free the name table
   for (i = 0; i < NUM_OBJECTS; i++)
      XmStringFree(table[i]);
   free(table);

   // Manage the list and return the shell
   XtManageChild(motifList);

   return shell;
}

// This callback is invoked every time the user picks
// an item in the Motif list.
void
myListPickCB(Widget, char *userData,
            XmListCallbackStruct *listData)
{
   SoSelection *selection = (SoSelection *) userData;
   SoSearchAction mySearchAction;

   // Remove the selection callbacks so that we don't get
   // called back while we are updating the selection list
   selection->removeSelectionCallback(
            mySelectionCB, (void *) TRUE);
   selection->removeDeselectionCallback(
            mySelectionCB, (void *) FALSE);

//////////////////////////////////////////////////////////////
// CODE FOR The Inventor Mentor STARTS HERE  (part 4)

   // Clear the selection node, then loop through the list
   // and reselect
   selection->deselectAll();

   // Update the SoSelection based on what is selected in
   // the motif list.  We do this by extracting the string
   // from the selected XmString, and searching for the 
   // object of that name.
   for (int i = 0; i < listData->selected_item_count; i++) {
      mySearchAction.setName(
            SoXt::decodeString(listData->selected_items[i]));
      mySearchAction.apply(selection);
      selection->select(mySearchAction.getPath());
   }

// CODE FOR The Inventor Mentor ENDS HERE
//////////////////////////////////////////////////////////////

   // Add the selection callbacks again
   selection->addSelectionCallback(
            mySelectionCB, (void *) TRUE);
   selection->addDeselectionCallback(
            mySelectionCB, (void *) FALSE);
}


// This is called whenever an object is selected or deselected.
// if userData is TRUE, then it's a selection; else deselection.
// (we set this convention up when we registered this callback).
// The function updates the Motif list to reflect the current
// selection.
void
mySelectionCB(void *userData, SoPath *selectionPath)
{
   Arg args[1];
   SbBool isSelection = (SbBool) (userData != NULL);

   // We have to temporarily change the selection policy to
   // MULTIPLE so that we can select and deselect single items.
   XtSetArg(args[0], XmNselectionPolicy, XmMULTIPLE_SELECT);
   XtSetValues(motifList, args, 1);

   SoNode *node = selectionPath->getTail();

   for (int i = 0; i < NUM_OBJECTS; i++) {
      if (node->getName() == objectNames[i]) {
         if (isSelection) {
             XmListSelectPos(motifList, i+1, False);
         } else XmListDeselectPos(motifList, i+1);
         XmUpdateDisplay(motifList);
         break;
      }
   }

   // Restore the selection policy to extended.
   XtSetArg(args[0], XmNselectionPolicy, XmEXTENDED_SELECT);
   XtSetValues(motifList, args, 1);
}

// If the event in the graphics window is down arrow, then 
// scale every object in the selection list by 1/2; if the 
// event is up arrow, scale by 2.  
// userData is the selectionRoot from main().
void
myKeyPressCB(void *userData, SoEventCallback *eventCB)
{
   SoSelection *selection = (SoSelection *) userData;
   const SoEvent *event = eventCB->getEvent();

   // check for the Up and Down arrow keys being pressed
   if (SO_KEY_PRESS_EVENT(event, UP_ARROW)) {
      myScaleSelection(selection, 1.1);
      eventCB->setHandled();
   } else if (SO_KEY_PRESS_EVENT(event, DOWN_ARROW)) {
      myScaleSelection(selection, 1.0/1.1);
      eventCB->setHandled();
   }
}

//////////////////////////////////////////////////////////////
// CODE FOR The Inventor Mentor STARTS HERE  (Example 10-4)

// Scale each object in the selection list
void
myScaleSelection(SoSelection *selection, float sf)
{
   SoPath *selectedPath;
   SoTransform *xform;
   SbVec3f scaleFactor;
   int i,j;

   // Scale each object in the selection list

   for (i = 0; i < selection->getNumSelected(); i++) {
      selectedPath = selection->getPath(i);
      xform = NULL;

      // Look for the shape node, starting from the tail of the 
      // path.  Once we know the type of shape, we know which 
      // transform to modify
      for (j=0; j < selectedPath->getLength() && 
		(xform == NULL); j++) {
         SoNode *n = (SoNode *)selectedPath->getNodeFromTail(j);
         if (n->isOfType(SoCube::getClassTypeId())) {
            xform = cubeTransform;
         } else if (n->isOfType(SoCone::getClassTypeId())) {
            xform = coneTransform;
         } else if (n->isOfType(SoSphere::getClassTypeId())) {
            xform = sphereTransform;
         } else if (n->isOfType(SoCylinder::getClassTypeId())) {
            xform = cylTransform;
         }
      }

      // Apply the scale
      scaleFactor = xform->scaleFactor.getValue();
      scaleFactor *= sf;
      xform->scaleFactor.setValue(scaleFactor);
   }

}

// CODE FOR The Inventor Mentor ENDS HERE  
//////////////////////////////////////////////////////////////
