/*
*
* Copyright (C) 2000 Silicon Graphics, Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
* Mountain View, CA 94043, or:
*
* http://www.sgi.com
*
* For further information regarding this notice, see:
*
* http://oss.sgi.com/projects/GenInfo/NoticeExplan/
*
*/
/*
* Copyright (C) 1990-93 Silicon Graphics, Inc.
*
_______________________________________________________________________
______________ S I L I C O N G R A P H I C S I N C . ____________
|
| $Revision: 1.1.1.1 $
|
| Classes:
| SoSelection
|
| Authors: David Mott, Paul Isaacs, Thaddeus Beier
|
______________ S I L I C O N G R A P H I C S I N C . ____________
_______________________________________________________________________
*/
#include <X11/Xlib.h>
#include <Inventor/SoDB.h>
#include <Inventor/SoInput.h>
#include <Inventor/SoInteraction.h>
#include <Inventor/SoLists.h>
#include <Inventor/SoOutput.h>
#include <Inventor/SoPickedPoint.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoHandleEventAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/actions/SoWriteAction.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/events/SoEvent.h>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/fields/SoFieldData.h>
#include <Inventor/misc/SoCallbackList.h>
#include <Inventor/nodes/SoSelection.h>
#include <Inventor/nodes/SoSubNode.h>
SO_NODE_SOURCE(SoSelection);
SoSearchAction *SoSelection::searchAction = NULL;
////////////////////////////////////////////////////////////////////////
//
// Description:
// Constructor initialization
//
// Use: private
//
void
SoSelection::constructorCommon()
//
////////////////////////////////////////////////////////////////////////
{
SO_NODE_CONSTRUCTOR(SoSelection);
SO_NODE_ADD_FIELD(policy, (SoSelection::SHIFT));
// Set up static info for enumerated type field
SO_NODE_DEFINE_ENUM_VALUE(Policy, SINGLE);
SO_NODE_DEFINE_ENUM_VALUE(Policy, TOGGLE);
SO_NODE_DEFINE_ENUM_VALUE(Policy, SHIFT);
// Set up info in enumerated type field
SO_NODE_SET_SF_ENUM_TYPE(policy, Policy);
isBuiltIn = TRUE;
// selection callback functions and user data
selCBList = NULL;
deselCBList = NULL;
startCBList = NULL;
finishCBList = NULL;
changeCBList = NULL;
pickCBFunc = NULL;
pickCBData = NULL;
mouseDownPickPath = NULL;
pickMatching = TRUE;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Constructors
//
// Use: public
//
SoSelection::SoSelection()
{
constructorCommon();
}
SoSelection::SoSelection(int numChildren)
: SoSeparator(numChildren)
{
constructorCommon();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Destructor.
//
// Use: protected
SoSelection::~SoSelection()
//
////////////////////////////////////////////////////////////////////////
{
delete selCBList;
delete deselCBList;
delete startCBList;
delete finishCBList;
delete changeCBList;
if (mouseDownPickPath != NULL)
mouseDownPickPath->unref();
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Traversal for event handling. The parent class SoSeparator is called
// to traverse the children. If the event is not handled by any children,
// then handle it here by invoking the current selection policy.
// Calls action->setHandled() if the event was handled.
//
// Use: protected
void
SoSelection::handleEvent(SoHandleEventAction *action)
//
////////////////////////////////////////////////////////////////////////
{
// let SoSeparator traverse the children
SoSeparator::handleEvent(action);
// On Button1 press, see who got picked.
// On Button1 release, see who got picked.
// If pick-matching is on, and they match,
// then select the object that got picked.
//??? this event should be programmer configurable! translation tables?
const SoEvent *event = action->getEvent();
if (! event->isOfType(SoMouseButtonEvent::getClassTypeId()))
return;
const SoMouseButtonEvent *be = (const SoMouseButtonEvent *) event;
if (be->getButton() != SoMouseButtonEvent::BUTTON1)
return;
if ((pickMatching && (be->getState() == SoButtonEvent::DOWN)) ||
(be->getState() == SoButtonEvent::UP)) {
//??? change the action so we only pick the FIRST thing, not a list?
const SoPickedPoint *pickedPoint = action->getPickedPoint();
SoPath *pickPath = NULL;
// Get the pick path
if (pickedPoint != NULL) {
// If the pick callback exists, let it tell us what path to pick
if (pickCBFunc != NULL) {
if (callPickCBOnlyIfSelectable) {
// Make sure pick passes through this
if (pickedPoint->getPath()->containsNode(this))
pickPath = (*pickCBFunc)(pickCBData, pickedPoint);
}
else pickPath = (*pickCBFunc)(pickCBData, pickedPoint);
}
// Else no pick CB - use the picked path as is.
else pickPath = pickedPoint->getPath();
}
// For button press, save the pick path whether a child
// handled the event or not - we may get a crack at the
// button release event later.
if (be->getState() == SoButtonEvent::DOWN) {
if (mouseDownPickPath != NULL)
mouseDownPickPath->unref(); // out with the old...
mouseDownPickPath = copyFromThis(pickPath);
if (mouseDownPickPath != NULL) {
mouseDownPickPath->ref(); // ...in with the new
action->setHandled();
}
}
// Else button release.
// If no children handled the event during SoSeparator::handleEvent()
// traversal, handle it here.
else if (! action->isHandled()) {
// For button release, get the pick path and see if it matches
// the button press pick path.
//
// If they match, invoke the selection policy.
// If they do NOT match, do nothing.
if (pickPath == NULL) {
// If nothing was picked, pass NULL to invokeSelectionPolicy.
if ((! pickMatching) || (mouseDownPickPath == NULL)) {
invokeSelectionPolicy(NULL, be->wasShiftDown());
action->setHandled();
}
// else paths do not match - ignore event
}
else {
pickPath->ref();
if ((! pickMatching) || (mouseDownPickPath != NULL)) {
// Mouse down pick hit something.
// Was it same as mouse up pick?
// See if the pick path passes through this node
SoPath *mouseUpPickPath = copyFromThis(pickPath);
if (mouseUpPickPath != NULL) {
mouseUpPickPath->ref();
// If paths match, invoke the selection policy.
if ((! pickMatching) ||
(*mouseDownPickPath == *mouseUpPickPath)) {
if (mouseUpPickPath->getLength() == 1) {
// Path is to ONLY the selection node.
// Act as if nothing were picked, but
// allow traversal to continue.
invokeSelectionPolicy(NULL, be->wasShiftDown());
}
else {
// Alter selection and halt traversal.
invokeSelectionPolicy(mouseUpPickPath,
be->wasShiftDown());
action->setHandled();
}
}
// else paths do not match - ignore event
mouseUpPickPath->unref();
}
// else path does not pass through this node - ignore event
}
// else paths do not match - ignore event
pickPath->unref();
}
// All done with mouse down pick path
if (mouseDownPickPath != NULL) {
mouseDownPickPath->unref();
mouseDownPickPath = NULL;
}
}
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// invoke the appropriate routine which implements the current
// selection policy.
//
// Use: protected
//
void
SoSelection::invokeSelectionPolicy(SoPath *path, SbBool shiftDown)
//
////////////////////////////////////////////////////////////////////////
{
// employ the current selection policy
switch (policy.getValue()) {
case SINGLE:
performSingleSelection(path);
break;
case TOGGLE:
performToggleSelection(path);
break;
case SHIFT:
if (shiftDown)
performToggleSelection(path); // SHIFT DOWN
else performSingleSelection(path); // NO SHIFT
break;
default:
#ifdef DEBUG
SoDebugError::post("SoSelection::invokeSelectionPolicy",
"Unknown selection policy %d", policy.getValue());
#endif
break;
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// clear the selection, then select the picked object.
// if no object is picked, then nothing is selected.
// only one object may be selected at a time. The passed path should have
// already been copied (i.e. we just store it!)
//
// Use: protected
//
void
SoSelection::performSingleSelection(SoPath *path)
//
////////////////////////////////////////////////////////////////////////
{
SbBool needFinishCB = FALSE;
// let app know (if and only if) user is changing the selection
if ((getNumSelected() > 0) || (path != NULL)) {
if (startCBList != NULL)
startCBList->invokeCallbacks(this);
needFinishCB = TRUE;
}
if (path == NULL) {
deselectAll();
}
else {
if (isSelected(path)) {
// Deselect everything except the given path.
// This avoids deselecting and then selecting the same thing.
int which = selectionList.findPath(*path);
if (which != -1) {
for (int i = selectionList.getLength() - 1; i >= 0; i--) {
if (i != which)
removePath(i);
}
}
}
else {
deselectAll();
addPath(path);
}
}
// let app know user is done changing the selection
if (needFinishCB) {
if (finishCBList != NULL)
finishCBList->invokeCallbacks(this);
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// picking on an object toggles that objects selection status.
// multiple objects may be selected. The passed path should have
// already been copied (i.e. we just store it!)
//
// Use: protected
//
void
SoSelection::performToggleSelection(SoPath *path)
//
////////////////////////////////////////////////////////////////////////
{
if (path != NULL) {
// let app know user is changing the selection
if (startCBList != NULL)
startCBList->invokeCallbacks(this);
// toggle the picked object
int which = findPath(path);
if (which == -1)
addPath(path); // path not in list, add it
else removePath(which); // path in list, remove it
// let app know user is done changing the selection
if (finishCBList != NULL)
finishCBList->invokeCallbacks(this);
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Find 'this' in the path, then copy the path from 'this' down to
// the path tail. If 'this' is not in the path, return NULL.
//
// Use: private
//
SoPath *
SoSelection::copyFromThis(const SoPath *path) const
{
if (path == NULL)
return NULL;
SoNode *node;
int i, indexToThis = -1;
SoPath *p = NULL;
SoFullPath *fullInPath = (SoFullPath *) path;
for (i = 0; i < fullInPath->getLength(); i++) {
node = fullInPath->getNode(i);
if (node == this) {
indexToThis = i;
break;
}
}
if (indexToThis != -1) {
p = fullInPath->copy(indexToThis);
}
return p;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Add path to the selection list. It is assumed that path->head
// is this - DO NOT CALL THIS FUNCTION unless this is true. Also, the
// path must include more than just the selection node by itself.
//
// Use: private
//
void
SoSelection::addPath(SoPath *path)
//
////////////////////////////////////////////////////////////////////////
{
// Add the path to the selection list,
// and notify the app that this path has been selected
if (selectionList.findPath(*path) == -1) {
path->ref();
selectionList.append(path);
if (selCBList != NULL)
selCBList->invokeCallbacks(path);
path->unref();
if (changeCBList != NULL)
changeCBList->invokeCallbacks(this);
}
}
//////////////////////////////////////////////////////////////////////////////
//
// This method takes the path indexed by `which' out of the selection list.
// The path will be unreferenced by the SoPathList (SoBaseList) class.
//
// Use: protected
//
//
void
SoSelection::removePath(int which)
//
//////////////////////////////////////////////////////////////////////////////
{
// Remove the path from the selection list,
// and notify the app that this path has been deselected.
if (which >= 0) {
SoPath *p = (SoPath *) selectionList[which];
p->ref();
selectionList.remove(which);
if (deselCBList != NULL)
deselCBList->invokeCallbacks(p);
p->unref();
if (changeCBList != NULL)
changeCBList->invokeCallbacks(this);
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Add this path to the selection list. Path should include this
// node. The path will be referenced by the SoPathList (SoBaseList) class.
//
// Use: public
//
void
SoSelection::select(const SoPath *path)
//
////////////////////////////////////////////////////////////////////////
{
// get a path whose head is this selection node
SoPath *selPath = copyFromThis(path);
if ((selPath != NULL) && (selPath->getLength() > 1)) {
selPath->ref();
addPath(selPath);
selPath->unref();
}
#ifdef DEBUG
else {
SoDebugError::post("SoSelection::select",
"Path does not pass through this selection node.");
return;
}
#endif
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Search for the passed node under this, then select that path.
//
// Use: public
//
void
SoSelection::select(SoNode *node)
//
////////////////////////////////////////////////////////////////////////
{
if (node != NULL) {
node->ref();
if (searchAction == NULL)
searchAction = new SoSearchAction;
else
searchAction->reset();
searchAction->setInterest(SoSearchAction::FIRST);
searchAction->setFind(SoSearchAction::NODE);
searchAction->setNode(node);
searchAction->apply(this);
SoPath *p = searchAction->getPath();
if ((p != NULL) && (p->getLength() > 1))
addPath(p);
#ifdef DEBUG
else {
SoDebugError::post("SoSelection::select",
"Node does not pass through this selection node.");
return;
}
#endif
node->unref();
}
}
//////////////////////////////////////////////////////////////////////////////
//
// This method takes the path indexed by `which' out of the selection list.
// The path will be unreferenced by the SoPathList (SoBaseList) class.
//
// Use: public
//
//
void
SoSelection::deselect(int which)
//
//////////////////////////////////////////////////////////////////////////////
{
removePath(which);
}
//////////////////////////////////////////////////////////////////////////////
//
// remove a path, finds path and removes it by index
//
// Use: public
//
void
SoSelection::deselect(const SoPath *path)
//
//////////////////////////////////////////////////////////////////////////////
{
//??? should we copyFromThis() as a convenience to the caller?
if (path != NULL) {
int which;
if ((which = findPath(path)) != -1)
removePath(which);
#ifdef DEBUG
else
SoDebugError::post("SoSelection::deselect", "Path not already selected");
#endif
}
#ifdef DEBUG
else {
SoDebugError::post("SoSelection::deselect", "Passed path is NULL");
}
#endif
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Search for the passed node under this, then deselect that path.
//
// Use: public
//
void
SoSelection::deselect(SoNode *node)
//
////////////////////////////////////////////////////////////////////////
{
if (node != NULL) {
node->ref();
if (searchAction == NULL)
searchAction = new SoSearchAction;
else
searchAction->reset();
searchAction->setInterest(SoSearchAction::FIRST);
searchAction->setFind(SoSearchAction::NODE);
searchAction->setNode(node);
searchAction->apply(this);
if (searchAction->getPath() != NULL)
deselect(searchAction->getPath());
node->unref();
}
}
//////////////////////////////////////////////////////////////////////////////
//
// returns the index of the path in the selection list
//
// Use: protected
//
int
SoSelection::findPath(const SoPath *path) const
//
//////////////////////////////////////////////////////////////////////////////
{
int index = -1;
if (path != NULL) {
SoPath *selPath = NULL;
if (path->getHead() != this)
selPath = copyFromThis(path);
else selPath = (SoPath *) path; // const cast away
// selPath still not NULL? (copyFromThis() might have returned NULL)
if (selPath != NULL) {
selPath->ref();
index = ((SoPathList)selectionList).findPath(*selPath);
selPath->unref();
}
}
return index;
}
//////////////////////////////////////////////////////////////////////////////
//
// returns TRUE if the path is already selected
//
// Use: public
//
SbBool
SoSelection::isSelected(const SoPath *path) const
//
//////////////////////////////////////////////////////////////////////////////
{
//??? should we copyFromThis() as a convenience?
return (findPath(path) != -1);
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Search for the passed node under this, then return whether it
// is selected.
//
// Use: public
//
SbBool
SoSelection::isSelected(SoNode *node) const
//
////////////////////////////////////////////////////////////////////////
{
SbBool itIs = FALSE;
if (node != NULL) {
node->ref();
if (searchAction == NULL)
searchAction = new SoSearchAction;
else
searchAction->reset();
searchAction->setInterest(SoSearchAction::FIRST);
searchAction->setFind(SoSearchAction::NODE);
searchAction->setNode(node);
searchAction->apply((SoSelection *) this); // const cast away
if (searchAction->getPath() != NULL)
itIs = isSelected(searchAction->getPath());
node->unref();
}
return itIs;
}
//////////////////////////////////////////////////////////////////////////////
//
// toggle adds path if it's not in the selectionList, removes path if it is.
//
// Use: public
//
void
SoSelection::toggle(const SoPath *path)
//
//////////////////////////////////////////////////////////////////////////////
{
// get a path whose head is this selection node
SoPath *selPath = copyFromThis(path);
if ((selPath != NULL) && (selPath->getLength() > 1)) {
selPath->ref();
int which = findPath(selPath);
if (which == -1)
addPath(selPath); // path not in list, add it
else removePath(which); // path in list, remove it
selPath->unref();
}
#ifdef DEBUG
else {
SoDebugError::post("SoSelection::toggle",
"Path does not pass through this selection node.");
}
#endif
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Search for the passed node under this, then toggle that path.
//
// Use: public
//
void
SoSelection::toggle(SoNode *node)
//
////////////////////////////////////////////////////////////////////////
{
if (node != NULL) {
node->ref();
if (searchAction == NULL)
searchAction = new SoSearchAction;
else
searchAction->reset();
searchAction->setInterest(SoSearchAction::FIRST);
searchAction->setFind(SoSearchAction::NODE);
searchAction->setNode(node);
searchAction->apply(this);
if (searchAction->getPath() != NULL)
toggle(searchAction->getPath());
node->unref();
}
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// removes every path in the selection list
//
// Use: public
void
SoSelection::deselectAll()
//
////////////////////////////////////////////////////////////////////////
{
for(int i = selectionList.getLength() - 1; i >= 0; i--)
deselect(i);
}
//////////////////////////////////////////////////////////////////////////////
//
// Description:
// get the ith path.
//
// Use: public
//
SoPath *
SoSelection::getPath(int index) const
//
//////////////////////////////////////////////////////////////////////////////
{
SoPath *p;
#ifdef DEBUG
if ((index >= selectionList.getLength()) || (index < 0)) {
SoDebugError::post("SoSelection::getPath", "Index out of range. Index = %d, numSelected = %d",
index, selectionList.getLength());
p = NULL;
}
else
#endif
p = selectionList[index];
return p;
}
////////////////////////////////////////////////////////////////////////
//
// Description:
// Methods for setting callback functions.
//
// Use: public
//
//
////////////////////////////////////////////////////////////////////////
void
SoSelection::addSelectionCallback(SoSelectionPathCB *f, void *userData)
{
if (selCBList == NULL)
selCBList = new SoCallbackList;
selCBList->addCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::removeSelectionCallback(SoSelectionPathCB *f, void *userData)
{
if (selCBList != NULL)
selCBList->removeCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::addDeselectionCallback(SoSelectionPathCB *f, void *userData)
{
if (deselCBList == NULL)
deselCBList = new SoCallbackList;
deselCBList->addCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::removeDeselectionCallback(SoSelectionPathCB *f, void *userData)
{
if (deselCBList != NULL)
deselCBList->removeCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::addStartCallback(SoSelectionClassCB *f, void *userData)
{
if (startCBList == NULL)
startCBList = new SoCallbackList;
startCBList->addCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::removeStartCallback(SoSelectionClassCB *f, void *userData)
{
if (startCBList != NULL)
startCBList->removeCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::addFinishCallback(SoSelectionClassCB *f, void *userData)
{
if (finishCBList == NULL)
finishCBList = new SoCallbackList;
finishCBList->addCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::removeFinishCallback(SoSelectionClassCB *f, void *userData)
{
if (finishCBList != NULL)
finishCBList->removeCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::addChangeCallback(SoSelectionClassCB *f, void *userData)
{
if (changeCBList == NULL)
changeCBList = new SoCallbackList;
changeCBList->addCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::removeChangeCallback(SoSelectionClassCB *f, void *userData)
{
if (changeCBList != NULL)
changeCBList->removeCallback((SoCallbackListCB *) f, userData);
}
void
SoSelection::setPickFilterCallback(
SoSelectionPickCB *f,
void *userData,
SbBool callOnlyIfSelectable)
{
pickCBFunc = f;
pickCBData = userData;
callPickCBOnlyIfSelectable = callOnlyIfSelectable;
}