1// @configure_input@
2
3/**************************************************************************\
4 * Copyright (c) Kongsberg Oil & Gas Technologies AS
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 *
14 * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * Neither the name of the copyright holder nor the names of its
19 * contributors may be used to endorse or promote products derived from
20 * this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33\**************************************************************************/
34
35// FIXME: overload the getClassName() type functions
36
37#include <Inventor/SbPList.h>
38#include <Inventor/SoSceneManager.h>
39#include <Inventor/fields/SoSFColor.h>
40#include <Inventor/fields/SoMFColor.h>
41#include <Inventor/fields/SoMFUInt32.h>
42#include <Inventor/nodes/SoSeparator.h>
43#include <Inventor/nodes/SoTexture2.h>
44#include <Inventor/sensors/SoFieldSensor.h>
45#include <Inventor/actions/SoGLRenderAction.h>
46
47#include <Inventor/@Gui@/SoAny.h>
48#include <Inventor/@Gui@/nodes/SoGuiViewportFix.h>
49#include <Inventor/@Gui@/nodes/SoGuiColorEditor.h>
50#include <Inventor/@Gui@/editors/So@Gui@ColorEditor.h>
51
52/*
53#include <Inventor/@Gui@/nodes/SoGuiPane.h>
54#include <Inventor/@Gui@/nodes/SoGuiClickCounter.h>
55#include <Inventor/@Gui@/nodes/SoGuiSlider1.h>
56#include <Inventor/@Gui@/nodes/SoGuiSlider2.h>
57*/
58
59
60/*!
61  \class So@Gui@ColorEditor Inventor/@Gui@/editors/So@Gui@ColorEditor.h
62  \brief The So@Gui@ColorEditor class is a GUI component for interactively
63  editing color fields.
64*/
65
66/*!
67  \enum So@Gui@ColorEditor::Sliders
68*/
69
70/*!
71  \val So@Gui@ColorEditor::NONE
72*/
73
74/*!
75  \val So@Gui@ColorEditor::INTENSITY
76*/
77
78/*!
79  \val So@Gui@ColorEditor::RGB
80*/
81
82/*!
83  \val So@Gui@ColorEditor::HSV
84*/
85
86/*!
87  \val So@Gui@ColorEditor::RGB_V
88*/
89
90/*!
91  \val So@Gui@ColorEditor::RGB_HSV
92*/
93
94/*!
95  \enum So@Gui@ColorEditor::UpdateFrequency
96*/
97
98/*!
99  \val So@Gui@ColorEditor::CONTINUOUS
100*/
101
102/*!
103  \val So@Gui@ColorEditor::AFTER_ACCEPT
104*/
105
106// *************************************************************************
107
108static const SbBool SGI_ATTACHMENT_REF_COMPATIBILITY = TRUE;
109
110enum Attachment {
111  DETACHED,
112  SFCOLOR,
113  MFCOLOR,
114  MFUINT32
115};
116
117// Name suffix used to avoid conflict with private part of the ColorEditor
118// node kit.
119
120class ColorEditorComponent {
121public:
122  So@Gui@ColorEditor * api;
123
124  static const char * superscene[];
125
126  SbPList callbacks;
127
128  // attachment is redundant - the existence of the field sensor, and the
129  // field type it is attached to is all the info needed really
130  Attachment attachment;
131  // the field pointers can actually be dropped since the sensor will have
132  // that info
133  SoSFColor * sfcolor;
134  SoMFColor * mfcolor;
135  SoMFUInt32 * mfuint32;
136  int mfindex;
137
138  SoFieldSensor * editor_sensor;
139  static void editor_update_cb(void * closure, SoSensor * sensor);
140
141  SoFieldSensor * attachment_sensor;
142  static void attachment_update_cb(void * closure, SoSensor * sensor);
143
144  SoGuiColorEditor * editor;
145
146  void invokeColorChangeCallbacks(void);
147  SbBool colorsEqual(void);
148};
149
150// *************************************************************************
151
152SO@GUI@_OBJECT_SOURCE(So@Gui@ColorEditor);
153
154#define PRIVATE(obj) ((ColorEditorComponent *) ((So@Gui@ColorEditor *) obj)->internals)
155#define PUBLIC(obj) (((ColorEditorComponent *) obj)->api)
156
157So@Gui@ColorEditor::So@Gui@ColorEditor(@WIDGET@ parent, const char * name, SbBool embed)
158  : inherited(parent, name, embed)
159{
160  this->internals = (void *) new ColorEditorComponent;
161  PRIVATE(this)->api = this;
162
163  PRIVATE(this)->attachment = DETACHED;
164  PRIVATE(this)->sfcolor = NULL;
165  PRIVATE(this)->sfcolor = NULL;
166  PRIVATE(this)->mfcolor = NULL;
167  PRIVATE(this)->mfuint32 = NULL;
168  PRIVATE(this)->mfindex = 0;
169
170  PRIVATE(this)->editor_sensor = NULL;
171  PRIVATE(this)->attachment_sensor = NULL;
172
173  PRIVATE(this)->editor = NULL;
174
175  this->setSize(SbVec2s(320, 256));
176
177  SoNode * root = SoAny::loadSceneGraph(ColorEditorComponent::superscene);
178  assert(root != NULL);
179  assert(root->isOfType(SoSeparator::getClassTypeId()));
180  SoSeparator * superscene = (SoSeparator *) root;
181
182  PRIVATE(this)->editor = new SoGuiColorEditor;
183  superscene->addChild(PRIVATE(this)->editor);
184  this->setSceneGraph(superscene);
185
186  PRIVATE(this)->attachment_sensor = new SoFieldSensor(ColorEditorComponent::attachment_update_cb, PRIVATE(this));
187
188  PRIVATE(this)->editor_sensor = new SoFieldSensor(ColorEditorComponent::editor_update_cb, PRIVATE(this));
189  PRIVATE(this)->editor_sensor->attach(&(PRIVATE(this)->editor->color));
190}
191
192So@Gui@ColorEditor::~So@Gui@ColorEditor(void)
193{
194  if ( PRIVATE(this)->attachment != DETACHED ) this->detach();
195  delete PRIVATE(this)->attachment_sensor;
196  delete PRIVATE(this)->editor_sensor;
197  this->setSceneGraph(NULL);
198  ColorEditorComponent * instance = PRIVATE(this);
199  delete instance;
200}
201
202/*!
203  Attach the editor to a color single field.  Any existing attachments are
204  detached.
205
206  The node argument defaults to NULL and is ignored.  It is part of the
207  argument list for compatibility reasons.
208*/
209
210void
211So@Gui@ColorEditor::attach(SoSFColor * color, SoBase * node)
212{
213  if ( PRIVATE(this)->attachment != DETACHED ) this->detach();
214  if ( color != NULL ) {
215    if ( SGI_ATTACHMENT_REF_COMPATIBILITY ) {
216      SoFieldContainer * container = color->getContainer();
217      if ( container != NULL ) container->ref();
218    }
219    PRIVATE(this)->attachment = SFCOLOR;
220    PRIVATE(this)->sfcolor = color;
221    assert(PRIVATE(this)->attachment_sensor != NULL);
222    PRIVATE(this)->attachment_sensor->attach(color);
223    PRIVATE(this)->editor->color.setValue(color->getValue());
224  }
225}
226
227/*!
228  Attach the editor to an element in a color multi field.  Any existing attachments are
229  detached.
230
231  The node argument defaults to NULL and is ignored.  It is part of the
232  argument list for compatibility reasons.
233*/
234
235void
236So@Gui@ColorEditor::attach(SoMFColor * color, int idx, SoBase * node)
237{
238  if ( PRIVATE(this)->attachment != DETACHED ) this->detach();
239  if ( color != NULL ) {
240    if ( SGI_ATTACHMENT_REF_COMPATIBILITY ) {
241      SoFieldContainer * container = color->getContainer();
242      if ( container != NULL ) container->ref();
243    }
244    PRIVATE(this)->attachment = MFCOLOR;
245    PRIVATE(this)->mfcolor = color;
246    PRIVATE(this)->mfindex = idx;
247    assert(PRIVATE(this)->attachment_sensor != NULL);
248    PRIVATE(this)->attachment_sensor->attach(color);
249    PRIVATE(this)->editor->color.setValue(color->operator[](idx));
250  }
251}
252
253/*!
254  Attach the editor to an element in an uint32 multi field.  The field
255  is assumed to be of the RGBA packed color format.  Any existing attachments are
256  detached.
257
258  The node argument defaults to NULL and is ignored.  It is part of the
259  argument list for compatibility reasons.
260*/
261
262void
263So@Gui@ColorEditor::attach(SoMFUInt32 * color, int idx, SoBase * node)
264{
265  if ( PRIVATE(this)->attachment != DETACHED ) this->detach();
266  if ( color != NULL ) {
267    if ( SGI_ATTACHMENT_REF_COMPATIBILITY ) {
268      SoFieldContainer * container = color->getContainer();
269      if ( container != NULL ) container->ref();
270    }
271    PRIVATE(this)->attachment = MFUINT32;
272    PRIVATE(this)->mfuint32 = color;
273    PRIVATE(this)->mfindex = idx;
274    assert(PRIVATE(this)->attachment_sensor != NULL);
275    PRIVATE(this)->attachment_sensor->attach(color);
276    SbColor col;
277    float transparency = 0.0f;
278    col.setPackedValue(color->operator[](idx), transparency);
279    PRIVATE(this)->editor->color.setValue(col);
280  }
281}
282
283/*!
284  Detach the editor from the field it is attached to.
285*/
286
287void
288So@Gui@ColorEditor::detach(void)
289{
290  if ( PRIVATE(this)->attachment != DETACHED ) {
291    SoField * field = NULL;
292    switch ( PRIVATE(this)->attachment ) {
293    case SFCOLOR:
294      field = PRIVATE(this)->sfcolor;
295      PRIVATE(this)->sfcolor = NULL;
296      break;
297    case MFCOLOR:
298      field = PRIVATE(this)->mfcolor;
299      PRIVATE(this)->mfcolor = NULL;
300      break;
301    case MFUINT32:
302      field = PRIVATE(this)->mfuint32;
303      PRIVATE(this)->mfuint32 = NULL;
304      break;
305    case DETACHED:
306    default:
307      assert(0 && "impossible switch case");
308      break;
309    }
310    assert(field != NULL);
311    if ( field != NULL ) {
312      assert(PRIVATE(this)->attachment_sensor != NULL);
313      PRIVATE(this)->attachment_sensor->detach();
314      if ( SGI_ATTACHMENT_REF_COMPATIBILITY ) {
315        SoFieldContainer * container = field->getContainer();
316        if ( container != NULL ) container->unref();
317      }
318    }
319    PRIVATE(this)->attachment = DETACHED;
320  }
321}
322
323/*!
324  This method returns whether or not the editor is currently attached to a field.
325*/
326
327SbBool
328So@Gui@ColorEditor::isAttached(void) const
329{
330  return (PRIVATE(this)->attachment != DETACHED) ? TRUE : FALSE;
331}
332
333/*
334  Add a callback to be triggered when the color value is changed.
335
336  \sa So@Gui@ColorEditor::setUpdateFrequency
337*/
338
339void
340So@Gui@ColorEditor::addColorChangedCallback(So@Gui@ColorEditorCB * callback, void * closure)
341{
342  PRIVATE(this)->callbacks.append((void *) callback);
343  PRIVATE(this)->callbacks.append(closure);
344}
345
346/*!
347  Remove all color change callbacks matching the given arguments.
348*/
349
350void
351So@Gui@ColorEditor::removeColorChangedCallback(So@Gui@ColorEditorCB * callback, void * closure)
352{
353  const int len = PRIVATE(this)->callbacks.getLength();
354  int i;
355  for ( i = 0; i < len; i += 2 ) {
356    So@Gui@ColorEditorCB * cb =
357      (So@Gui@ColorEditorCB *) PRIVATE(this)->callbacks[i];
358    if ( (callback == cb) && (closure == PRIVATE(this)->callbacks[i+1]) ) {
359      PRIVATE(this)->callbacks.remove(i+1);
360      PRIVATE(this)->callbacks.remove(i);
361      i -= 2;
362    }
363  }
364}
365
366/*!
367  Set a new color value.
368
369  If the field value gets updated, the color change callbacks will be triggered.
370*/
371
372void
373So@Gui@ColorEditor::setColor(const SbColor & color)
374{
375  // callbacks are triggered on the sensor rebound...
376  switch ( PRIVATE(this)->attachment ) {
377  case DETACHED:
378    break;
379  case SFCOLOR:
380    assert(PRIVATE(this)->sfcolor != NULL);
381    if ( PRIVATE(this)->sfcolor->getValue() != color ) {
382      PRIVATE(this)->sfcolor->setValue(color);
383    }
384    break;
385  case MFCOLOR:
386    assert(PRIVATE(this)->mfcolor != NULL);
387    if ( PRIVATE(this)->mfcolor->operator[](PRIVATE(this)->mfindex) != color ) {
388      PRIVATE(this)->mfcolor->set1Value(PRIVATE(this)->mfindex, color);
389    }
390    break;
391  case MFUINT32:
392    assert(PRIVATE(this)->mfuint32 != NULL);
393    if ( PRIVATE(this)->mfuint32->operator[](PRIVATE(this)->mfindex) != color.getPackedValue() ) {
394      PRIVATE(this)->mfuint32->set1Value(PRIVATE(this)->mfindex, color.getPackedValue());
395    }
396    break;
397  }
398  assert(PRIVATE(this)->editor != NULL);
399  PRIVATE(this)->editor->color.setValue(color);
400}
401
402/*!
403  Get the current color value.
404*/
405
406const SbColor &
407So@Gui@ColorEditor::getColor(void) const
408{
409  assert(PRIVATE(this)->editor != NULL);
410  return PRIVATE(this)->editor->color.getValue();
411}
412
413/*!
414  Not implemented yet.
415
416  Sets whether or not the color sliders should be in WYSIWYG mode.
417  When enabled, the color backgrounds in the sliders will be updated to
418  reflect what the color will be, taken all color components into account.
419  When disabled, the color backgrounds only reflect the component the slider
420  controls.
421*/
422
423void
424So@Gui@ColorEditor::setWYSIWYG(SbBool enable)
425{
426  assert(PRIVATE(this)->editor != NULL);
427  PRIVATE(this)->editor->wysiwyg.setValue(enable);
428}
429
430/*!
431  Returns whether or not the editor sliders are in WYSIWYG mode.
432*/
433
434SbBool
435So@Gui@ColorEditor::isWYSIWYG(void) const
436{
437  assert(PRIVATE(this)->editor != NULL);
438  return PRIVATE(this)->editor->wysiwyg.getValue();
439}
440
441/*!
442  Sets which if the slider sets is to be used.
443
444  \sa So@Gui@ColorEditor::Sliders
445*/
446
447void
448So@Gui@ColorEditor::setCurrentSliders(So@Gui@ColorEditor::Sliders which)
449{
450  assert(PRIVATE(this)->editor != NULL);
451  PRIVATE(this)->editor->sliders.setValue((So@Gui@ColorEditor::Sliders) which);
452}
453
454/*!
455  Returns which slider sets is being used.
456
457  \sa So@Gui@ColorEditor::Sliders
458*/
459
460So@Gui@ColorEditor::Sliders
461So@Gui@ColorEditor::getCurrentSliders(void) const
462{
463  assert(PRIVATE(this)->editor != NULL);
464  return (So@Gui@ColorEditor::Sliders) PRIVATE(this)->editor->sliders.getValue();
465}
466
467/*!
468  Sets the update-frequency setting, which affects when color change callbacks
469  will be triggered.
470
471  \sa So@Gui@ColorEditor::UpdateFrequency
472*/
473
474void
475So@Gui@ColorEditor::setUpdateFrequency(So@Gui@ColorEditor::UpdateFrequency freq)
476{
477  assert(PRIVATE(this)->editor != NULL);
478  PRIVATE(this)->editor->update.setValue((So@Gui@ColorEditor::UpdateFrequency) freq);
479}
480
481/*!
482  Returns the update-frequency setting.
483
484  \sa So@Gui@ColorEditor::UpdateFrequency
485*/
486
487So@Gui@ColorEditor::UpdateFrequency
488So@Gui@ColorEditor::getUpdateFrequency(void) const
489{
490  assert(PRIVATE(this)->editor != NULL);
491  return (So@Gui@ColorEditor::UpdateFrequency) PRIVATE(this)->editor->update.getValue();
492}
493
494SoGuiColorEditor *
495So@Gui@ColorEditor::getEditor(void) const
496{
497  return PRIVATE(this)->editor;
498}
499
500// *************************************************************************
501
502const char *
503So@Gui@ColorEditor::getDefaultWidgetName(void) const
504{
505  static const char widgetName[] = "So@Gui@ColorEditor";
506  return widgetName;
507}
508
509const char *
510So@Gui@ColorEditor::getDefaultTitle(void) const
511{
512  static const char title[] = "ColorEditor";
513  return title;
514}
515
516const char *
517So@Gui@ColorEditor::getDefaultIconTitle(void) const
518{
519  static const char iconTitle[] = "ColEd";
520  return iconTitle;
521}
522
523// *************************************************************************
524// ColorEditorComponent
525// *************************************************************************
526
527const char *
528ColorEditorComponent::superscene[] =
529{
530  "#Inventor V2.1 ascii",
531  "",
532  "Separator {",
533  "  DirectionalLight { direction 0 0 -1 color 1 1 1 intensity 0.8 }",
534  "  OrthographicCamera { }",
535  "  DEF viewportfix SoGuiViewportFix { }",
536  "  Material { ambientColor 0.8 0.8 0.8 }",
537  "}",
538  NULL
539};
540
541// *************************************************************************
542
543void
544ColorEditorComponent::invokeColorChangeCallbacks(void)
545{
546  int i;
547  for ( i = 0; i < this->callbacks.getLength(); i += 2 ) {
548    So@Gui@ColorEditorCB * callback = (So@Gui@ColorEditorCB *) this->callbacks[i];
549    void * closure = this->callbacks[i+1];
550    callback(closure, &this->editor->color.getValue());
551  }
552}
553
554SbBool
555ColorEditorComponent::colorsEqual(void)
556{
557  SbColor attachmentColor;
558  switch ( this->attachment ) {
559  case SFCOLOR:
560    assert(this->sfcolor != NULL);
561    attachmentColor = this->sfcolor->getValue();
562    break;
563  case MFCOLOR:
564    assert(this->mfcolor != NULL);
565    attachmentColor = this->mfcolor->operator[](this->mfindex);
566    break;
567  case MFUINT32:
568    assert(this->mfcolor != NULL);
569    do {
570      float transparency = 0.0f;
571      attachmentColor.setPackedValue(this->mfuint32->operator[](this->mfindex), transparency);
572    } while ( FALSE );
573    break;
574  case DETACHED:
575  default:
576    return TRUE;
577  }
578  return (attachmentColor == this->editor->color.getValue()) ? TRUE : FALSE;
579}
580
581void
582ColorEditorComponent::attachment_update_cb(void * closure, SoSensor * sensor)
583{
584  assert(closure != NULL);
585  ColorEditorComponent * me = (ColorEditorComponent *) closure;
586  if ( me->colorsEqual() ) return;
587
588  switch ( me->attachment ) {
589  case SFCOLOR:
590    assert(me->sfcolor != NULL);
591    me->editor->color.setValue(me->sfcolor->getValue());
592    break;
593  case MFCOLOR:
594    assert(me->mfcolor != NULL);
595    me->editor->color.setValue(me->mfcolor->operator[](me->mfindex));
596    break;
597  case MFUINT32:
598    assert(me->mfcolor != NULL);
599    do {
600      SbColor color;
601      float transparency = 0.0f;
602      color.setPackedValue(me->mfuint32->operator[](me->mfindex), transparency);
603      me->editor->color.setValue(color);
604    } while ( FALSE );
605    break;
606  case DETACHED:
607  default:
608    break;
609  }
610}
611
612void
613ColorEditorComponent::editor_update_cb(void * closure, SoSensor * sensor)
614{
615  assert(closure != NULL);
616  ColorEditorComponent * me = (ColorEditorComponent *) closure;
617  if ( me->colorsEqual() ) return;
618
619  SbColor color = me->editor->color.getValue();
620
621  switch ( me->attachment ) {
622  case SFCOLOR:
623    assert(me->sfcolor != NULL);
624    me->sfcolor->setValue(color);
625    break;
626  case MFCOLOR:
627    assert(me->mfcolor != NULL);
628    me->mfcolor->set1Value(me->mfindex, color);
629    break;
630  case MFUINT32:
631    assert(me->mfuint32 != NULL);
632    me->mfuint32->set1Value(me->mfindex, color.getPackedValue());
633    break;
634  case DETACHED:
635  default:
636    break;
637  }
638
639  if ( me->editor->update.getValue() == SoGuiColorEditor::CONTINUOUS )
640    me->invokeColorChangeCallbacks();
641}
642
643// *************************************************************************
644
645#undef PRIVATE
646#undef PUBLIC
647
648