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

//
// Routines for handling the text scene graph
//
// The scene graph consists of a Profile2, Font, and Translation node,
// and then a series of separators, each of which represents a
// paragraph.  The paragraphs consist of a Separator, under which
// there is a Translation, Material, and a Text3 node (underneath
// another separator which is used to cache the polygons).
//
// The whole thing is underneath a root Separator.
//

#include <cstdio>
#include <cstring>
#include <cmath>

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtClipboard.h>
#include <Inventor/Xt/SoXtMaterialEditor.h>
#include <Inventor/SoPath.h>
#include <Inventor/nodes/SoFont.h>
#include <Inventor/nodes/SoLinearProfile.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoMaterialBinding.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoProfileCoordinate2.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoText3.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/sensors/SoAlarmSensor.h>
#include <Inventor/sensors/SoSensor.h>

#include <Xm/Xm.h>
#include <X11/Shell.h>
#include <Xm/Text.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>
#include <Xm/ToggleBG.h>
#include <Xm/LabelG.h>

#include "TextGraph.h"
#include "TextWrapper.h"
#include "labels.h"

//
// Global variables.  Evil, but I'm in a hurry.
//
SoProfileCoordinate2 *profileCoords;
SoLinearProfile *profile;

// Global for resources...
extern Labels labels;


//
// Variables local to this file.  Also evil (not as evil as globals),
// but I'm not going to bother to encapsulate all this into a class,
// since I don't really plan on reusing it.
//

static SoSeparator *root;

struct TextThing {
    SoSeparator *top;
    SoTranslation *translation;
    SoSeparator *cache;
    SoText3 *text;
};
    
static TextThing *paragraphs;
static SoFont *TheFont;
static SoTranslation *TheTranslation;
static SoMaterial *TheMaterial;
static int numParagraphs = 0;
static const float spacing = 1.0;	// Space between lines
static const float paraSpacing = 0.5;	// Extra space between paragraphs

// Which parts material editor is editing
static int whichMatEditing = SoText3::ALL;
// Which parts (front/sides/back) of the text are visible
static int whichPartsOn = SoText3::FRONT;

//
// These two callbacks are called when the text in the Motif text
// widget changes.  delayUpdateText is called while typing is
// happening; it, in turn, just schedules the delayCB to be called one
// second later.  While typing, that sensor will be constantly
// rescheduled, so delayCB is not called until one second after typing
// has ceased.  This is a pretty good compromise between the ideal
// immediate feedback and some sort of explicit gesture (like an OK
// button) that the user would have to use.
//
void
delayCB(void *textwidget, SoSensor *)
{
    updateText(NULL, (XtPointer)textwidget, NULL);
}
void
delayUpdateText(Widget, XtPointer textwidget, XtPointer)
{
    static SoAlarmSensor *alarmSensor = NULL;
    if (alarmSensor == NULL) {
	alarmSensor = new SoAlarmSensor;
	alarmSensor->setFunction(delayCB);
    }
    else {
	alarmSensor->unschedule();
    }
    alarmSensor->setData(textwidget);
    alarmSensor->setTimeFromNow(SbTime(1.0));	// Wait a second...
    alarmSensor->schedule();
}
//
// Actually change the scene graph based on the text entered into the
// Motif text widget
//
void
updateText(Widget, XtPointer textwidget, XtPointer)
{
    char *str = XmTextGetString((Widget)textwidget);
    //
    // The TextWrapper word-wraps the text string.  Here, we tell it
    // to format the string for 30 character lines.
    //
    TextWrapper twrap(str, 30);
    XtFree(str);

    //
    // Next, get rid of old text in scene graph and add the new text
    // (with appropriate translation to move the text apart)
    //
    int i;
    for (i = numParagraphs-1; i >= 0; i--) {
	root->removeChild(paragraphs[i].top);
    }
    if (numParagraphs != 0) {
	delete[] paragraphs;
    }

    numParagraphs = twrap.numParagraphs();

    if (numParagraphs != 0) {
	paragraphs = new TextThing[numParagraphs];
    }
    float currentY = 0.0;
    for (i = 0; i < numParagraphs; i++) {
	SoSeparator *top = new SoSeparator;
	paragraphs[i].top = top;
	root->addChild(top);
	
	paragraphs[i].translation = new SoTranslation;
	top->addChild(paragraphs[i].translation);
	paragraphs[i].translation->
	    translation.setValue(0.0, currentY, 0.0);

	paragraphs[i].cache = new SoSeparator;
	top->addChild(paragraphs[i].cache);
        paragraphs[i].cache->renderCaching = SoSeparator::ON;
        paragraphs[i].cache->boundingBoxCaching = SoSeparator::ON;

	SoText3 *tnode = new SoText3;
	paragraphs[i].text = tnode;
	paragraphs[i].cache->addChild(tnode);
	tnode->justification = SoText3::CENTER;
	tnode->parts = whichPartsOn;

	for (int j = 0; j < twrap.numLines(i); j++) {
	    tnode->string.set1Value(j, twrap.getLine(i, j));
	    currentY -= spacing;
	}
	currentY -= paraSpacing;
    }
    //
    // This centers the entire text vertically
    //
    float total_d = (-currentY - paraSpacing - spacing);
    TheTranslation->translation.setValue(0.0, total_d/2.0, 0.0);
}

//
// Copy the text scene graph to the clipboard
//
void
copyText(Widget, XtPointer w, XtPointer cbstruct)
{
    static SoXtClipboard *theClipboard = NULL;

    if (theClipboard == NULL) {
	theClipboard = new SoXtClipboard((Widget)w);
    }
    theClipboard->copy(root,
	((XmAnyCallbackStruct *)cbstruct)->event->xbutton.time);
}

//
// Turn the given part on or off.
//
static void
togglePart(Widget, XtPointer mydata, XtPointer cbstruct)
{
    int flag = ((XmToggleButtonCallbackStruct *)cbstruct)->set;
    int whichPart = (long)mydata;

    if (flag) {	// Turn part on
	whichPartsOn |= whichPart;
    }
    else {
	whichPartsOn &= ~whichPart;
    }
    for (int i = 0; i < numParagraphs; i++) {
	paragraphs[i].text->parts.setValue(whichPartsOn);
    }
}

//
// And cause the material editor to edit/not edit the given part.
//
static void
editToggle(Widget, XtPointer mydata, XtPointer cbstruct)
{
    int flag = ((XmToggleButtonCallbackStruct *)cbstruct)->set;
    int whichPart = (long)mydata;

    if (flag) {	// Edit the part
	whichMatEditing |= whichPart;
    }
    else {	// Don't edit it
	whichMatEditing &= (~whichPart);
    }
}

//
// The material editor calls this function whenever its material
// changes.  We have to copy its material into the right place in the
// scene graph.
//
static void
matEditCB(void *, const SoMaterial *mtl)
{
    if (whichMatEditing & SoText3::FRONT) {
	TheMaterial->ambientColor.set1Value(0, mtl->ambientColor[0]);
	TheMaterial->diffuseColor.set1Value(0, mtl->diffuseColor[0]);
	TheMaterial->specularColor.set1Value(0, mtl->specularColor[0]);
	TheMaterial->emissiveColor.set1Value(0, mtl->emissiveColor[0]);
	TheMaterial->shininess.set1Value(0, mtl->shininess[0]);
	TheMaterial->transparency.set1Value(0, mtl->transparency[0]);
    }
    if (whichMatEditing & SoText3::SIDES) {
	TheMaterial->ambientColor.set1Value(1, mtl->ambientColor[0]);
	TheMaterial->diffuseColor.set1Value(1, mtl->diffuseColor[0]);
	TheMaterial->specularColor.set1Value(1, mtl->specularColor[0]);
	TheMaterial->emissiveColor.set1Value(1, mtl->emissiveColor[0]);
	TheMaterial->shininess.set1Value(1, mtl->shininess[0]);
	TheMaterial->transparency.set1Value(1, mtl->transparency[0]);
    }
    if (whichMatEditing & SoText3::BACK) {
	TheMaterial->ambientColor.set1Value(2, mtl->ambientColor[0]);
	TheMaterial->diffuseColor.set1Value(2, mtl->diffuseColor[0]);
	TheMaterial->specularColor.set1Value(2, mtl->specularColor[0]);
	TheMaterial->emissiveColor.set1Value(2, mtl->emissiveColor[0]);
	TheMaterial->shininess.set1Value(2, mtl->shininess[0]);
	TheMaterial->transparency.set1Value(2, mtl->transparency[0]);
    }
}

//
// This is called when the user presses the 'Edit Parts' button.  It
// creates the parts editor (if necessary) and then manages it.
//
void
createPartEditor(Widget w, XtPointer, XtPointer)
{
    static Widget shell = NULL;

    if (shell == NULL) {
	// Create 3 labels and 6 togglebuttons, and one material
	// editor
	Arg resources[20];
	int n;
	
	// change the XmNdeleteResponse (when use closes the window) from
	// XmDESTROY to XmUNMAP since we don't want to delete the material
	// editor widgets (we would also have to delete the material editor
	// class since we can rebuild them independently). We could use
	// XmCreateFormDialog() but some some strange reason the material
	// editor has problems building inside one of them.
	n = 0;
	XtSetArg(resources[n], XmNdeleteResponse, XmUNMAP); ++n;
	shell = XtCreatePopupShell("editParts", topLevelShellWidgetClass, 
	    SoXt::getShellWidget(w), resources, n);
	
	Widget form = XmCreateForm(shell, NULL, NULL, 0);

	SoXtMaterialEditor *medit = new SoXtMaterialEditor(form);
	medit->addMaterialChangedCallback(matEditCB, NULL);
	XtSetArg(resources[n], XmNtopAttachment, XmATTACH_FORM); ++n;
	XtSetArg(resources[n], XmNleftAttachment, XmATTACH_FORM); ++n;
	XtSetArg(resources[n], XmNrightAttachment, XmATTACH_FORM); ++n;
	XtSetValues(medit->getWidget(), resources, n);

	// Create three columns (row/column widgets) containing 3
	// buttons each, for a total of 12 widgets/gadgets.
	Widget w[12]; // Used for XtManageChildren() call
	
	n = 0;
	XtSetArg(resources[n], XmNnumColumns, 1); ++n;
	XtSetArg(resources[n], XmNorientation, XmVERTICAL); ++n;
	XtSetArg(resources[n], XmNpacking, XmPACK_COLUMN); ++n;
	XtSetArg(resources[n], XmNisAligned, TRUE); ++n;
	XtSetArg(resources[n], XmNentryAlignment, XmALIGNMENT_END); ++n;
	XtSetArg(resources[n], XmNadjustLast, FALSE); ++n;
	XtSetArg(resources[n], XmNtopAttachment, XmATTACH_WIDGET); ++n;
	XtSetArg(resources[n], XmNtopWidget, medit->getWidget()); ++n;
	XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_FORM); ++n;

	// All the previous resources are the same for the three
	// column widgets...
	int nn = n;
	XtSetArg(resources[n], XmNleftAttachment, XmATTACH_FORM); ++n;
	XtSetArg(resources[n], XmNrightAttachment, XmATTACH_POSITION); ++n;
	XtSetArg(resources[n], XmNrightPosition, 32); ++n;
	w[0] = XmCreateRowColumn(form, "PartsLabels", resources, n);

	n = nn;
	XtSetArg(resources[n], XmNleftAttachment, XmATTACH_POSITION); ++n;
	XtSetArg(resources[n], XmNleftPosition, 34); ++n;
	XtSetArg(resources[n], XmNrightAttachment, XmATTACH_POSITION); ++n;
	XtSetArg(resources[n], XmNrightPosition, 66); ++n;
	w[1] = XmCreateRowColumn(form, "PartsOnOff", resources, n);

	n = nn;
	XtSetArg(resources[n], XmNleftAttachment, XmATTACH_POSITION); ++n;
	XtSetArg(resources[n], XmNleftPosition, 67); ++n;
	XtSetArg(resources[n], XmNrightAttachment, XmATTACH_FORM); ++n;
	w[2] = XmCreateRowColumn(form, "PartsEdit", resources, n);
	n = 0;

#define STRING(a) XmStringCreateLocalized(a)

	XtSetArg(resources[n], XmNlabelString, STRING(labels.front)); ++n;
	w[3] = XmCreateLabelGadget(w[0], "frontLabel", resources, n); n = 0;
	XtSetArg(resources[n], XmNlabelString, STRING(labels.sides)); ++n;
	w[4] = XmCreateLabelGadget(w[0], "sidesLabel", resources, n); n = 0;
	XtSetArg(resources[n], XmNlabelString, STRING(labels.back)); ++n;
	w[5] = XmCreateLabelGadget(w[0], "backLabel",  resources, n); n = 0;

	// The three parts toggles
	XtSetArg(resources[n], XmNlabelString, STRING(labels.on)); ++n;
	XtSetArg(resources[n], XmNset, 1); ++n;
	w[6] = XmCreateToggleButtonGadget(w[1], "frontOnOff", resources, n);
	XtAddCallback(w[6], XmNvalueChangedCallback,
		      togglePart, (XtPointer)SoText3::FRONT);
	n = 0;
	
	XtSetArg(resources[n], XmNlabelString, STRING(labels.on)); ++n;
	if (whichPartsOn & SoText3::SIDES) {
	    XtSetArg(resources[n], XmNset, 1); ++n;
	} else {
	    XtSetArg(resources[n], XmNset, 0); ++n;
	}
	w[7] = XmCreateToggleButtonGadget(w[1], "sidesOnOff", resources, n);
	XtAddCallback(w[7], XmNvalueChangedCallback,
		      togglePart, (XtPointer)SoText3::SIDES);
	n = 0;
	
	XtSetArg(resources[n], XmNlabelString, STRING(labels.on)); ++n;
	if (whichPartsOn & SoText3::BACK) {
	    XtSetArg(resources[n], XmNset, 1); ++n;
	} else {
	    XtSetArg(resources[n], XmNset, 0); ++n;
	}
	w[8] = XmCreateToggleButtonGadget(w[1], "backOnOff", resources, n);
	XtAddCallback(w[8], XmNvalueChangedCallback,
		      togglePart, (XtPointer)SoText3::BACK);
	n = 0;

	// And the three material toggles
	XtSetArg(resources[n], XmNlabelString, STRING(labels.edit)); ++n;
	XtSetArg(resources[n], XmNset, 1); ++n;
	w[9] = XmCreateToggleButtonGadget(w[2], "frontEdit", resources, n);
	XtAddCallback(w[9], XmNvalueChangedCallback,
		      editToggle, (XtPointer)SoText3::FRONT);
	
	w[10] = XmCreateToggleButtonGadget(w[2], "sidesEdit", resources, n);
	XtAddCallback(w[10], XmNvalueChangedCallback,
		      editToggle, (XtPointer)SoText3::SIDES);
		      
	w[11] = XmCreateToggleButtonGadget(w[2], "backEdit",  resources, n);
	XtAddCallback(w[11], XmNvalueChangedCallback,
		      editToggle, (XtPointer)SoText3::BACK);
	
	XtManageChildren(w+3, 3); // labels/buttons
	XtManageChildren(w+6, 3);
	XtManageChildren(w+9, 3);
	medit->show();
	XtManageChildren(w, 3);	// row-columns..
	XtManageChild(form);
    }
    SoXt::show(shell);
}

//
// This function is called when the user goes into or out of the
// profile editor window.  I could give the line manipulator
// start/edit/finish callbacks, and have those start and finish
// callbacks turn off/on render caching, but I think it is neat to be
// able to demonstrate the change in speed of caching vs. no caching
// by moving the mouse between windows while the text spins.
//
void
setTextCaching(int flag)
{
    for (int i = 0; i < numParagraphs; i++) {
	if (flag) {
            paragraphs[i].cache->renderCaching = SoSeparator::ON;
	}
	else {        
            paragraphs[i].cache->renderCaching = SoSeparator::OFF;
        }
    }
}

//
// Create the main scene graph (the scene graph viewed by the
// Examiner).
//
SoSeparator *
createTextSceneGraph()
{
    SoSeparator *veryTop = new SoSeparator;
    veryTop->ref();

    SoPerspectiveCamera *camera = new SoPerspectiveCamera;
    veryTop->addChild(camera);
    camera->heightAngle = 28.0 * M_PI/180.0;
    camera->aspectRatio = 1.0;
    camera->position.setValue(0.0, 0.0, 25.0);
    camera->nearDistance = 8.0;
    camera->farDistance = 50.0;
    camera->focalDistance = 25.0;

    root = new SoSeparator;
    veryTop->addChild(root);

    TheFont = new SoFont;
    TheFont->name = labels.sofontlist;
    TheFont->size = 1;
    root->addChild(TheFont);

    TheMaterial = new SoMaterial;
    root->addChild(TheMaterial);
    SoMaterial *defaultMaterial = new SoMaterial;
    defaultMaterial->ref();
    // Make all three materials (for front/back/sides) the same:
    matEditCB(NULL, defaultMaterial);
    defaultMaterial->unref();

    SoMaterialBinding *mb = new SoMaterialBinding;
    root->addChild(mb);
    mb->value = SoMaterialBinding::PER_PART;

    TheTranslation = new SoTranslation;
    root->addChild(TheTranslation);

    profileCoords = new SoProfileCoordinate2;
    root->addChild(profileCoords);

    profile = new SoLinearProfile;
    root->addChild(profile);

    return veryTop;
}

