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