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// This file contains the generic, "templatize-able" parts of the
36// So*ExaminerViewer sourcecode.
37
38// *************************************************************************
39
40/*!
41  \class So@Gui@ExaminerViewer So@Gui@ExaminerViewer.h Inventor/@Gui@/viewers/So@Gui@ExaminerViewer.h
42  \brief The So@Gui@ExaminerViewer class is a 3D-model examination viewer.
43
44  \ingroup components viewers
45
46  This class is the viewer considered to be the most "general purpose"
47  viewer, and it is often used in rapid prototyping to examine simple
48  models aswell as complete scenes (although for the latter, you might
49  be better off with one of the other viewer classes).
50
51  <center>
52  <img src="examinerviewer.png">
53  </center>
54
55  Here is a complete, stand-alone example that shows how to set up an
56  So@Gui@ExaminerViewer as a model viewer that loads Inventor and VRML
57  files from disk and places them inside the viewer for the end-user
58  to examine:
59
60  \code
61  #include <Inventor/@Gui@/So@Gui@.h>
62  #include <Inventor/@Gui@/viewers/So@Gui@ExaminerViewer.h>
63  #include <Inventor/nodes/SoBaseColor.h>
64  #include <Inventor/nodes/SoCone.h>
65  #include <Inventor/nodes/SoSeparator.h>
66
67  int
68  main(int argc, char ** argv)
69  {
70    if (argc < 2) {
71      (void)fprintf(stderr, "\n\n\tUsage: %s <modelfilename>\n\n",
72                    argc > 0 ? argv[0] : "viewerapp");
73      exit(1);
74    }
75
76    // Initialize So@Gui@ and Inventor API libraries. This returns a main
77    // window to use.
78    @WIDGET@ mainwin = So@Gui@::init(argc, argv, argv[0]);
79
80    // Open the argument file..
81    SoInput in;
82    SbBool ok = in.openFile(argv[1]);
83    if (!ok) { exit(1); }
84
85    // ..and import it.
86    SoSeparator * root = SoDB::readAll(&in);
87    if (root == NULL) { exit(1); }
88    root->ref();
89
90    // Use the ExaminerViewer, for a nice interface for 3D model
91    // inspection.
92    So@Gui@ExaminerViewer * viewer = new So@Gui@ExaminerViewer(mainwin);
93    viewer->setSceneGraph(root);
94    viewer->show();
95
96    // Pop up the main window.
97    So@Gui@::show(mainwin);
98    // Loop until exit.
99    So@Gui@::mainLoop();
100
101    // Clean up resources.
102    delete viewer;
103    root->unref();
104
105    return 0;
106  }
107  \endcode
108
109  So@Gui@ExaminerViewer has a convenient interface for repositioning
110  and reorientation of the camera, by panning, rotating and zooming
111  it's position. The following controls can be used:
112
113  <ul>
114
115  <li>hold down left mousebutton and move mouse pointer to rotate the
116  camera around it's current focal point (the focal point can be
117  changed by doing a seek operation)</li>
118
119  <li>hold middle mousebutton to pan (or a CTRL-key plus left
120  mousebutton, or a SHIFT-key plus left mousebutton)</li>
121
122  <li>hold down left + middle mousebutton to zoom / dolly, or CTRL +
123  middle mousebutton, or CTRL + SHIFT + the left mousebutton</li>
124
125  <li>click 's', then pick with the left mousebutton to seek</li>
126
127  <li>right mousebutton opens the popup menu</li>
128
129  <li>click 'ESC' key to switch to and from 'camera interaction' mode
130  and 'scenegraph interaction' mode (see setViewing()
131  documentation)</li>
132
133  <!--
134    FIXME: This functionality has been disabled. See FIXME comment
135    20050202 larsa below.
136
137  <li>hold down the 'ALT' key to temporary toggle from
138  camera-interaction mode to scenegraph-interaction mode</li>
139
140  //-->
141
142  <li>'q' quits the application</li>
143
144  </ul>
145
146  The So@Gui@ExaminerViewer provides a user decoration's button for
147  toggling between orthographic or perspective camera view volumes and
148  projection methods. This is the bottom-most click button on the
149  right decoration border.
150
151  It also inherits the decoration buttons from the So@Gui@FullViewer:
152  the arrow for switching to "scenegraph interaction" mode, the hand
153  for setting back to "camera interaction" mode, the house for "reset
154  camera to home position", the blueprint house for "set new camera
155  home position", the eye for "zoom camera out to view full scene" and
156  the flashlight for setting "click to seek" mode.
157
158  Note that a common faulty assumption about all the viewer-classes is
159  that user interaction (in the "examine"-mode, not the
160  scenegraph-interaction mode) influences the model or 3D-scene in the
161  view. This is not correct, as it is always the viewer's \e camera
162  that is translated and rotated.
163
164  The initial position of the camera is placed such that all of the
165  scenegraph's geometry fits within it's view.
166
167  \sa So@Gui@FlyViewer, So@Gui@PlaneViewer
168*/
169
170// *************************************************************************
171
172// Documentation shared between So* toolkits follows below.
173
174/*!
175  \fn So@Gui@ExaminerViewer::So@Gui@ExaminerViewer(@WIDGET@ parent, const char * name, SbBool embed, So@Gui@FullViewer::BuildFlag flag, So@Gui@Viewer::Type type)
176
177  Constructor.  See parent class for explanation of arguments.
178  Calling this constructor will make sure the examiner viewer widget
179  will be built immediately.
180*/
181
182/*!
183  \fn So@Gui@ExaminerViewer::So@Gui@ExaminerViewer(@WIDGET@ parent, const char * name, SbBool embed, So@Gui@FullViewer::BuildFlag flag, So@Gui@Viewer::Type type, SbBool build)
184
185  Constructor. See parent class for explanation of arguments.
186*/
187
188// *************************************************************************
189
190#ifdef HAVE_CONFIG_H
191#include <config.h>
192#endif // HAVE_CONFIG_H
193
194#include <assert.h>
195#include <math.h>
196
197#include <Inventor/SbTime.h>
198#include <Inventor/errors/SoDebugError.h>
199#include <Inventor/nodes/SoOrthographicCamera.h>
200#include <Inventor/nodes/SoPerspectiveCamera.h>
201#include <Inventor/projectors/SbSphereSheetProjector.h>
202#include <Inventor/projectors/SbSpherePlaneProjector.h>
203#include <Inventor/events/SoKeyboardEvent.h>
204#include <Inventor/events/SoMouseButtonEvent.h>
205#include <Inventor/events/SoLocation2Event.h>
206#include <Inventor/events/SoMotion3Event.h>
207
208#include <so@gui@defs.h>
209
210#include <Inventor/@Gui@/common/gl.h>
211#include <Inventor/@Gui@/viewers/So@Gui@ExaminerViewer.h>
212#include <Inventor/@Gui@/viewers/So@Gui@ExaminerViewerP.h>
213
214#include <Inventor/@Gui@/So@Gui@Basic.h>
215#include <Inventor/@Gui@/So@Gui@Cursor.h>
216#include <Inventor/@Gui@/viewers/SoGuiFullViewerP.h> // for pan() and zoom()
217
218#define PRIVATE(obj) ((obj)->pimpl)
219#define PUBLIC(obj) ((obj)->pub)
220
221static const int MOUSEPOSLOGSIZE = 16;
222
223// Bitmap representations of an "X", a "Y" and a "Z" for the axis cross.
224static GLubyte xbmp[] = { 0x11,0x11,0x0a,0x04,0x0a,0x11,0x11 };
225static GLubyte ybmp[] = { 0x04,0x04,0x04,0x04,0x0a,0x11,0x11 };
226static GLubyte zbmp[] = { 0x1f,0x10,0x08,0x04,0x02,0x01,0x1f };
227
228// ************************************************************************
229
230/*!
231  Decide if it should be possible to start a spin animation of the
232  model in the viewer by releasing the mouse button while dragging.
233
234  If the \a enable flag is \c FALSE and we're currently animating, the
235  spin will be stopped.
236
237  \sa isAnimationEnabled
238*/
239void
240So@Gui@ExaminerViewer::setAnimationEnabled(const SbBool enable)
241{
242  PRIVATE(this)->spinanimatingallowed = enable;
243  if (!enable && this->isAnimating()) { this->stopAnimating(); }
244}
245
246// *************************************************************************
247
248/*!
249  Query whether or not it is possible to start a spinning animation by
250  releasing the left mouse button while dragging the mouse.
251
252  \sa setAnimationEnabled
253*/
254
255SbBool
256So@Gui@ExaminerViewer::isAnimationEnabled(void) const
257{
258  return PRIVATE(this)->spinanimatingallowed;
259}
260
261// *************************************************************************
262
263/*!
264  Stop the model from spinning.
265*/
266
267void
268So@Gui@ExaminerViewer::stopAnimating(void)
269{
270  if (PRIVATE(this)->currentmode != SoGuiExaminerViewerP::SPINNING) {
271#if SO@GUI@_DEBUG
272    SoDebugError::postWarning("So@Gui@ExaminerViewer::stopAnimating",
273                              "not animating");
274#endif // SO@GUI@_DEBUG
275    return;
276  }
277  PRIVATE(this)->setMode(this->isViewing() ?
278                         SoGuiExaminerViewerP::IDLE :
279                         SoGuiExaminerViewerP::INTERACT);
280}
281
282// *************************************************************************
283
284/*!
285  Query if the model in the viewer is currently in spinning mode after
286  a user drag.
287*/
288
289SbBool
290So@Gui@ExaminerViewer::isAnimating(void) const
291{
292  return PRIVATE(this)->currentmode == SoGuiExaminerViewerP::SPINNING;
293}
294
295// ************************************************************************
296
297/*!
298  Set the flag deciding whether or not to show the axis cross.
299
300  \sa isFeedbackVisible, getFeedbackSize, setFeedbackSize
301*/
302
303void
304So@Gui@ExaminerViewer::setFeedbackVisibility(const SbBool enable)
305{
306  if (enable == PRIVATE(this)->axiscrossEnabled) {
307#ifdef SO@GUI@_EXTRA_DEBUG
308    SoDebugError::postWarning("So@Gui@ExaminerViewer::setFeedbackVisibility",
309                              "feedback visibility already set to %s", enable ? "TRUE" : "FALSE");
310#endif // SO@GUI@_EXTRA_DEBUG
311    return;
312  }
313  PRIVATE(this)->axiscrossEnabled = enable;
314
315  if (this->isViewing()) { this->scheduleRedraw(); }
316}
317
318/*!
319  Check if the feedback axis cross is visible.
320
321  \sa setFeedbackVisibility, getFeedbackSize, setFeedbackSize
322*/
323
324SbBool
325So@Gui@ExaminerViewer::isFeedbackVisible(void) const
326{
327  return PRIVATE(this)->axiscrossEnabled;
328}
329
330// ************************************************************************
331
332/*!
333  Set the size of the feedback axiscross.  The value is interpreted as
334  an approximate percentage chunk of the dimensions of the total
335  canvas.
336
337  \sa getFeedbackSize, isFeedbackVisible, setFeedbackVisibility
338*/
339void
340So@Gui@ExaminerViewer::setFeedbackSize(const int size)
341{
342#if SO@GUI@_DEBUG
343  if (size < 1) {
344    SoDebugError::postWarning("So@Gui@ExaminerViewer::setFeedbackSize",
345                              "the size setting should be larger than 0");
346    return;
347  }
348#endif // SO@GUI@_DEBUG
349
350  PRIVATE(this)->axiscrossSize = size;
351
352  if (this->isFeedbackVisible() && this->isViewing()) {
353    this->scheduleRedraw();
354  }
355}
356
357/*!
358  Return the size of the feedback axis cross. Default is 25.
359
360  \sa setFeedbackSize, isFeedbackVisible, setFeedbackVisibility
361*/
362
363int
364So@Gui@ExaminerViewer::getFeedbackSize(void) const
365{
366  return PRIVATE(this)->axiscrossSize;
367}
368
369// *************************************************************************
370
371// Documented in superclass.
372SbBool
373So@Gui@ExaminerViewer::processSoEvent(const SoEvent * const ev)
374{
375#if SO@GUI@_DEBUG && 0 // debug
376  SoDebugError::postInfo("So@Gui@ExaminerViewer::processSoEvent",
377                          "[invoked], event '%s'",
378                          ev->getTypeId().getName().getString());
379#endif // debug
380
381  // We're in "interact" mode (ie *not* the camera modification mode),
382  // so don't handle the event here. It should either be forwarded to
383  // the scenegraph, or caught by So@Gui@Viewer::processSoEvent() if
384  // it's an ESC or ALT press (to switch modes).
385  if (!this->isViewing()) { return inherited::processSoEvent(ev); }
386
387  // Events when in "ready-to-seek" mode are ignored, except those
388  // which influence the seek mode itself -- these are handled further
389  // up the inheritance hierarchy.
390  if (this->isSeekMode()) { return inherited::processSoEvent(ev); }
391
392  const SoType type(ev->getTypeId());
393
394  const SbVec2s size(this->getGLSize());
395  const SbVec2f prevnormalized = PRIVATE(this)->lastmouseposition;
396  const SbVec2s pos(ev->getPosition());
397  const SbVec2f posn((float) pos[0] / (float) So@Gui@Max((int)(size[0] - 1), 1),
398                     (float) pos[1] / (float) So@Gui@Max((int)(size[1] - 1), 1));
399
400  PRIVATE(this)->lastmouseposition = posn;
401
402  // Set to TRUE if any event processing happened. Note that it is not
403  // necessary to restrict ourselves to only do one "action" for an
404  // event, we only need this flag to see if any processing happened
405  // at all.
406  SbBool processed = FALSE;
407
408  const SoGuiExaminerViewerP::ViewerMode currentmode = PRIVATE(this)->currentmode;
409  SoGuiExaminerViewerP::ViewerMode newmode = currentmode;
410
411  PRIVATE(this)->ctrldown = ev->wasCtrlDown();
412  PRIVATE(this)->shiftdown = ev->wasShiftDown();
413
414  // Mouse Button / Spaceball Button handling
415
416  if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
417    processed = TRUE;
418
419    const SoMouseButtonEvent * const event = (const SoMouseButtonEvent *) ev;
420    const int button = event->getButton();
421    const SbBool press = event->getState() == SoButtonEvent::DOWN ? TRUE : FALSE;
422
423    switch (button) {
424    case SoMouseButtonEvent::BUTTON1:
425      PRIVATE(this)->button1down = press;
426      if (press && (currentmode == SoGuiExaminerViewerP::SEEK_WAIT_MODE)) {
427        newmode = SoGuiExaminerViewerP::SEEK_MODE;
428        this->seekToPoint(pos); // implicitly calls interactiveCountInc()
429      }
430      break;
431    case SoMouseButtonEvent::BUTTON2:
432      processed = FALSE; // pass on to superclass, so popup menu is shown
433      break;
434    case SoMouseButtonEvent::BUTTON3:
435      PRIVATE(this)->button3down = press;
436      break;
437#ifdef HAVE_SOMOUSEBUTTONEVENT_BUTTON5
438    case SoMouseButtonEvent::BUTTON4:
439      if (press) SoGuiFullViewerP::zoom(this->getCamera(), 0.1f);
440      break;
441    case SoMouseButtonEvent::BUTTON5:
442      if (press) SoGuiFullViewerP::zoom(this->getCamera(), -0.1f);
443      break;
444#endif // HAVE_SOMOUSEBUTTONEVENT_BUTTON5
445    default:
446      break;
447    }
448  }
449
450  // Keyboard handling
451  if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
452    const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev;
453    const SbBool press = event->getState() == SoButtonEvent::DOWN ? TRUE : FALSE;
454    switch (event->getKey()) {
455    case SoKeyboardEvent::LEFT_CONTROL:
456    case SoKeyboardEvent::RIGHT_CONTROL:
457      processed = TRUE;
458      PRIVATE(this)->ctrldown = press;
459      break;
460    case SoKeyboardEvent::LEFT_SHIFT:
461    case SoKeyboardEvent::RIGHT_SHIFT:
462      processed = TRUE;
463      PRIVATE(this)->shiftdown = press;
464      break;
465    default:
466      break;
467    }
468  }
469
470  // Mouse Movement handling
471  if (type.isDerivedFrom(SoLocation2Event::getClassTypeId())) {
472    const SoLocation2Event * const event = (const SoLocation2Event *) ev;
473
474    processed = TRUE;
475
476    if (PRIVATE(this)->currentmode == SoGuiExaminerViewerP::ZOOMING) {
477      PRIVATE(this)->zoomByCursor(posn, prevnormalized);
478    }
479    else if (PRIVATE(this)->currentmode == SoGuiExaminerViewerP::PANNING) {
480      SoGuiFullViewerP::pan(this->getCamera(), this->getGLAspectRatio(),
481                            PRIVATE(this)->panningplane, posn, prevnormalized);
482    }
483    else if (PRIVATE(this)->currentmode == SoGuiExaminerViewerP::DRAGGING) {
484      PRIVATE(this)->addToLog(event->getPosition(), event->getTime());
485      PRIVATE(this)->spin(posn);
486    }
487    else {
488      processed = FALSE;
489    }
490  }
491
492  // Spaceball & Joystick handling
493  if (type.isDerivedFrom(SoMotion3Event::getClassTypeId())) {
494    SoMotion3Event * const event = (SoMotion3Event *) ev;
495    SoCamera * const camera = this->getCamera();
496    if (camera) {
497      if (PRIVATE(this)->motion3OnCamera) {
498        SbVec3f dir = event->getTranslation();
499        camera->orientation.getValue().multVec(dir,dir);
500        camera->position = camera->position.getValue() + dir;
501        camera->orientation =
502          event->getRotation() * camera->orientation.getValue();
503        processed = TRUE;
504      }
505      else {
506        // FIXME: move/rotate model
507#if SO@GUI@_DEBUG
508        SoDebugError::postInfo("So@Gui@ExaminerViewer::processSoEvent",
509                               "SoMotion3Event for model movement is not implemented yet");
510#endif // SO@GUI@_DEBUG
511        processed = TRUE;
512      }
513    }
514  }
515
516  enum {
517    BUTTON1DOWN = 1 << 0,
518    BUTTON3DOWN = 1 << 1,
519    CTRLDOWN =    1 << 2,
520    SHIFTDOWN =   1 << 3
521  };
522  unsigned int combo =
523    (PRIVATE(this)->button1down ? BUTTON1DOWN : 0) |
524    (PRIVATE(this)->button3down ? BUTTON3DOWN : 0) |
525    (PRIVATE(this)->ctrldown ? CTRLDOWN : 0) |
526    (PRIVATE(this)->shiftdown ? SHIFTDOWN : 0);
527
528  switch (combo) {
529  case 0:
530    if (currentmode == SoGuiExaminerViewerP::SPINNING) { break; }
531    newmode = SoGuiExaminerViewerP::IDLE;
532    if ((currentmode == SoGuiExaminerViewerP::DRAGGING) &&
533        this->isAnimationEnabled() && (PRIVATE(this)->log.historysize >= 3)) {
534      SbTime stoptime = (ev->getTime() - PRIVATE(this)->log.time[0]);
535      if (stoptime.getValue() < 0.100) {
536        const SbVec2s glsize(this->getGLSize());
537        SbVec3f from = PRIVATE(this)->spinprojector->project(SbVec2f(float(PRIVATE(this)->log.position[2][0]) / float(So@Gui@Max(glsize[0]-1, 1)),
538                                                                     float(PRIVATE(this)->log.position[2][1]) / float(So@Gui@Max(glsize[1]-1, 1))));
539        SbVec3f to = PRIVATE(this)->spinprojector->project(posn);
540        SbRotation rot = PRIVATE(this)->spinprojector->getRotation(from, to);
541
542        SbTime delta = (PRIVATE(this)->log.time[0] - PRIVATE(this)->log.time[2]);
543        double deltatime = delta.getValue();
544        rot.invert();
545        rot.scaleAngle(float(0.200 / deltatime));
546
547        SbVec3f axis;
548        float radians;
549        rot.getValue(axis, radians);
550        if ((radians > 0.01f) && (deltatime < 0.300)) {
551          newmode = SoGuiExaminerViewerP::SPINNING;
552          PRIVATE(this)->spinRotation = rot;
553        }
554      }
555    }
556    break;
557  case BUTTON1DOWN:
558    newmode = SoGuiExaminerViewerP::DRAGGING;
559    break;
560  case BUTTON3DOWN:
561  case CTRLDOWN|BUTTON1DOWN:
562  case SHIFTDOWN|BUTTON1DOWN:
563    newmode = SoGuiExaminerViewerP::PANNING;
564    break;
565  case BUTTON1DOWN|BUTTON3DOWN:
566  case CTRLDOWN|BUTTON3DOWN:
567  case CTRLDOWN|SHIFTDOWN|BUTTON1DOWN:
568    newmode = SoGuiExaminerViewerP::ZOOMING;
569    break;
570
571    // There are many cases we don't handle that just falls through to
572    // the default case, like SHIFTDOWN, CTRLDOWN, CTRLDOWN|SHIFTDOWN,
573    // SHIFTDOWN|BUTTON3DOWN, SHIFTDOWN|CTRLDOWN|BUTTON3DOWN, etc.
574    // This is a feature, not a bug. :-)
575    //
576    // mortene.
577
578  default:
579    // The default will make a spin stop and otherwise not do
580    // anything.
581    if ((currentmode != SoGuiExaminerViewerP::SEEK_WAIT_MODE) &&
582        (currentmode != SoGuiExaminerViewerP::SEEK_MODE)) {
583      newmode = SoGuiExaminerViewerP::IDLE;
584    }
585    break;
586  }
587
588  if (newmode != currentmode) {
589    PRIVATE(this)->setMode(newmode);
590  }
591
592  // If not handled in this class, pass on upwards in the inheritance
593  // hierarchy.
594  return processed || inherited::processSoEvent(ev);
595}
596
597// *************************************************************************
598
599// documented in superclass
600void
601So@Gui@ExaminerViewer::setSeekMode(SbBool on)
602{
603  // Overrides this method to make sure any animations are stopped
604  // before we go into seek mode.
605
606  // Note: this method is almost identical to the setSeekMode() in the
607  // So@Gui@FlyViewer and So@Gui@PlaneViewer, so migrate any changes.
608
609#if SO@GUI@_DEBUG
610  if (on == this->isSeekMode()) {
611    SoDebugError::postWarning("So@Gui@ExaminerViewer::setSeekMode",
612                              "seek mode already %sset", on ? "" : "un");
613    return;
614  }
615#endif // SO@GUI@_DEBUG
616
617  if (this->isAnimating()) { this->stopAnimating(); }
618  inherited::setSeekMode(on);
619  PRIVATE(this)->setMode(on ?
620                         SoGuiExaminerViewerP::SEEK_WAIT_MODE :
621                         (this->isViewing() ?
622                          SoGuiExaminerViewerP::IDLE : SoGuiExaminerViewerP::INTERACT));
623}
624
625// *************************************************************************
626
627/*!
628  Decide whether or not the mouse pointer cursor should be visible in
629  the rendering canvas.
630*/
631void
632So@Gui@ExaminerViewer::setCursorEnabled(SbBool enable)
633{
634  inherited::setCursorEnabled(enable);
635  PRIVATE(this)->setCursorRepresentation(PRIVATE(this)->currentmode);
636}
637
638// *************************************************************************
639
640// Documented in superclass.
641const char *
642So@Gui@ExaminerViewer::getDefaultWidgetName(void) const
643{
644  return "So@Gui@ExaminerViewer";
645}
646
647// *************************************************************************
648
649// Documented in superclass.
650const char *
651So@Gui@ExaminerViewer::getDefaultTitle(void) const
652{
653  return "Examiner Viewer";
654}
655
656// *************************************************************************
657
658// Documented in superclass.
659const char *
660So@Gui@ExaminerViewer::getDefaultIconTitle(void) const
661{
662  return "Examiner Viewer";
663}
664
665// *************************************************************************
666
667// Documented in superclass. Overrides this method to be able to draw
668// the axis cross, if selected, and to keep a continuous animation
669// upon spin.
670void
671So@Gui@ExaminerViewer::actualRedraw(void)
672{
673  SbTime now = SbTime::getTimeOfDay();
674  double secs = now.getValue() -  PRIVATE(this)->prevRedrawTime.getValue();
675
676  PRIVATE(this)->prevRedrawTime = now;
677
678  if (this->isAnimating()) {
679    SbRotation deltaRotation = PRIVATE(this)->spinRotation;
680    deltaRotation.scaleAngle(float(secs * 5.0));
681    PRIVATE(this)->reorientCamera(deltaRotation);
682  }
683
684  inherited::actualRedraw();
685
686  if (this->isFeedbackVisible()) { PRIVATE(this)->drawAxisCross(); }
687
688  // Immediately reschedule to get continous spin animation.
689  if (this->isAnimating()) { this->scheduleRedraw(); }
690}
691
692// *************************************************************************
693
694// doc in super
695void
696So@Gui@ExaminerViewer::afterRealizeHook(void)
697{
698  inherited::afterRealizeHook();
699  PRIVATE(this)->setCursorRepresentation(PRIVATE(this)->currentmode);
700}
701
702// *************************************************************************
703
704// Documented in superclass. Overridden to provide the examiner viewer
705// functionality on the left thumbwheel (x axis rotation).
706void
707So@Gui@ExaminerViewer::leftWheelMotion(float value)
708{
709  if (this->isAnimating()) this->stopAnimating();
710
711  float newval = PRIVATE(this)->rotXWheelMotion(value, this->getLeftWheelValue());
712  inherited::leftWheelMotion(newval);
713}
714
715// Documented in superclass. Overridden to provide the examiner viewer
716// functionality on the bottom thumbwheel (y axis rotation).
717void
718So@Gui@ExaminerViewer::bottomWheelMotion(float value)
719{
720  if (this->isAnimating()) this->stopAnimating();
721
722  float newval = PRIVATE(this)->rotYWheelMotion(value, this->getBottomWheelValue());
723  inherited::bottomWheelMotion(newval);
724}
725
726// Documented in superclass. Overridden to provide the examiner viewer
727// functionality on the left thumbwheel (dolly/zoom).
728void
729So@Gui@ExaminerViewer::rightWheelMotion(float value)
730{
731  SoGuiFullViewerP::zoom(this->getCamera(), this->getRightWheelValue() - value);
732  inherited::rightWheelMotion(value);
733}
734
735// *************************************************************************
736
737// Documented in superclass. This method overridden from parent class
738// to make sure the mouse pointer cursor is updated.
739void
740So@Gui@ExaminerViewer::setViewing(SbBool enable)
741{
742  if (!!this->isViewing() == !!enable) {
743#if SO@GUI@_DEBUG
744    SoDebugError::postWarning("So@Gui@ExaminerViewer::setViewing",
745                              "current state already %s", enable ? "TRUE" : "FALSE");
746#endif // SO@GUI@_DEBUG
747    return;
748  }
749
750  PRIVATE(this)->setMode(enable ?
751                         SoGuiExaminerViewerP::IDLE :
752                         SoGuiExaminerViewerP::INTERACT);
753  inherited::setViewing(enable);
754}
755
756// *************************************************************************
757
758#ifndef DOXYGEN_SKIP_THIS
759
760// Remaining code is for the SoGuiExaminerViewerP "private
761// implementation" class.
762
763SoGuiExaminerViewerP::SoGuiExaminerViewerP(So@Gui@ExaminerViewer * publ)
764{
765  PUBLIC(this) = publ;
766}
767
768SoGuiExaminerViewerP::~SoGuiExaminerViewerP()
769{
770}
771
772void
773SoGuiExaminerViewerP::genericConstructor(void)
774{
775  this->currentmode = SoGuiExaminerViewerP::IDLE;
776
777  this->prevRedrawTime = SbTime::getTimeOfDay();
778  this->spinanimatingallowed = TRUE;
779  this->spinsamplecounter = 0;
780  this->spinincrement = SbRotation::identity();
781
782  // FIXME: use a smaller sphere than the default one to have a larger
783  // area close to the borders that gives us "z-axis rotation"?
784  // 19990425 mortene.
785  this->spinprojector = new SbSphereSheetProjector(SbSphere(SbVec3f(0, 0, 0), 0.8f));
786  SbViewVolume volume;
787  volume.ortho(-1, 1, -1, 1, -1, 1);
788  this->spinprojector->setViewVolume(volume);
789
790  this->axiscrossEnabled = FALSE;
791  this->axiscrossSize = 25;
792
793  this->spinRotation.setValue(SbVec3f(0, 0, -1), 0);
794
795  this->log.size = MOUSEPOSLOGSIZE;
796  this->log.position = new SbVec2s [ MOUSEPOSLOGSIZE ];
797  this->log.time = new SbTime [ MOUSEPOSLOGSIZE ];
798  this->log.historysize = 0;
799  this->button1down = FALSE;
800  this->button3down = FALSE;
801  this->ctrldown = FALSE;
802  this->shiftdown = FALSE;
803  this->pointer.now = SbVec2s(0, 0);
804  this->pointer.then = SbVec2s(0, 0);
805  this->motion3OnCamera = TRUE;
806}
807
808void
809SoGuiExaminerViewerP::genericDestructor(void)
810{
811  delete this->spinprojector;
812  delete[] this->log.position;
813  delete[] this->log.time;
814}
815
816// ************************************************************************
817
818// rotate a camera around its focalpoint, in the direction around the
819// given axis, by the given delta value (in radians)
820void
821SoGuiExaminerViewerP::rotateCamera(SoCamera * cam,
822                                   const SbVec3f & aroundaxis,
823                                   const float delta)
824{
825  const SbVec3f DEFAULTDIRECTION(0, 0, -1);
826  const SbRotation currentorientation = cam->orientation.getValue();
827
828  SbVec3f currentdir;
829  currentorientation.multVec(DEFAULTDIRECTION, currentdir);
830
831  const SbVec3f focalpoint = cam->position.getValue() +
832    cam->focalDistance.getValue() * currentdir;
833
834  // set new orientation
835  cam->orientation = SbRotation(aroundaxis, delta) * currentorientation;
836
837  SbVec3f newdir;
838  cam->orientation.getValue().multVec(DEFAULTDIRECTION, newdir);
839  cam->position = focalpoint - cam->focalDistance.getValue() * newdir;
840}
841
842// The "rotX" wheel is the wheel on the left decoration on the
843// examiner viewer.  This function translates interaction with the
844// "rotX" wheel into camera movement.
845float
846SoGuiExaminerViewerP::rotXWheelMotion(float value, float oldvalue)
847{
848  SoCamera * cam = PUBLIC(this)->getCamera();
849  if (cam == NULL) return 0.0f; // can happen for empty scenegraph
850
851  SoGuiExaminerViewerP::rotateCamera(cam, SbVec3f(-1, 0, 0), value - oldvalue);
852  return value;
853}
854
855// The "rotY" wheel is the wheel on the bottom decoration on the
856// examiner viewer.  This function translates interaction with the
857// "rotX" wheel into camera movement.
858float
859SoGuiExaminerViewerP::rotYWheelMotion(float value, float oldvalue)
860{
861  SoCamera * cam = PUBLIC(this)->getCamera();
862  if (cam == NULL) return 0.0f; // can happen for empty scenegraph
863
864  SoGuiExaminerViewerP::rotateCamera(cam, SbVec3f(0, -1, 0), value - oldvalue);
865  return value;
866}
867
868// ************************************************************************
869
870// The viewer is a state machine, and all changes to the current state
871// are made through this call.
872void
873SoGuiExaminerViewerP::setMode(const ViewerMode newmode)
874{
875  const ViewerMode oldmode = this->currentmode;
876  if (newmode == oldmode) { return; }
877
878  switch (newmode) {
879  case DRAGGING:
880    // Set up initial projection point for the projector object when
881    // first starting a drag operation.
882    this->spinprojector->project(this->lastmouseposition);
883    PUBLIC(this)->interactiveCountInc();
884    this->clearLog();
885    break;
886
887  case SPINNING:
888    PUBLIC(this)->interactiveCountInc();
889    PUBLIC(this)->scheduleRedraw();
890    break;
891
892  case PANNING:
893    {
894      // The plane we're projecting the mouse coordinates to get 3D
895      // coordinates should stay the same during the whole pan
896      // operation, so we should calculate this value here.
897      SoCamera * cam = PUBLIC(this)->getCamera();
898      if (cam == NULL) { // can happen for empty scenegraph
899        this->panningplane = SbPlane(SbVec3f(0, 0, 1), 0);
900      }
901      else {
902        SbViewVolume vv = cam->getViewVolume(PUBLIC(this)->getGLAspectRatio());
903        this->panningplane = vv.getPlane(cam->focalDistance.getValue());
904      }
905    }
906    PUBLIC(this)->interactiveCountInc();
907    break;
908
909  case ZOOMING:
910    PUBLIC(this)->interactiveCountInc();
911    break;
912
913  default: // include default to avoid compiler warnings.
914    break;
915  }
916
917  switch (oldmode) {
918  case SPINNING:
919  case DRAGGING:
920  case PANNING:
921  case ZOOMING:
922    PUBLIC(this)->interactiveCountDec();
923    break;
924
925  default:
926    break;
927  }
928
929#if SO@GUI@_DEBUG && 0 // debug
930  if (oldmode == ZOOMING) {
931    SbVec3f v = PUBLIC(this)->getCamera()->position.getValue();
932    SoDebugError::postInfo("So@Gui@ExaminerViewerP::setMode",
933                           "new camera position after zoom: <%e, %e, %e>",
934                           v[0], v[1], v[2]);
935  }
936#endif // debug
937
938  this->setCursorRepresentation(newmode);
939  this->currentmode = newmode;
940}
941
942// ************************************************************************
943
944void
945SoGuiExaminerViewerP::drawAxisCross(void)
946{
947  // FIXME: convert this to a superimposition scenegraph instead of
948  // OpenGL calls. 20020603 mortene.
949
950  // Store GL state.
951  glPushAttrib(GL_ALL_ATTRIB_BITS);
952  GLfloat depthrange[2];
953  glGetFloatv(GL_DEPTH_RANGE, depthrange);
954  GLdouble projectionmatrix[16];
955  glGetDoublev(GL_PROJECTION_MATRIX, projectionmatrix);
956
957  glDepthFunc(GL_ALWAYS);
958  glDepthMask(GL_TRUE);
959  glDepthRange(0, 0);
960  glEnable(GL_DEPTH_TEST);
961  glDisable(GL_LIGHTING);
962  glEnable(GL_COLOR_MATERIAL);
963  glDisable(GL_BLEND); // Kills transparency.
964
965  // Set the viewport in the OpenGL canvas. Dimensions are calculated
966  // as a percentage of the total canvas size.
967  SbVec2s view = PUBLIC(this)->getGLSize();
968  const int pixelarea =
969    int(float(this->axiscrossSize)/100.0f * So@Gui@Min(view[0], view[1]));
970#if 0 // middle of canvas
971  SbVec2s origin(view[0]/2 - pixelarea/2, view[1]/2 - pixelarea/2);
972#endif // middle of canvas
973#if 1 // lower right of canvas
974  SbVec2s origin(view[0] - pixelarea, 0);
975#endif // lower right of canvas
976  glViewport(origin[0], origin[1], pixelarea, pixelarea);
977
978
979
980  // Set up the projection matrix.
981  glMatrixMode(GL_PROJECTION);
982  glLoadIdentity();
983
984  const float NEARVAL = 0.1f;
985  const float FARVAL = 10.0f;
986  const float dim = NEARVAL * float(tan(M_PI / 8.0)); // FOV is 45� (45/360 = 1/8)
987  glFrustum(-dim, dim, -dim, dim, NEARVAL, FARVAL);
988
989
990  // Set up the model matrix.
991  glMatrixMode(GL_MODELVIEW);
992  glPushMatrix();
993  SbMatrix mx;
994  SoCamera * cam = PUBLIC(this)->getCamera();
995
996  // If there is no camera (like for an empty scene, for instance),
997  // just use an identity rotation.
998  if (cam) { mx = cam->orientation.getValue(); }
999  else { mx = SbMatrix::identity(); }
1000
1001  mx = mx.inverse();
1002  mx[3][2] = -3.5; // Translate away from the projection point (along z axis).
1003  glLoadMatrixf((float *)mx);
1004
1005
1006  // Find unit vector end points.
1007  SbMatrix px;
1008  glGetFloatv(GL_PROJECTION_MATRIX, (float *)px);
1009  SbMatrix comb = mx.multRight(px);
1010
1011  SbVec3f xpos;
1012  comb.multVecMatrix(SbVec3f(1,0,0), xpos);
1013  xpos[0] = (1 + xpos[0]) * view[0]/2;
1014  xpos[1] = (1 + xpos[1]) * view[1]/2;
1015  SbVec3f ypos;
1016  comb.multVecMatrix(SbVec3f(0,1,0), ypos);
1017  ypos[0] = (1 + ypos[0]) * view[0]/2;
1018  ypos[1] = (1 + ypos[1]) * view[1]/2;
1019  SbVec3f zpos;
1020  comb.multVecMatrix(SbVec3f(0,0,1), zpos);
1021  zpos[0] = (1 + zpos[0]) * view[0]/2;
1022  zpos[1] = (1 + zpos[1]) * view[1]/2;
1023
1024
1025  // Render the cross.
1026  {
1027    glLineWidth(2.0);
1028
1029    enum { XAXIS, YAXIS, ZAXIS };
1030    int idx[3] = { XAXIS, YAXIS, ZAXIS };
1031    float val[3] = { xpos[2], ypos[2], zpos[2] };
1032
1033    // Bubble sort.. :-}
1034    if (val[0] < val[1]) { So@Gui@Swap(val[0], val[1]); So@Gui@Swap(idx[0], idx[1]); }
1035    if (val[1] < val[2]) { So@Gui@Swap(val[1], val[2]); So@Gui@Swap(idx[1], idx[2]); }
1036    if (val[0] < val[1]) { So@Gui@Swap(val[0], val[1]); So@Gui@Swap(idx[0], idx[1]); }
1037    assert((val[0] >= val[1]) && (val[1] >= val[2])); // Just checking..
1038
1039    for (int i=0; i < 3; i++) {
1040      glPushMatrix();
1041      if (idx[i] == XAXIS) {                       // X axis.
1042        glColor3f(0.500f, 0.125f, 0.125f);
1043      } else if (idx[i] == YAXIS) {                // Y axis.
1044        glRotatef(90, 0, 0, 1);
1045        glColor3f(0.125f, 0.500f, 0.125f);
1046      } else {                                     // Z axis.
1047        glRotatef(-90, 0, 1, 0);
1048        glColor3f(0.125f, 0.125f, 0.500f);
1049      }
1050      this->drawArrow();
1051      glPopMatrix();
1052    }
1053  }
1054
1055  // Render axis notation letters ("X", "Y", "Z").
1056  glMatrixMode(GL_PROJECTION);
1057  glLoadIdentity();
1058  glOrtho(0, view[0], 0, view[1], -1, 1);
1059
1060  glMatrixMode(GL_MODELVIEW);
1061  glLoadIdentity();
1062
1063  GLint unpack;
1064  glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack);
1065  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1066
1067  glColor3fv(SbVec3f(0.8f, 0.8f, 0.0f).getValue());
1068
1069  glRasterPos2d(xpos[0], xpos[1]);
1070  glBitmap(8, 7, 0, 0, 0, 0, xbmp);
1071  glRasterPos2d(ypos[0], ypos[1]);
1072  glBitmap(8, 7, 0, 0, 0, 0, ybmp);
1073  glRasterPos2d(zpos[0], zpos[1]);
1074  glBitmap(8, 7, 0, 0, 0, 0, zbmp);
1075
1076  glPixelStorei(GL_UNPACK_ALIGNMENT, unpack);
1077  glPopMatrix();
1078
1079  // Reset original state.
1080
1081  // FIXME: are these 3 lines really necessary, as we push
1082  // GL_ALL_ATTRIB_BITS at the start? 20000604 mortene.
1083  glDepthRange(depthrange[0], depthrange[1]);
1084  glMatrixMode(GL_PROJECTION);
1085  glLoadMatrixd(projectionmatrix);
1086
1087  glPopAttrib();
1088}
1089
1090// Draw an arrow for the axis representation directly through OpenGL.
1091void
1092SoGuiExaminerViewerP::drawArrow(void)
1093{
1094  glBegin(GL_LINES);
1095  glVertex3f(0.0f, 0.0f, 0.0f);
1096  glVertex3f(1.0f, 0.0f, 0.0f);
1097  glEnd();
1098  glDisable(GL_CULL_FACE);
1099  glBegin(GL_TRIANGLES);
1100  glVertex3f(1.0f, 0.0f, 0.0f);
1101  glVertex3f(1.0f - 1.0f / 3.0f, +0.5f / 4.0f, 0.0f);
1102  glVertex3f(1.0f - 1.0f / 3.0f, -0.5f / 4.0f, 0.0f);
1103  glVertex3f(1.0f, 0.0f, 0.0f);
1104  glVertex3f(1.0f - 1.0f / 3.0f, 0.0f, +0.5f / 4.0f);
1105  glVertex3f(1.0f - 1.0f / 3.0f, 0.0f, -0.5f / 4.0f);
1106  glEnd();
1107  glBegin(GL_QUADS);
1108  glVertex3f(1.0f - 1.0f / 3.0f, +0.5f / 4.0f, 0.0f);
1109  glVertex3f(1.0f - 1.0f / 3.0f, 0.0f, +0.5f / 4.0f);
1110  glVertex3f(1.0f - 1.0f / 3.0f, -0.5f / 4.0f, 0.0f);
1111  glVertex3f(1.0f - 1.0f / 3.0f, 0.0f, -0.5f / 4.0f);
1112  glEnd();
1113}
1114
1115// ************************************************************************
1116
1117// Rotate the camera by the given amount, then reposition it so we're
1118// still pointing at the same focal point.
1119void
1120SoGuiExaminerViewerP::reorientCamera(const SbRotation & rot)
1121{
1122  SoCamera * cam = PUBLIC(this)->getCamera();
1123  if (cam == NULL) return;
1124
1125  // Find global coordinates of focal point.
1126  SbVec3f direction;
1127  cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
1128  SbVec3f focalpoint = cam->position.getValue() +
1129    cam->focalDistance.getValue() * direction;
1130
1131  // Set new orientation value by accumulating the new rotation.
1132  cam->orientation = rot * cam->orientation.getValue();
1133
1134  // Reposition camera so we are still pointing at the same old focal point.
1135  cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
1136  cam->position = focalpoint - cam->focalDistance.getValue() * direction;
1137}
1138
1139// ************************************************************************
1140
1141// Uses the sphere sheet projector to map the mouseposition unto
1142// a 3D point and find a rotation from this and the last calculated point.
1143void
1144SoGuiExaminerViewerP::spin(const SbVec2f & pointerpos)
1145{
1146  if (this->log.historysize < 2) return;
1147  assert(this->spinprojector != NULL);
1148
1149  SbVec2s glsize(PUBLIC(this)->getGLSize());
1150  SbVec2f lastpos;
1151  lastpos[0] = float(this->log.position[1][0]) / float(So@Gui@Max((int)(glsize[0]-1), 1));
1152  lastpos[1] = float(this->log.position[1][1]) / float(So@Gui@Max((int)(glsize[1]-1), 1));
1153
1154  this->spinprojector->project(lastpos);
1155  SbRotation r;
1156  this->spinprojector->projectAndGetRotation(pointerpos, r);
1157  r.invert();
1158  this->reorientCamera(r);
1159
1160  // Calculate an average angle magnitude value to make the transition
1161  // to a possible spin animation mode appear smooth.
1162
1163  SbVec3f dummy_axis, newaxis;
1164  float acc_angle, newangle;
1165  this->spinincrement.getValue(dummy_axis, acc_angle);
1166  acc_angle *= this->spinsamplecounter; // weight
1167  r.getValue(newaxis, newangle);
1168  acc_angle += newangle;
1169
1170  this->spinsamplecounter++;
1171  acc_angle /= this->spinsamplecounter;
1172  // FIXME: accumulate and average axis vectors aswell? 19990501 mortene.
1173  this->spinincrement.setValue(newaxis, acc_angle);
1174
1175  // Don't carry too much baggage, as that'll give unwanted results
1176  // when the user quickly trigger (as in "click-drag-release") a spin
1177  // animation.
1178  if (this->spinsamplecounter > 3) this->spinsamplecounter = 3;
1179}
1180
1181// ************************************************************************
1182
1183// Calculate a zoom/dolly factor from the difference of the current
1184// cursor position and the last.
1185void
1186SoGuiExaminerViewerP::zoomByCursor(const SbVec2f & thispos,
1187                                   const SbVec2f & prevpos)
1188{
1189  // There is no "geometrically correct" value, 20 just seems to give
1190  // about the right "feel".
1191  SoGuiFullViewerP::zoom(PUBLIC(this)->getCamera(),
1192                         (thispos[1] - prevpos[1]) * 20.0f);
1193}
1194
1195// *************************************************************************
1196// Methods used for spin animation tracking.
1197
1198// This method "clears" the mouse location log, used for spin
1199// animation calculations.
1200void
1201SoGuiExaminerViewerP::clearLog(void)
1202{
1203  this->log.historysize = 0;
1204}
1205
1206// This method adds another point to the mouse location log, used for spin
1207// animation calculations.
1208void
1209SoGuiExaminerViewerP::addToLog(const SbVec2s pos, const SbTime time)
1210{
1211  // In case someone changes the const size setting at the top of this
1212  // file too small.
1213  assert (this->log.size > 2 && "mouse log too small!");
1214
1215  if (this->log.historysize > 0 && pos == this->log.position[0]) {
1216#if SO@GUI@_DEBUG && 0 // debug
1217    // This can at least happen under SoQt.
1218    SoDebugError::postInfo("SoGuiExaminerViewerP::addToLog", "got position already!");
1219#endif // debug
1220    return;
1221  }
1222
1223  int lastidx = this->log.historysize;
1224  // If we've filled up the log, we should throw away the last item:
1225  if (lastidx == this->log.size) { lastidx--; }
1226
1227  assert(lastidx < this->log.size);
1228  for (int i = lastidx; i > 0; i--) {
1229    this->log.position[i] = this->log.position[i-1];
1230    this->log.time[i] = this->log.time[i-1];
1231  }
1232
1233  this->log.position[0] = pos;
1234  this->log.time[0] = time;
1235  if (this->log.historysize < this->log.size)
1236    this->log.historysize += 1;
1237}
1238
1239// *************************************************************************
1240
1241// This method sets whether Motion3 events should affect the camera or
1242// the model.
1243void
1244SoGuiExaminerViewerP::setMotion3OnCamera(SbBool enable)
1245{
1246  this->motion3OnCamera = enable;
1247}
1248
1249// This method returns whether Motion3 events affects the camera or
1250// the model.
1251SbBool
1252SoGuiExaminerViewerP::getMotion3OnCamera(void) const
1253{
1254  return this->motion3OnCamera;
1255}
1256
1257// ************************************************************************
1258
1259// Set cursor graphics according to mode.
1260void
1261SoGuiExaminerViewerP::setCursorRepresentation(int modearg)
1262{
1263  if (!PUBLIC(this)->isCursorEnabled()) {
1264    PUBLIC(this)->setComponentCursor(So@Gui@Cursor::getBlankCursor());
1265    return;
1266  }
1267
1268  switch (modearg) {
1269  case SoGuiExaminerViewerP::INTERACT:
1270    PUBLIC(this)->setComponentCursor(So@Gui@Cursor(So@Gui@Cursor::DEFAULT));
1271    break;
1272
1273  case SoGuiExaminerViewerP::IDLE:
1274  case SoGuiExaminerViewerP::DRAGGING:
1275  case SoGuiExaminerViewerP::SPINNING:
1276    PUBLIC(this)->setComponentCursor(So@Gui@Cursor::getRotateCursor());
1277    break;
1278
1279  case SoGuiExaminerViewerP::ZOOMING:
1280    PUBLIC(this)->setComponentCursor(So@Gui@Cursor::getZoomCursor());
1281    break;
1282
1283  case SoGuiExaminerViewerP::SEEK_MODE:
1284  case SoGuiExaminerViewerP::SEEK_WAIT_MODE:
1285    PUBLIC(this)->setComponentCursor(So@Gui@Cursor(So@Gui@Cursor::CROSSHAIR));
1286    break;
1287
1288  case SoGuiExaminerViewerP::PANNING:
1289    PUBLIC(this)->setComponentCursor(So@Gui@Cursor::getPanCursor());
1290    break;
1291
1292  default: assert(0); break;
1293  }
1294}
1295
1296#endif // DOXYGEN_SKIP_THIS
1297
1298// *************************************************************************
1299
1300#undef PRIVATE
1301#undef PUBLIC
1302
1303