/*
*
* 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/
*
*/
#include <stdlib.h>
#include <ctype.h>
#include <X11/Intrinsic.h>
#include <Xm/Form.h>
#include <Xm/LabelG.h>
#include <Xm/PushB.h>
#include <Xm/Scale.h>
#include <Xm/SeparatoG.h>
#include <Xm/Text.h>
#include <Xm/ToggleBG.h>
#include <Inventor/errors/SoReadError.h>
#include <Inventor/fields/SoField.h>
#include <Inventor/fields/SoFieldData.h>
#include <Inventor/nodes/SoNode.h>
#include <Inventor/sensors/SoNodeSensor.h>
#include "Error.h"
#include "FieldEditor.h"
#include "MotifHelp.h"
char *FieldEditor::fieldBuf; // Buffer for writing field values
int FieldEditor::fieldBufSize = 0; // Size of buffer in bytes
////////////////////////////////////////////////////////////////////////
//
// This class is used to hold info about each individual field widget.
// It contains the widget and other information accessible through
// callbacks in one neat package.
//
////////////////////////////////////////////////////////////////////////
struct FieldInfo {
// These are set in initInfo():
FieldEditor *editor; // Pointer to FieldEditor this is part of
SoField *field; // Field being edited
int index; // Index of field in node
SbName fieldName; // Name of field
SbBool setToDefault; // TRUE if user set value to default
SbBool isMultiple; // TRUE if field is multiple value
// These are set in buildFieldWidget():
Widget widget; // Top-level field editing widget
Widget textWidget; // Text editing widget
Widget ignoreButton; // Ignore button widget
Widget defaultButton; // Set-default button widget
// These are used only for multiple-value fields:
Widget numberWidget; // Text widget showing current scale value
Widget scrollWidget; // Text widget scrollbar widget
};
////////////////////////////////////////////////////////////////////////
//
// Description:
// Constructor. Takes pointer to node whose fields we are to edit.
//
FieldEditor::FieldEditor(SoNode *node, Widget parent, const char *name)
: SoXtComponent(parent, name)
//
////////////////////////////////////////////////////////////////////////
{
int i;
nodeToEdit = node;
nodeToEdit->ref();
defNode = (SoNode *) node->getTypeId().createInstance();
defNode->ref();
finishCB = NULL;
finishData = NULL;
errorString = NULL;
// Set up sensor. Make it priority 0 so we can be called
// immediately when a field changes. This way we can update only
// the fields that change in the sensor callback.
nodeSensor = new SoNodeSensor(&FieldEditor::nodeSensorCB, (void *) this);
nodeSensor->setPriority(0);
nodeSensor->attach(node);
// Allocate buffer to write field values into. We have to use
// malloc, since we may use realloc on it later
if (fieldBufSize == 0)
fieldBuf = (char *) malloc((unsigned) (fieldBufSize = 1028));
// Allocate and initialize field info structures
numFields = getNumFields(nodeToEdit);
fieldInfos = new FieldInfo[numFields];
for (i = 0; i < numFields; i++)
initInfo(&fieldInfos[i], i);
// Set up the class name
setClassName("FieldEditor");
// Build and set the widget
setBaseWidget(buildWidget(getParentWidget()));
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Destructor.
//
FieldEditor::~FieldEditor()
//
////////////////////////////////////////////////////////////////////////
{
// Widget stuff is handled in SoXtComponent...
nodeToEdit->unref();
defNode->unref();
delete nodeSensor;
delete [] fieldInfos;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Returns number of fields in node being edited. This is static to
// allow the check to be made before an editor is constructed.
//
int
FieldEditor::getNumFields(SoNode *node)
//
////////////////////////////////////////////////////////////////////////
{
const SoFieldData *fdata = node->getFieldData();
if (fdata == NULL)
return 0;
return fdata->getNumFields();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Sets a callback to call when editing is finished.
//
void
FieldEditor::setFinishCallback(FieldEditorCB *cb, const void *userData)
//
////////////////////////////////////////////////////////////////////////
{
finishCB = cb;
finishData = (void *) userData;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Builds and returns top-level editor widget.
//
Widget
FieldEditor::buildWidget(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
Widget topWidget, fieldForm, sep, buttonForm;
ARG_VARS(20);
// If no fields to edit, go away
if (numFields == 0)
return NULL;
// The title is the class of the node whose fields we are editing
setTitle(nodeToEdit->getTypeId().getName().getString());
// Create the top level form widget and register it with a class name
RESET_ARGS();
topWidget = XmCreateForm(parent, (char *) getWidgetName(), ARGS);
registerWidget(topWidget);
// Create buttons at bottom
buttonForm = buildButtonWidget(topWidget);
RESET_ARGS();
ADD_LEFT_FORM(4);
ADD_RIGHT_FORM(4);
ADD_BOTTOM_FORM(4);
XtSetValues(buttonForm, ARGS);
// Create separator between buttons and field editors
RESET_ARGS();
ADD_ARG(XmNorientation, XmHORIZONTAL);
ADD_LEFT_FORM(0);
ADD_RIGHT_FORM(0);
ADD_BOTTOM_WIDGET(buttonForm, 4);
sep = XmCreateSeparatorGadget(topWidget, "Separator", ARGS);
// Create widget containing all field widgets
fieldForm = buildFieldForm(topWidget);
RESET_ARGS();
ADD_TOP_FORM(4);
ADD_LEFT_FORM(4);
ADD_RIGHT_FORM(4);
ADD_BOTTOM_WIDGET(sep, 4);
XtSetValues(fieldForm, ARGS);
// Manage the children
XtManageChild(fieldForm);
XtManageChild(sep);
XtManageChild(buttonForm);
return topWidget;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Creates and returns a widget containing all field-editing widgets.
//
Widget
FieldEditor::buildFieldForm(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
Widget form, sep;
FieldInfo *info;
int i;
ARG_VARS(20);
// Create form to hold all field widgets
RESET_ARGS();
form = XmCreateForm(parent, "Form", ARGS);
// Create widget for each field and add them to form
for (i = 0, info = fieldInfos; i < numFields; i++, info++) {
// Build separator if necessary
if (i > 0) {
RESET_ARGS();
ADD_ARG(XmNorientation, XmHORIZONTAL);
ADD_LEFT_FORM(0);
ADD_RIGHT_FORM(0);
ADD_TOP_WIDGET((info - 1)->widget, 1);
sep = XmCreateSeparatorGadget(form, "Separator", ARGS);
XtManageChild(sep);
}
// Build field editor widget; sets widget field in info
buildFieldWidget(form, info);
// Set up widget contents from node
updateInfo(info);
RESET_ARGS();
ADD_LEFT_FORM(0);
ADD_RIGHT_FORM(0);
if (i == 0) {
ADD_TOP_FORM(0);
}
else {
ADD_TOP_WIDGET(sep, 0);
}
if (i == numFields - 1) {
ADD_BOTTOM_FORM(0);
}
XtSetValues(info->widget, ARGS);
XtManageChild(info->widget);
}
return form;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Creates a widget for editing the i'th field
//
void
FieldEditor::buildFieldWidget(Widget parent, FieldInfo *info)
//
////////////////////////////////////////////////////////////////////////
{
Widget form, label, sep1, valueWidget, sep2, butForm, ign, def;
ARG_VARS(20);
// Create a form widget
RESET_ARGS();
form = XmCreateForm(parent, "Field", ARGS);
// Create a label widget with the name of the field
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING((char *) info->fieldName.getString()));
ADD_ARG(XmNwidth, 140);
ADD_LEFT_FORM(4);
ADD_TOP_FORM(4);
ADD_BOTTOM_FORM(4);
label = XmCreateLabelGadget(form, "Label", ARGS);
// Create a separator widget
RESET_ARGS();
ADD_ARG(XmNorientation, XmVERTICAL);
ADD_LEFT_WIDGET(label, 4);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
sep1 = XmCreateSeparatorGadget(form, "Separator", ARGS);
// Create a form widget to hold buttons
RESET_ARGS();
ADD_BOTTOM_FORM(4);
ADD_RIGHT_FORM(4);
butForm = XmCreateForm(form, "FieldButtons", ARGS);
// Create a push button to set the field to default value.
// (Disable it if the field is already the default value)
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING("Set To Default"));
ADD_ARG(XmNheight, 28);
ADD_ARG(XmNhighlightThickness, 0);
ADD_RIGHT_FORM(0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
def = XmCreatePushButton(butForm, "defaultButton", ARGS);
ADD_CB(def, XmNactivateCallback, &FieldEditor::defaultButtonCB, info);
// Create a toggle button to set the ignore flag
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING("Ignore"));
ADD_ARG(XmNhighlightThickness, 0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
ADD_LEFT_FORM(0);
ADD_RIGHT_WIDGET(def, 4);
ign = XmCreateToggleButtonGadget(butForm, "ignoreButton", ARGS);
// Create a separator widget
RESET_ARGS();
ADD_ARG(XmNorientation, XmVERTICAL);
ADD_RIGHT_WIDGET(butForm, 4);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
sep2 = XmCreateSeparatorGadget(form, "Separator", ARGS);
// Field value editor widget
if (info->isMultiple)
valueWidget = buildMultipleValueWidget(form, info);
else
valueWidget = buildSingleValueWidget(form, info);
RESET_ARGS();
ADD_LEFT_WIDGET(sep1, 4);
ADD_RIGHT_WIDGET(sep2, 4);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
XtSetValues(valueWidget, ARGS);
// Manage the children
XtManageChild(label);
XtManageChild(sep1);
XtManageChild(valueWidget);
XtManageChild(sep2);
XtManageChild(ign);
XtManageChild(def);
XtManageChild(butForm);
info->widget = form;
info->ignoreButton = ign;
info->defaultButton = def;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Creates and returns a widget that does the main work of editing
// a multiple-value field.
//
Widget
FieldEditor::buildMultipleValueWidget(Widget parent, FieldInfo *info)
//
////////////////////////////////////////////////////////////////////////
{
Widget form, text, number, scroll;
char buf[20];
ARG_VARS(20);
//////////////////////////////////////////////////////////////////
//
// Set up top level form widget
//
RESET_ARGS();
form = XmCreateForm(parent, "Form", ARGS);
//////////////////////////////////////////////////////////////////
//
// Set up the main scrolled text widget
//
RESET_ARGS();
ADD_ARG(XmNeditMode, XmMULTI_LINE_EDIT);
ADD_ARG(XmNcolumns, 30);
ADD_ARG(XmNrows, 5);
ADD_RIGHT_FORM(4);
ADD_TOP_FORM(4);
ADD_BOTTOM_FORM(4);
text = XmCreateScrolledText(form, "text", ARGS);
// Set up a callback to check changes to text before allowing them
ADD_CB(text, XmNmodifyVerifyCallback, &FieldEditor::textChangedCB, info);
// Get the scroll bar resource from the scrolled window (the
// parent widget of the scrolled text)
XtVaGetValues(XtParent(text), XmNverticalScrollBar, &scroll, NULL);
// Set up callbacks on the scroll bar so that the line number
// widget scrolls with the other text. Note that we have to add
// this to all of the scroll bar callbacks since they are already
// set up for the scrolled window.
ADD_CB(scroll, XmNvalueChangedCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNincrementCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNdecrementCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNpageIncrementCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNpageDecrementCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNtoTopCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNtoBottomCallback, &FieldEditor::scrollCB, info);
ADD_CB(scroll, XmNdragCallback, &FieldEditor::scrollCB, info);
//////////////////////////////////////////////////////////////////
//
// Set up the line number text widget
//
RESET_ARGS();
ADD_ARG(XmNeditable, False);
ADD_ARG(XmNeditMode, XmMULTI_LINE_EDIT);
ADD_ARG(XmNvalue, buf);
ADD_ARG(XmNcolumns, 5);
ADD_ARG(XmNrows, 5);
ADD_ARG(XmNhighlightThickness, 0);
ADD_RIGHT_WIDGET(text, 0);
ADD_TOP_FORM(6);
ADD_LEFT_FORM(4);
ADD_BOTTOM_FORM(4);
number = XmCreateText(form, "numbers", ARGS);
XmTextSetString(number, "?"); // Bogus value
// Set up a callback on the line number widget so that users
// cannot scroll the text in there by moving the cursor
ADD_CB(number, XmNmotionVerifyCallback, &FieldEditor::lineCB, info);
XtManageChild(text);
XtManageChild(number);
info->textWidget = text;
info->numberWidget = number;
info->scrollWidget = scroll;
return form;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Creates and returns a widget that does the main work of editing
// a single-value field.
//
Widget
FieldEditor::buildSingleValueWidget(Widget parent, FieldInfo *info)
//
////////////////////////////////////////////////////////////////////////
{
Widget form, text;
ARG_VARS(20);
RESET_ARGS();
form = XmCreateForm(parent, "Form", ARGS);
RESET_ARGS();
ADD_ARG(XmNcolumns, 30);
ADD_ARG(XmNeditMode, XmSINGLE_LINE_EDIT);
ADD_LEFT_FORM(4);
ADD_RIGHT_FORM(4);
ADD_TOP_FORM(4);
ADD_BOTTOM_FORM(4);
text = XmCreateText(form, "Text", ARGS);
ADD_CB(text, XmNmodifyVerifyCallback, &FieldEditor::textChangedCB, info);
XtManageChild(text);
info->textWidget = text;
return form;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Creates and returns a widget containing all buttons.
//
Widget
FieldEditor::buildButtonWidget(Widget parent)
//
////////////////////////////////////////////////////////////////////////
{
Widget form, acceptButton, revertButton, cancelButton;
ARG_VARS(20);
// Create a form widget to hold it all
RESET_ARGS();
form = XmCreateForm(parent, "Field", ARGS);
// Create "Apply", "Accept", "Revert", and "Cancel" buttons
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING("Accept"));
ADD_ARG(XmNhighlightThickness, 0);
ADD_LEFT_FORM(0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
acceptButton = XmCreatePushButton(form, "acceptButton", ARGS);
ADD_CB(acceptButton, XmNactivateCallback,
&FieldEditor::acceptButtonCB, this);
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING("Apply"));
ADD_ARG(XmNhighlightThickness, 0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
ADD_LEFT_WIDGET(acceptButton, 4);
applyButton = XmCreatePushButton(form, "applyButton", ARGS);
ADD_CB(applyButton, XmNactivateCallback,
&FieldEditor::applyButtonCB, this);
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING("Revert"));
ADD_ARG(XmNhighlightThickness, 0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
ADD_LEFT_WIDGET(applyButton, 4);
revertButton = XmCreatePushButton(form, "revertButton", ARGS);
ADD_CB(revertButton, XmNactivateCallback,
&FieldEditor::revertButtonCB, this);
RESET_ARGS();
ADD_ARG(XmNlabelString, STRING("Cancel"));
ADD_ARG(XmNhighlightThickness, 0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
ADD_LEFT_WIDGET(revertButton, 4);
cancelButton = XmCreatePushButton(form, "cancelButton", ARGS);
ADD_CB(cancelButton, XmNactivateCallback,
&FieldEditor::cancelButtonCB, this);
// Create an "Override" toggle button to allow user to change
// override flag for node
RESET_ARGS();
ADD_ARG(XmNset, nodeToEdit->isOverride());
ADD_ARG(XmNlabelString, STRING("Override"));
ADD_ARG(XmNhighlightThickness, 0);
ADD_TOP_FORM(0);
ADD_BOTTOM_FORM(0);
ADD_RIGHT_FORM(0);
overrideButton = XmCreateToggleButtonGadget(form, "overrideButton", ARGS);
XtManageChild(acceptButton);
XtManageChild(applyButton);
XtManageChild(revertButton);
XtManageChild(cancelButton);
XtManageChild(overrideButton);
// Hitting a carriage-return activates the "Apply" button
RESET_ARGS();
ADD_ARG(XmNdefaultButton, applyButton);
XtSetValues(parent, ARGS);
return form;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Applies changes made in editor to node it is editing.
//
void
FieldEditor::apply()
//
////////////////////////////////////////////////////////////////////////
{
FieldInfo *info;
int i;
SbBool override;
// Turn off sensor for a minute...
nodeSensor->detach();
// Get all field values and flags and store them back into the node
for (i = 0, info = fieldInfos; i < numFields; i++, info++)
updateNode(info);
// Set the override flag if it has changed
override = XmToggleButtonGadgetGetState(overrideButton);
if (nodeToEdit->isOverride() != override)
nodeToEdit->setOverride(override);
// Make sure the text window reflects the exact state
revert();
// Reattach sensor
nodeSensor->attach(nodeToEdit);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Sets field editor based on the values in the node being edited.
// If the passed field index is not -1 (the default), only the
// value for the specified field is reverted.
//
void
FieldEditor::revert(int fieldIndex)
//
////////////////////////////////////////////////////////////////////////
{
FieldInfo *info;
int i;
if (fieldIndex >= 0)
updateInfo(&fieldInfos[fieldIndex]);
// Update all field info from node
else
for (i = 0, info = fieldInfos; i < numFields; i++, info++)
updateInfo(info);
// Set the override button
XmToggleButtonGadgetSetState(overrideButton,
nodeToEdit->isOverride(), FALSE);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Initializes given FieldInfo structure based on indexed field.
//
void
FieldEditor::initInfo(FieldInfo *info, int i)
//
////////////////////////////////////////////////////////////////////////
{
info->editor = this;
info->index = i;
info->setToDefault = FALSE;
// Set the field name and pointer and determine whether it is a
// single or multiple value field.
const SoFieldData *fdata = nodeToEdit->getFieldData();
info->field = fdata->getField(nodeToEdit, i);
info->fieldName = fdata->getFieldName(i);
info->isMultiple = info->field->isOfType(SoMField::getClassTypeId());
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Sets up field editor from correct field in node being edited.
//
void
FieldEditor::updateInfo(FieldInfo *info)
//
////////////////////////////////////////////////////////////////////////
{
SbString fieldString;
ARG_VARS(10);
// Set text field from current field value(s)
showFieldString(info);
// Set ignore button state
RESET_ARGS();
ADD_ARG(XmNset, info->field->isIgnored());
XtSetValues(info->ignoreButton, ARGS);
// Turn set-default button on or off
info->setToDefault = info->field->isDefault();
RESET_ARGS();
ADD_ARG(XmNsensitive, ! info->setToDefault);
XtSetValues(info->defaultButton, ARGS);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Makes field editor show default value for given field. Does NOT
// change the node at all!
//
void
FieldEditor::showDefaultValue(FieldInfo *info)
//
////////////////////////////////////////////////////////////////////////
{
SoField *defField, *saveField;
ARG_VARS(10);
// Make sure we know that this was done
info->setToDefault = TRUE;
// Get the appropriate field from a node instance with all default values
defField = defNode->getFieldData()->getField(defNode, info->index);
// Temporarily change the field to the default one
saveField = info->field;
info->field = defField;
// Set the string in the text widget
showFieldString(info);
// Restore the real field value
info->field = saveField;
// Disable the default button
RESET_ARGS();
ADD_ARG(XmNsensitive, FALSE);
XtSetValues(info->defaultButton, ARGS);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Sets string(s) in text widget to display field value(s).
//
void
FieldEditor::showFieldString(FieldInfo *info) const
//
////////////////////////////////////////////////////////////////////////
{
SbString valueString;
REM_CB(info->textWidget, XmNmodifyVerifyCallback,
&FieldEditor::textChangedCB, info);
// Get the field value(s) as a string
getFieldString(info, valueString);
// Set the text widget to show the string
XmTextSetString(info->textWidget, (char *) valueString.getString());
// Set up the line number widget for multiple-value fields
if (info->isMultiple)
updateLineNumbers(info, TRUE);
// Reconnect the callback
ADD_CB(info->textWidget, XmNmodifyVerifyCallback,
&FieldEditor::textChangedCB, info);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Updates field value and flags in node from current editor settings.
//
void
FieldEditor::updateNode(FieldInfo *info)
//
////////////////////////////////////////////////////////////////////////
{
SbString string;
SbBool ignore;
getEditorString(info, string);
// Set the read-error callback so that read errors caused by the
// following call to SoField::set() can be trapped and displayed
// in an error dialog box
SoReadError::setHandlerCallback(&FieldEditor::readErrorCB, info);
if (! nodeToEdit->set(string.getString()))
displayReadError();
// If value was set to default, set flag in the field
if (info->setToDefault)
info->field->setDefault(TRUE);
// Make sure ignore flag is correct
ignore = XmToggleButtonGadgetGetState(info->ignoreButton);
if (info->field->isIgnored() != ignore)
info->field->setIgnored(ignore);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Fills in SbString with string representing field value that can
// be displayed in editor's text widget.
//
void
FieldEditor::getFieldString(FieldInfo *info, SbString &string) const
//
////////////////////////////////////////////////////////////////////////
{
// Get the field value(s) as a string
info->field->get(string);
// If it's a multiple-value string, we have to some extra work
if (info->isMultiple) {
const char *s = string.getString();
// If first character is an open bracket, we have work to do
if (*s == '[') {
// Skip over the open brace
s++;
// Allocate working space for the string
char *buf = new char [strlen(s) + 1];
char *b = buf;
SbBool atNewline = TRUE;
while (*s != '\0') {
// Skip over quoted strings - this will have a problem
// with quotes nested inside quotes ???
if (*s == '\"') {
*b++ = *s++;
while (*s != '\"')
*b++ = *s++;
*b++ = *s++; // Closing quote
}
// Change commas into newlines
if (*s == ',') {
*b++ = '\n';
atNewline = TRUE;
s++;
}
// Skip over spaces (including extra newlines) after newlines
else if (atNewline && isspace(*s))
s++;
else {
*b++ = *s++;
atNewline = FALSE;
}
}
// Remove closing brace and space before it
while (*b != ']')
b--;
while (isspace(*(b - 1)))
b--;
// Terminate string
*b = '\0';
string = buf;
delete [] buf;
}
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Fills in SbString with string containing current field value in
// editor's text widget, to store back into node being edited.
//
void
FieldEditor::getEditorString(FieldInfo *info, SbString &string) const
//
////////////////////////////////////////////////////////////////////////
{
char *str = XmTextGetString(info->textWidget);
// Store name of string and a space
string = info->fieldName.getString();
string += " ";
// If a single-value field, just append text string from widget
if (! info->isMultiple)
string += str;
// If this is a multiple-value field, add extra characters
else {
char *c;
string += "[";
for (c = str; *c != '\0'; c++) {
// Change all newlines to commas in string first
if (*c == '\n') {
*c = ',';
// Remove all whitespace after commas. This also gets
// rid of blank lines.
c++;
while (isspace(*c))
c++;
c--;
}
}
string += str;
string += "]";
}
XtFree(str);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Redefines this method to clean up when the window is destroyed.
//
void
FieldEditor::windowCloseAction()
//
////////////////////////////////////////////////////////////////////////
{
// Alert caller if necessary
if (finishCB != NULL)
finishCB(finishData, this);
delete this;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Updates the line-number widget whenever the text area changes or
// is scrolled. The textChanged flag indicates why the line number
// may be changing.
//
void
FieldEditor::updateLineNumbers(FieldInfo *info, SbBool textChanged) const
//
////////////////////////////////////////////////////////////////////////
{
if (textChanged) {
// See if the number of lines in the text area has changed. If
// so, insert or remove line numbers from the widget.
// Count newlines in the text string
char *textString = XmTextGetString(info->textWidget);
int numLines = 0;
for (char *b = textString; *b != '\0'; b++)
if (*b == '\n')
numLines++;
// Last line may not end in a newline?
if (b > textString && *(b-1) != '\n')
numLines++;
XtFree(textString);
// Make sure line number widget has the correct info
char *numberString = XmTextGetString(info->numberWidget);
int numNumbers = strlen(numberString) / 6;
XtFree(numberString);
// Too many line numbers? Remove some.
if (numNumbers > numLines)
XmTextReplace(info->numberWidget,
numLines * 6, numNumbers * 6, NULL);
// Too few line numbers? Add some.
else if (numNumbers < numLines) {
int numNeeded = numLines - numNumbers, i;
char *buf = new char [numNeeded * 6 + 1], *b = buf;
for (i = 0; i < numNeeded; i++) {
sprintf(b, "%5d\n", numNumbers + i);
b += strlen(b);
}
if (numNumbers == 0)
XmTextReplace(info->numberWidget, 0, 1, buf);
else
XmTextInsert(info->numberWidget, numLines * 6, buf);
delete [] buf;
}
}
// Find out the scroll bar value and set the line number position
// based on it
ARG_VARS(1);
int topLine;
RESET_ARGS();
ADD_ARG(XmNvalue, &topLine);
XtGetValues(info->scrollWidget, ARGS);
RESET_ARGS();
ADD_ARG(XmNtopCharacter, topLine * 6);
XtSetValues(info->numberWidget, ARGS);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the node we are editing changes. The data
// pointer points to a FieldEditor instance.
//
void
FieldEditor::nodeSensorCB(void *data, SoSensor *sensor)
//
////////////////////////////////////////////////////////////////////////
{
// Try to update only the field that changed, if there is one
FieldEditor *editor = (FieldEditor *) data;
SoNodeSensor *nodeSensor = (SoNodeSensor *) sensor;
const SoField *trigField = nodeSensor->getTriggerField();
int fieldIndex = -1;
if (trigField != NULL) {
int i;
for (i = 0; i < editor->numFields; i++) {
if (editor->fieldInfos[i].field == trigField) {
fieldIndex = i;
break;
}
}
}
editor->revert(fieldIndex);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the "Accept" button in the editor is
// activated. The clientData pointer points to a FieldEditor instance.
//
void
FieldEditor::acceptButtonCB(Widget, XtPointer clientData, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
FieldEditor *editor = (FieldEditor *) clientData;
// Make sure latest changes were applied
editor->apply();
// Get rid of window
editor->windowCloseAction();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the "Apply" button in the editor is
// activated. The clientData pointer points to a FieldEditor instance.
//
void
FieldEditor::applyButtonCB(Widget, XtPointer clientData, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
((FieldEditor *) clientData)->apply();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the "Revert" button in the editor is
// activated. The clientData pointer points to a FieldEditor instance.
//
void
FieldEditor::revertButtonCB(Widget, XtPointer clientData, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
((FieldEditor *) clientData)->revert();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the "Cancel" button in the editor is
// activated. The clientData pointer points to a FieldEditor instance.
//
void
FieldEditor::cancelButtonCB(Widget, XtPointer clientData, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
FieldEditor *editor = (FieldEditor *) clientData;
// Get rid of window
editor->windowCloseAction();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the "Set Default" button in a field editor
// is activated. The clientData pointer points to a FieldInfo
// instance for the field to change.
//
void
FieldEditor::defaultButtonCB(Widget, XtPointer clientData, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
FieldInfo *info = (FieldInfo *) clientData;
// Make sure the correct value is displayed in the text widget
info->editor->showDefaultValue(info);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the text in a field editor text widget
// is changed. The clientData pointer points to a FieldInfo
// instance.
//
void
FieldEditor::textChangedCB(Widget, XtPointer clientData, XtPointer callData)
//
////////////////////////////////////////////////////////////////////////
{
FieldInfo *info = (FieldInfo *) clientData;
XmTextVerifyPtr tvp = (XmTextVerifyPtr) callData;
// If we are inserting a text character, see if it is a carriage return
if (tvp->text->length > 0 && *tvp->text->ptr == '\n') {
// We don't want the text to be inserted if it's a
// single-value field - just act as if we hit the "Apply"
// button
if (! info->isMultiple) {
tvp->doit = FALSE;
XtCallActionProc(info->editor->applyButton, "ArmAndActivate",
tvp->event, NULL, 0);
}
}
// Multiple-value fields need to scroll correctly when the text
// changes
if (info->isMultiple)
info->editor->updateLineNumbers(info, TRUE);
// Reset the setToDefault flag to FALSE if necessary
if (info->setToDefault && tvp->doit) {
XtSetSensitive(info->defaultButton, TRUE);
info->setToDefault = FALSE;
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback called when the value of the scrollbar in a
// multiple-value field text scrolling area changes. The clientData
// pointer points to a FieldInfo instance.
//
void
FieldEditor::scrollCB(Widget, XtPointer clientData, XtPointer)
//
////////////////////////////////////////////////////////////////////////
{
FieldInfo *info = (FieldInfo *) clientData;
// Determine the new top line number from the scroll bar
info->editor->updateLineNumbers(info, FALSE);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Callback used to keep the user from moving the insertion cursor
// within the line-number widget. The clientData pointer points to
// a FieldInfo instance.
//
void
FieldEditor::lineCB(Widget, XtPointer, XtPointer callData)
//
////////////////////////////////////////////////////////////////////////
{
XmTextVerifyCallbackStruct *cbInfo =
(XmTextVerifyCallbackStruct *) callData;
// Indicate that no cursor motion should occur
cbInfo->doit = False;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Reallocates fieldBuf and sets fieldBufSize.
//
void *
FieldEditor::reallocBuf(void *ptr, size_t newSize)
//
////////////////////////////////////////////////////////////////////////
{
fieldBufSize = newSize;
fieldBuf = (char *) realloc(ptr, newSize);
return fieldBuf;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Handler for read errors that occur when setting values in the
// field. Saves the error message to be displayed later.
//
void
FieldEditor::readErrorCB(const SoError *error, void *data)
//
////////////////////////////////////////////////////////////////////////
{
FieldInfo *info = (FieldInfo *) data;
// Save only the first error that is found
if (info->editor->errorString == NULL) {
SbString *err = new SbString;
(*err) = "There was an error reading the values for the field named ";
(*err) += info->fieldName.getString();
(*err) += ":\n\n";
(*err) += error->getDebugString();
info->editor->errorString = err;
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Displays error message detected during reading.
//
void
FieldEditor::displayReadError()
//
////////////////////////////////////////////////////////////////////////
{
if (errorString != NULL) {
Error *err = new Error(XtParent(getWidget()),
errorString->getString());
delete errorString;
errorString = NULL;
}
}