/**************************************************************************\
*
* This file is part of the Coin 3D visualization library.
* Copyright (C) 1998-2007 by Systems in Motion. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* ("GPL") version 2 as published by the Free Software Foundation.
* See the file LICENSE.GPL at the root directory of this source
* distribution for additional information about the GNU GPL.
*
* For using Coin with software that can not be combined with the GNU
* GPL, and for taking advantage of the additional benefits of our
* support services, please contact Systems in Motion about acquiring
* a Coin Professional Edition License.
*
* See http://www.coin3d.org/ for more information.
*
* Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
* http://www.sim.no/ sales@sim.no coin-support@coin3d.org
*
\**************************************************************************/
/*!
\class SoBlinker SoBlinker.h Inventor/nodes/SoBlinker.h
\brief The SoBlinker class is a cycling switch node.
\ingroup nodes
This switch node cycles its children SoBlinker::speed number of
times per second. If the node has only one child, it will be cycled
on and off. Cycling can be turned off using the SoBlinker::on field,
and the node then behaves like a normal SoSwitch node.
FILE FORMAT/DEFAULTS:
\code
Blinker {
whichChild -1
speed 1
on TRUE
}
\endcode
*/
// *************************************************************************
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../misc/PointerMap.h"
// *************************************************************************
/*!
\var SoSFFloat SoBlinker::speed
Number of cycles per second.
*/
/*!
\var SoSFBool SoBlinker::on
Controls whether cycling is on or off.
*/
// *************************************************************************
SO_NODE_SOURCE(SoBlinker);
// *************************************************************************
#define PRIVATE(_obj_) ((SoBlinkerP*) PointerMap::get(_obj_))
class SoBlinkerP {
public:
SoBlinkerP(SoBlinker * master) : master(master) { }
static void whichChildCB(void * closure, SoSensor * sensor) {
SoBlinkerP * thisp = (SoBlinkerP*) closure;
thisp->counter->reset.setValue(thisp->whichvalue);
// if sensor/blinker isn't enabled, we need to manually set the whichChild field
if (!thisp->counter->on.getValue()) {
SbBool old = thisp->master->whichChild.enableNotify(FALSE);
thisp->master->whichChild = thisp->whichvalue;
thisp->master->whichChild.enableNotify(old);
}
}
SoBlinker * master;
int whichvalue;
SoTimeCounter * counter;
SoOneShotSensor * whichChildSensor;
};
// *************************************************************************
/*!
Constructor.
*/
SoBlinker::SoBlinker(void)
{
this->counter = NULL; // this->counter is now obsoleted (pimpl version is used)
SoBlinkerP * pimpl = new SoBlinkerP(this);
PointerMap::add(this, pimpl);
pimpl->counter = new SoTimeCounter;
pimpl->counter->ref();
pimpl->counter->min = SO_SWITCH_NONE;
pimpl->counter->max = SO_SWITCH_NONE;
pimpl->counter->frequency.connectFrom(&this->speed);
pimpl->counter->on.connectFrom(&this->on);
pimpl->whichChildSensor =
new SoOneShotSensor(SoBlinkerP::whichChildCB, pimpl);
pimpl->whichChildSensor->setPriority(1);
pimpl->whichvalue = SO_SWITCH_NONE;
SO_NODE_INTERNAL_CONSTRUCTOR(SoBlinker);
SO_NODE_ADD_FIELD(speed, (1));
SO_NODE_ADD_FIELD(on, (TRUE));
this->whichChild.connectFrom(&pimpl->counter->output, TRUE);
}
/*!
Destructor.
*/
SoBlinker::~SoBlinker()
{
SoBlinkerP * pimpl = PRIVATE(this);
delete pimpl->whichChildSensor;
pimpl->counter->unref();
PointerMap::remove(this);
delete pimpl;
}
// doc in parent
void
SoBlinker::initClass(void)
{
SO_NODE_INTERNAL_INIT_CLASS(SoBlinker, SO_FROM_INVENTOR_1);
}
// Documented in superclass. Overridden to calculate bbox of all
// children.
void
SoBlinker::getBoundingBox(SoGetBoundingBoxAction * action)
{
this->whichChild.getValue();
SoGroup::getBoundingBox(action);
}
// Documented in superclass. Overridden to not write internal engine
// connections.
void
SoBlinker::write(SoWriteAction * action)
{
SoOutput * out = action->getOutput();
// Decouple connections to/from internal engine to avoid it being
// written. (Only done at first pass.)
if (out->getStage() == SoOutput::COUNT_REFS)
this->deconnectInternalEngine();
inherited::write(action);
// Reenable all connections to/from internal engine. (Only done at
// last pass.)
if (out->getStage() == SoOutput::WRITE)
this->reconnectInternalEngine();
}
// Documented in superclass. Overridden to detect "external" changes
// (i.e. not caused by the internal timer engine).
void
SoBlinker::notify(SoNotList * nl)
{
SoBlinkerP * pimpl = PRIVATE(this);
// See if the whichChild field was "manually" set.
if (nl->getFirstRec()->getBase() == this &&
nl->getLastField() == &this->whichChild) {
// delay whichChild reset with the one shot sensor (to enable
// children to be added before the reset is actually done)
// disable connection while reading whichChild to get the actual value set
SbBool old = this->whichChild.isConnectionEnabled();
this->whichChild.enableConnection(FALSE);
pimpl->whichvalue = this->whichChild.getValue();
this->whichChild.enableConnection(old);
pimpl->whichChildSensor->schedule();
}
// Check if a child was added or removed.
int lastchildidx = this->getNumChildren() - 1;
if (pimpl->counter->max.getValue() != lastchildidx) {
// Wrap to avoid recursive invocation.
pimpl->counter->enableNotify(FALSE);
// Note that if we have one child, the counting should go from -1
// to 0 (so the child is toggled on and off).
pimpl->counter->min.setValue(lastchildidx > 0 ? 0 : SO_SWITCH_NONE);
pimpl->counter->max.setValue(lastchildidx >= 0 ? lastchildidx : SO_SWITCH_NONE);
// To avoid SoSwitch getting an out-of-range whichChild value, in
// case whichChild was at the end.
if (lastchildidx < this->whichChild.getValue()) {
pimpl->counter->reset.setValue(lastchildidx);
this->whichChild.setDirty(TRUE); // Force evaluate() on the field.
}
pimpl->counter->enableNotify(TRUE);
}
inherited::notify(nl);
}
// FIXME: I _think_ we made a mistake when overriding SoNode::copy()
// and making it virtual. The special handling we need below could
// just aswell have been done in an overridden copyContents() method,
// which is the recommended mechanism for application programmers. But
// now I think we'll have to support the "virtual-ity" of
// SoNode::copy(), even though it's confusing to have 2 virtual
// copy-methods (both copy() and copyContents()). 20011220 mortene.
// Overridden to decouple and reconnect engine around copy operation.
SoNode *
SoBlinker::copy(SbBool copyconnections) const
{
// Decouple connections to/from internal engine to avoid it being
// copied.
((SoBlinker *)this)->deconnectInternalEngine();
SoBlinker * cp = (SoBlinker *)inherited::copy(copyconnections);
// Reenable all connections to/from internal engine.
((SoBlinker *)this)->reconnectInternalEngine();
// Need to set this explicitly after reconnect, as the internal
// engine for the copy initially contains incorrect values. The
// resulting notification on the copy also sets up correct min and
// max values for the engine.
cp->whichChild.setValue(this->whichChild.getValue());
return cp;
}
// Remove connections to and from internal engine.
void
SoBlinker::deconnectInternalEngine(void)
{
// Do this first, to avoid field being set due to subsequent engine
// input value change.
this->whichChild.disconnect(&PRIVATE(this)->counter->output);
PRIVATE(this)->counter->on.disconnect(&this->on);
PRIVATE(this)->counter->on = FALSE;
PRIVATE(this)->counter->frequency.disconnect(&this->speed);
}
// Reset connections to and from internal engine.
void
SoBlinker::reconnectInternalEngine(void)
{
PRIVATE(this)->counter->frequency.connectFrom(&this->speed);
PRIVATE(this)->counter->on.connectFrom(&this->on);
this->whichChild.connectFrom(&PRIVATE(this)->counter->output, TRUE);
}
#undef PRIVATE