1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 // Class documentation in common/SoGuiComponentCommon.cpp.in.
34 
35 // *************************************************************************
36 
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif // HAVE_CONFIG_H
40 #include <qt-config.h>
41 
42 #include <qbitmap.h>
43 #include <qwidget.h>
44 #include <qmainwindow.h>
45 #include <qapplication.h>
46 #include <qmetaobject.h>
47 #include <qcursor.h>
48 #include <qevent.h>
49 
50 #include <Inventor/errors/SoDebugError.h>
51 
52 #include <Inventor/Qt/SoQtComponentP.h>
53 #include <Inventor/Qt/moc_SoQtComponentP.icc>
54 
55 #include <Inventor/Qt/SoQt.h>
56 #include <Inventor/Qt/SoQtComponent.h>
57 #include <Inventor/Qt/SoQtGLWidget.h>
58 #include <Inventor/Qt/SoQtRenderArea.h>
59 #include <Inventor/Qt/viewers/SoQtViewer.h>
60 #include <Inventor/Qt/viewers/SoQtFullViewer.h>
61 #include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
62 #include <Inventor/Qt/viewers/SoQtPlaneViewer.h>
63 #include <Inventor/Qt/viewers/SoQtConstrainedViewer.h>
64 #include <Inventor/Qt/viewers/SoQtFlyViewer.h>
65 #include <Inventor/Qt/SoAny.h>
66 
67 #include <soqtdefs.h>
68 
69 // debug
70 #define SOQTCOMP_RESIZE_DEBUG 0
71 
72 static const char nullstring[] = "(null)";
73 
74 #define PRIVATE(obj) (obj)
75 #define PUBLIC(obj) ((obj)->pub)
76 
77 // *************************************************************************
78 
79 #ifndef DOXYGEN_SKIP_THIS // Skip internal class SoQtComponentP.
80 
81 // The private data and code for the SoQtComponent.
82 
83 SbDict * SoQtComponentP::cursordict = NULL;
84 
SoQtComponentP(SoQtComponent * o)85 SoQtComponentP::SoQtComponentP(SoQtComponent * o)
86   : SoGuiComponentP(o), classname(""), widgetname("")
87 {
88 }
89 
~SoQtComponentP()90 SoQtComponentP::~SoQtComponentP()
91 {
92 }
93 
94 void
fatalerrorHandler(void * userdata)95 SoQtComponentP::fatalerrorHandler(void * userdata)
96 {
97   SoQtComponentP * that = (SoQtComponentP *)userdata;
98   that->cleanupQtReferences();
99 }
100 
101 void
cleanupQtReferences(void)102 SoQtComponentP::cleanupQtReferences(void)
103 {
104   // Kill the forwarding of messages to the eventFilter() method, as
105   // that can lead to all kinds of problems if a fatal-error triggers
106   // during construction of the component (like for instance in the
107   // case where no valid OpenGL canvas can be set up for the
108   // SoQtGLWidget).
109   this->parent->removeEventFilter(this);
110 }
111 
112 #if COIN_MAJOR_VERSION >= 3
delete_dict_value(SbDict::Key key,void * value)113 static void delete_dict_value(SbDict::Key key, void * value)
114 {
115   delete (QCursor *)value;
116 }
117 #else // COIN_MAJOR_VERSION >= 3
delete_dict_value(unsigned long key,void * value)118 static void delete_dict_value(unsigned long key, void * value)
119 {
120   delete (QCursor *)value;
121 }
122 #endif // COIN_MAJOR_VERSION < 3
123 void
atexit_cleanup()124 SoQtComponentP::atexit_cleanup()
125 {
126   if (SoQtComponentP::cursordict) {
127     SoQtComponentP::cursordict->applyToAll(delete_dict_value);
128     delete SoQtComponentP::cursordict;
129     SoQtComponentP::cursordict = NULL;
130   }
131 }
132 
133 // Converts from the common generic cursor format to a QCursor
134 // instance.
135 QCursor *
getNativeCursor(const SoQtCursor::CustomCursor * cc)136 SoQtComponentP::getNativeCursor(const SoQtCursor::CustomCursor * cc)
137 {
138   if (SoQtComponentP::cursordict == NULL) { // first call, initialize
139     SoQtComponentP::cursordict = new SbDict;
140     SoAny::atexit((SoAny::atexit_f*)SoQtComponentP::atexit_cleanup, 0);
141   }
142 
143   void * qc;
144   SbBool b = SoQtComponentP::cursordict->find((uintptr_t)cc, qc);
145   if (b) { return (QCursor *)qc; }
146 
147 #define MAXBITMAPWIDTH 32
148 #define MAXBITMAPHEIGHT 32
149 #define MAXBITMAPBYTES (((MAXBITMAPWIDTH + 7) / 8) * MAXBITMAPHEIGHT)
150 
151   uchar cursorbitmap[MAXBITMAPBYTES];
152   uchar cursormask[MAXBITMAPBYTES];
153   (void)memset(cursorbitmap, 0x00, MAXBITMAPBYTES);
154   (void)memset(cursormask, 0x00, MAXBITMAPBYTES);
155 
156   if ( !(cc->dim[0] <= MAXBITMAPWIDTH) )
157     printf("cursor bitmap width too large: %d\n", cc->dim[0]);
158   if ( !(cc->dim[1] <= MAXBITMAPHEIGHT) )
159     printf("cursor bitmap height too large: %d\n", cc->dim[1]);
160   assert(cc->dim[0] <= MAXBITMAPWIDTH && "internal bitmap too large");
161   assert(cc->dim[1] <= MAXBITMAPHEIGHT && "internal bitmap too large");
162 
163   const int BYTEWIDTH = (cc->dim[0] + 7) / 8;
164   for (int h=0; h < cc->dim[1]; h++) {
165     for (int w=0; w < BYTEWIDTH; w++) {
166       const int cursorpos = h * ((MAXBITMAPWIDTH + 7) / 8) + w;
167       const int nativepos = h * BYTEWIDTH + w;
168       cursorbitmap[cursorpos] = cc->bitmap[nativepos];
169       cursormask[cursorpos] = cc->mask[nativepos];
170     }
171   }
172 
173   // Always 32x32 because that's what is recommended in the Qt
174   // documentation for QCursor.  At least WinNT 4 will give us
175   // "interesting" bugs for other cursor sizes.
176 #if QT_VERSION >= 0x040000
177   QBitmap bitmap = QBitmap::fromData(QSize(32, 32), cursorbitmap, QImage::Format_MonoLSB);
178   QBitmap mask = QBitmap::fromData(QSize(32, 32), cursormask, QImage::Format_MonoLSB);
179 #else
180   QBitmap bitmap(32, 32, cursorbitmap, true);
181   QBitmap mask(32, 32, cursormask, true);
182 #endif
183 
184   // Sanity checks.
185   assert(bitmap.size().width() > 0 && bitmap.size().height() > 0);
186   assert(bitmap.size() == mask.size());
187   assert(bitmap.depth() == 1);
188   assert(mask.depth() == 1);
189 
190   QCursor * c = new QCursor(bitmap, mask, cc->hotspot[0], cc->hotspot[1]);
191   SoQtComponentP::cursordict->enter((uintptr_t)cc, c);
192   return c;
193 }
194 
195 // SLOT for when the user clicks/selects window decoration close.
196 void
widgetClosed(void)197 SoQtComponentP::widgetClosed(void)
198 {
199   if (this->closeCB) { this->closeCB(this->closeCBdata, PUBLIC(this)); }
200 }
201 
202 // Helps us detect changes in size (base widget and parent widget)
203 // and visibility status.
204 bool
eventFilter(QObject * obj,QEvent * e)205 SoQtComponentP::eventFilter(QObject * obj, QEvent * e)
206 {
207 #if 0 // debug
208   static const char eventnaming[][50] = {
209     "None", // 0
210     "Timer",
211     "MouseButtonPress",
212     "MouseButtonRelease",
213     "MouseButtonDblClick",
214     "MouseMove",
215     "KeyPress",
216     "KeyRelease",
217     "FocusIn",
218     "FocusOut",
219     "Enter",
220     "Leave",
221     "Paint",
222     "Move",
223     "Resize",
224     "Create",
225     "Destroy",
226     "Show",
227     "Hide",
228     "Close",
229     "Quit", // 20
230     "*error*", "*error*", "*error*", "*error*", "*error*",
231     "*error*", "*error*", "*error*", "*error*",
232     "Accel", // 30
233     "Wheel",
234     "AccelAvailable", // 32
235     "*error*", "*error*", "*error*", "*error*",
236     "*error*", "*error*", "*error*",
237     "Clipboard", // 40
238     "*error*", "*error*", "*error*", "*error*", "*error*",
239     "*error*", "*error*", "*error*", "*error*",
240     "SockAct", // 50
241     "AccelOverride", "*error*", "*error*", "*error*", "*error*",
242     "*error*", "*error*", "*error*", "*error*",
243     "DragEnter", // 60
244     "DragMove",
245     "DragLeave",
246     "Drop",
247     "DragResponse", // 64
248     "*error*", "*error*", "*error*", "*error*", "*error*",
249     "ChildInserted", // 70
250     "ChildRemoved",
251     "LayoutHint", // 72
252     "*error*", "*error*", "*error*", "*error*", "*error*",
253     "*error*", "*error*",
254     "ActivateControl", // 80
255     "DeactivateControl"
256   };
257 
258   SoDebugError::postInfo("SoQtComponent::eventFilter", "%p: %s",
259                          obj, eventnaming[e->type()]);
260 #endif // debug
261 
262   // The parent widget could receive resize events while we have still
263   // not completed the set up of our own widget, so we need to check
264   // for a NULL pointer.
265   //
266   // (One way to reproduce such a case: set up two SoQtExaminerViewer
267   // instances in an MDI workspace, first one, which is then
268   // maximized, then the second one instantiated will cause a crash.)
269   if (this->widget == NULL) { return false; }
270 
271   if (e->type() == QEvent::Resize) {
272     QResizeEvent * r = (QResizeEvent *)e;
273 
274     if (obj == (QObject *)this->parent) {
275 #if SOQTCOMP_RESIZE_DEBUG  // debug
276       SoDebugError::postInfo("SoQtComponent::eventFilter",
277                              "resize on parent (%p) to %p: (%d, %d)",
278                              this->parent, this->widget,
279                              r->size().width(), r->size().height());
280 #endif // debug
281       this->widget->resize(r->size());
282       this->storesize.setValue(r->size().width(), r->size().height());
283       PUBLIC(this)->sizeChanged(this->storesize);
284     }
285     else if (obj == (QObject *)this->widget) {
286       this->storesize.setValue(r->size().width(), r->size().height());
287       PUBLIC(this)->sizeChanged(this->storesize);
288     }
289   }
290   // Detect visibility changes.
291   else if (obj == this->widget &&
292            (e->type() == QEvent::Show || e->type() == QEvent::Hide)) {
293     if (this->visibilitychangeCBs) {
294       for (int i=0; i < this->visibilitychangeCBs->getLength()/2; i++) {
295         SoQtComponentVisibilityCB * cb =
296           (SoQtComponentVisibilityCB *)(*(this->visibilitychangeCBs))[i*2+0];
297         void * userdata = (*(this->visibilitychangeCBs))[i*2+1];
298         cb(userdata, e->type() == QEvent::Show ? true : false);
299       }
300     }
301   }
302 
303   // It would seem more sensible that we should trigger on
304   // QEvent::Create than on QEvent::Show for the afterRealizeHook()
305   // method, but the QEvent::Create type is not yet used in Qt (as of
306   // version 2.2.2 at least) -- it has just been reserved for future
307   // releases.
308   if (e->type() == QEvent::Show && !this->realized) {
309     this->realized = true;
310     PUBLIC(this)->afterRealizeHook();
311   }
312 
313   return false;
314 }
315 
316 #endif // DOXYGEN_SKIP_THIS
317 
318 #undef PUBLIC
319 #undef PRIVATE
320 
321 #define PRIVATE(obj) ((obj)->pimpl)
322 #define PUBLIC(obj) ((obj)->pub)
323 
324 // *************************************************************************
325 
326 SOQT_OBJECT_ABSTRACT_SOURCE(SoQtComponent);
327 
328 // documented in common/SoGuiComponentCommon.cpp.in.
329 void
initClasses(void)330 SoQtComponent::initClasses(void)
331 {
332   SoQtComponent::initClass();
333   SoQtGLWidget::initClass();
334   SoQtRenderArea::initClass();
335   SoQtViewer::initClass();
336   SoQtFullViewer::initClass();
337   SoQtExaminerViewer::initClass();
338   SoQtPlaneViewer::initClass();
339   SoQtConstrainedViewer::initClass();
340 #if 0  // TMP DISABLED: walkviewer not properly implemented yet. 20020624 mortene.
341   SoQtWalkViewer::initClass();
342 #endif // TMP DISABLED
343   SoQtFlyViewer::initClass();
344 }
345 
346 // *************************************************************************
347 
348 // documented in common/SoGuiComponentCommon.cpp.in.
SoQtComponent(QWidget * const parent,const char * const name,const SbBool embed)349 SoQtComponent::SoQtComponent(QWidget * const parent,
350                              const char * const name,
351                              const SbBool embed)
352 {
353   PRIVATE(this) = new SoQtComponentP(this);
354 
355   PRIVATE(this)->realized = false;
356   PRIVATE(this)->shelled = false;
357   PRIVATE(this)->widget = NULL;
358   PRIVATE(this)->parent = parent;
359   PRIVATE(this)->closeCB = NULL;
360   PRIVATE(this)->closeCBdata = NULL;
361   PRIVATE(this)->visibilitychangeCBs = NULL;
362   PRIVATE(this)->fullscreen = false;
363 
364   this->setClassName("SoQtComponent");
365 
366   PRIVATE(this)->storesize.setValue(-1, -1);
367 #ifdef Q_OS_MAC
368   PRIVATE(this)->windowsize.setValue(-1, -1);
369 #endif
370 
371   SoAny::si()->addInternalFatalErrorHandler(SoQtComponentP::fatalerrorHandler,
372                                             PRIVATE(this));
373 
374   PRIVATE(this)->widgetname = (name ? name :
375 			       this->getDefaultWidgetName());
376 
377   if (!parent || !embed) {
378 #if QT_VERSION >= 0x040000
379     PRIVATE(this)->parent = new QMainWindow();
380     PRIVATE(this)->parent->setObjectName(PRIVATE(this)->widgetname);
381 #else
382     PRIVATE(this)->parent = (QWidget *) new QMainWindow(NULL, name);
383 #endif
384     PRIVATE(this)->embedded = false;
385     PRIVATE(this)->shelled = true;
386   }
387   else {
388     PRIVATE(this)->parent = parent;
389     PRIVATE(this)->embedded = true;
390   }
391 
392   PRIVATE(this)->parent->installEventFilter(PRIVATE(this));
393 }
394 
395 // documented in common/SoGuiComponentCommon.cpp.in.
~SoQtComponent()396 SoQtComponent::~SoQtComponent()
397 {
398   if (PRIVATE(this)->widget) {
399     this->unregisterWidget(PRIVATE(this)->widget);
400   }
401 
402   delete PRIVATE(this)->visibilitychangeCBs;
403 
404   // Had to disable this code, as it caused seg-faults.  FIXME: does
405   // that mean there's a leak now? 20020524 mortene.
406 #if 0
407   // If we've got a toplevel widget on our hands it won't
408   // automatically be deallocated (there's no real parent widget).
409   if (PRIVATE(this)->widget && !PRIVATE(this)->widget->parentWidget()) delete PRIVATE(this)->widget;
410 #endif
411 
412   delete PRIVATE(this);
413 }
414 
415 // FIXME: the visibility callback handling could be done in common
416 // code. 20020624 mortene.
417 
418 // documented in common/SoGuiComponentCommon.cpp.in.
419 void
addVisibilityChangeCallback(SoQtComponentVisibilityCB * const func,void * const user)420 SoQtComponent::addVisibilityChangeCallback(SoQtComponentVisibilityCB * const func,
421                                            void * const user)
422 {
423   if (! PRIVATE(this)->visibilitychangeCBs)
424     PRIVATE(this)->visibilitychangeCBs = new SbPList;
425 
426   PRIVATE(this)->visibilitychangeCBs->append((void *) func);
427   PRIVATE(this)->visibilitychangeCBs->append(user);
428 }
429 
430 // documented in common/SoGuiComponentCommon.cpp.in.
431 void
removeVisibilityChangeCallback(SoQtComponentVisibilityCB * const func,void * const data)432 SoQtComponent::removeVisibilityChangeCallback(SoQtComponentVisibilityCB * const func,
433                                               void * const data)
434 {
435 #if SOQT_DEBUG
436   if (! PRIVATE(this)->visibilitychangeCBs) {
437     SoDebugError::postWarning("SoQtComponent::removeVisibilityChangeCallback",
438                               "empty callback list");
439     return;
440   }
441 #endif // SOQT_DEBUG
442 
443   int idx = PRIVATE(this)->visibilitychangeCBs->find((void *) func);
444   if (idx != -1) {
445     PRIVATE(this)->visibilitychangeCBs->remove(idx);
446     PRIVATE(this)->visibilitychangeCBs->remove(idx);
447   }
448 
449 #if SOQT_DEBUG
450   if (idx == -1) {
451     SoDebugError::postWarning("SoQtComponent::removeVisibilityChangeCallback",
452                               "tried to remove non-existant callback");
453     return;
454   }
455 #endif // SOQT_DEBUG
456 }
457 
458 // documented in common/SoGuiComponentCommon.cpp.in.
459 void
setClassName(const char * const name)460 SoQtComponent::setClassName(const char * const name)
461 {
462   PRIVATE(this)->classname = name;
463 }
464 
465 // *************************************************************************
466 
467 // documented in common/SoGuiComponentCommon.cpp.in.
468 void
setBaseWidget(QWidget * widget)469 SoQtComponent::setBaseWidget(QWidget * widget)
470 {
471   QString iconText = this->getDefaultIconTitle();
472   QString widgetName = PRIVATE(this)->widgetname;
473 
474   assert(widget);
475 
476   if (PRIVATE(this)->widget) {
477 #if QT_VERSION >= 0x040000
478     iconText = (PRIVATE(this)->widget->windowIconText().isEmpty() ?
479 		iconText :
480 		PRIVATE(this)->widget->windowIconText());
481     widgetName = (PRIVATE(this)->widget->objectName().isEmpty() ?
482 		  widgetName :
483 		  PRIVATE(this)->widget->objectName());
484 #else
485     iconText = (PRIVATE(this)->widget->iconText().isEmpty() ?
486 		iconText :
487 		PRIVATE(this)->widget->iconText());
488     widgetName = (PRIVATE(this)->widget->name() == "" ?
489 		  widgetName :
490 		  QString(PRIVATE(this)->widget->name()));
491 #endif
492 
493     PRIVATE(this)->widget->removeEventFilter(PRIVATE(this));
494     this->unregisterWidget(PRIVATE(this)->widget);
495   }
496 
497   PRIVATE(this)->widget = widget;
498   this->registerWidget(PRIVATE(this)->widget);
499 
500 #if 0 // debug
501   SoDebugError::postInfo("SoQtComponent::setBaseWidget",
502                          "widget: %p, parent: %p", w, PRIVATE(this)->parent);
503 #endif // debug
504 
505   if (!PRIVATE(this)->parent || PRIVATE(this)->parent->isTopLevel()) {
506 #if QT_VERSION >= 0x040000
507     if (PRIVATE(this)->widget->windowTitle() == "") {
508 #else
509     if (PRIVATE(this)->widget->caption() == "") {
510 #endif
511       this->setTitle(this->getDefaultTitle());
512     }
513 
514 #if QT_VERSION >= 0x040000
515     SoQt::getShellWidget(this->getWidget())->setWindowIconText(iconText);
516 #else
517     SoQt::getShellWidget(this->getWidget())->setIconText(iconText);
518 #endif
519   }
520 #if QT_VERSION >= 0x040000
521   PRIVATE(this)->widget->setObjectName(widgetName);
522 #else
523   PRIVATE(this)->widget->setName(widgetName);
524 #endif
525   // Need this to auto-detect resize events.
526   PRIVATE(this)->widget->installEventFilter(PRIVATE(this));
527 
528   QObject::connect(PRIVATE(this)->widget, SIGNAL(destroyed()),
529                    PRIVATE(this), SLOT(widgetClosed()));
530 }
531 
532 // *************************************************************************
533 
534 /*!
535   This function \e must be called by subclasses after the component's
536   widget has been otherwise initialized.
537 */
538 
539 /*
540 void
541 SoQtComponent::subclassInitialized(
542   void)
543 {
544 #if SOQT_DEBUG
545   if(!PRIVATE(this)->widget) {
546     SoDebugError::postWarning("SoQtComponent::subclassInitialized",
547                               "Called while no QWidget has been set.");
548     return;
549   }
550 #endif // SOQT_DEBUG
551 }
552 */
553 
554 // *************************************************************************
555 
556 // documented in common/SoGuiComponentCommon.cpp.in.
557 void
558 SoQtComponent::show(void)
559 {
560   if(SOQT_DEBUG && !PRIVATE(this)->widget) { // debug
561     SoDebugError::postWarning("SoQtComponent::show",
562                               "Called while no QWidget has been set.");
563     return;
564   }
565 
566   if (SOQTCOMP_RESIZE_DEBUG) {  // debug
567     SoDebugError::postInfo("SoQtComponent::show-1",
568                            "resizing %p: (%d, %d)",
569                            PRIVATE(this)->widget,
570                            PRIVATE(this)->storesize[0],
571                            PRIVATE(this)->storesize[1]);
572   }
573 
574   if (PRIVATE(this)->shelled) {
575     PRIVATE(this)->parent->resize(PRIVATE(this)->storesize[0],
576                                   PRIVATE(this)->storesize[1]);
577   }
578   else {
579     PRIVATE(this)->widget->resize(PRIVATE(this)->storesize[0],
580                                   PRIVATE(this)->storesize[1]);
581   }
582 
583   if (SOQTCOMP_RESIZE_DEBUG) {  // debug
584     SoDebugError::postInfo("SoQtComponent::show-2",
585                            "resized %p: (%d, %d)",
586                            PRIVATE(this)->widget,
587                            PRIVATE(this)->widget->size().width(),
588                            PRIVATE(this)->widget->size().height());
589   }
590 
591   PRIVATE(this)->widget->topLevelWidget()->show();
592 
593   if (SOQTCOMP_RESIZE_DEBUG) {  // debug
594     SoDebugError::postInfo("SoQtComponent::show-3",
595                            "showed %p: (%d, %d)",
596                            PRIVATE(this)->widget,
597                            PRIVATE(this)->widget->size().width(),
598                            PRIVATE(this)->widget->size().height());
599   }
600 
601   PRIVATE(this)->widget->raise();
602 
603   // Workaround for a bug Qt version 3.3.0 -3.3.3 on Mac OS X.
604   // Without this, a standalone examinerviewer gets displayed as a
605   // completely blank white window. The problem seems to be related to
606   // a paint event "getting lost" somewhere (it's registered in
607   // SoQtComponent but never makes it to SoQtGLWidget.)  Note that
608   // this workaround is a weird thing in itself: calling raise() twice
609   // should not make a difference, since raise() puts the widget to
610   // the top of the stack so that "the widget will be visually in
611   // front of any overlapping sibling widgets".
612 #if (defined Q_OS_MAC && QT_VERSION >= 0x030300 && QT_VERSION < 0x030304)
613   PRIVATE(this)->widget->raise();
614 #endif // Q_OS_MAC
615 
616   if (SOQTCOMP_RESIZE_DEBUG) {  // debug
617     SoDebugError::postInfo("SoQtComponent::show-4",
618                            "raised %p: (%d, %d)",
619                            PRIVATE(this)->widget,
620                            PRIVATE(this)->widget->size().width(),
621                            PRIVATE(this)->widget->size().height());
622   }
623 
624   this->sizeChanged(PRIVATE(this)->storesize);
625 }
626 
627 // documented in common/SoGuiComponentCommon.cpp.in.
628 void
629 SoQtComponent::hide(void)
630 {
631 #if SOQT_DEBUG
632   if(!PRIVATE(this)->widget) {
633     SoDebugError::postWarning("SoQtComponent::hide",
634                               "Called while no QWidget has been set.");
635     return;
636   }
637 #endif // SOQT_DEBUG
638 
639   if (PRIVATE(this)->widget) { PRIVATE(this)->widget->topLevelWidget()->hide(); }
640 }
641 
642 // *************************************************************************
643 
644 // documented in common/SoGuiComponentCommon.cpp.in.
645 SbBool
646 SoQtComponent::isVisible(void)
647 {
648   if (! PRIVATE(this)->widget) { return false; }
649   return PRIVATE(this)->widget->isVisible();
650 }
651 
652 // documented in common/SoGuiComponentCommon.cpp.in.
653 QWidget *
654 SoQtComponent::getWidget(void) const
655 {
656   return PRIVATE(this)->widget;
657 }
658 
659 // documented in common/SoGuiComponentCommon.cpp.in.
660 QWidget *
661 SoQtComponent::getBaseWidget(void) const
662 {
663   return PRIVATE(this)->widget;
664 }
665 
666 // documented in common/SoGuiComponentCommon.cpp.in.
667 SbBool
668 SoQtComponent::isTopLevelShell(void) const
669 {
670 #if SOQT_DEBUG && 0
671   if (! PRIVATE(this)->widget) {
672     SoDebugError::postWarning("SoQtComponent::isTopLevelShell",
673                               "Called while no QWidget has been set.");
674     return false;
675   }
676 #endif // SOQT_DEBUG
677   return PRIVATE(this)->embedded ? false : true;
678 }
679 
680 // documented in common/SoGuiComponentCommon.cpp.in.
681 QWidget *
682 SoQtComponent::getParentWidget(void) const
683 {
684   return PRIVATE(this)->parent;
685 }
686 
687 // documented in common/SoGuiComponentCommon.cpp.in.
688 void
689 SoQtComponent::setTitle(const char * const title)
690 {
691   if (this->getWidget()) {
692     QWidget * toplevel = this->getWidget();
693     while (!toplevel->isTopLevel() ) {
694       toplevel = toplevel->parentWidget();
695     }
696     if (toplevel) {
697 #if QT_VERSION >= 0x040000
698       toplevel->setWindowTitle(title);
699 #else
700       toplevel->setCaption(title);
701 #endif
702     }
703   }
704 }
705 
706 // documented in common/SoGuiComponentCommon.cpp.in.
707 const char *
708 SoQtComponent::getTitle(void) const
709 {
710   const char * result = "";
711 
712   if (this->getWidget()) {
713     QWidget * toplevel = this->getWidget();
714     while (!toplevel->isTopLevel() ) {
715       toplevel = toplevel->parentWidget();
716     }
717     if (toplevel) {
718 #if QT_VERSION >= 0x040000
719       result = toplevel->windowTitle().toUtf8().constData();
720 #else
721       // Qt3 featured an implicit operator const char * () const
722       result = toplevel->caption();
723 #endif
724     }
725   }
726 
727   return result;
728 }
729 
730 // documented in common/SoGuiComponentCommon.cpp.in.
731 void
732 SoQtComponent::setIconTitle(const char * const title)
733 {
734   QWidget * w = this->getWidget();
735   if (w && this->isTopLevelShell()) {
736 #if QT_VERSION >= 0x040000
737     SoQt::getShellWidget(w)->setWindowIconText(title);
738 #else
739     SoQt::getShellWidget(w)->setIconText(title);
740 #endif
741   }
742 }
743 
744 // documented in common/SoGuiComponentCommon.cpp.in.
745 const char *
746 SoQtComponent::getIconTitle(void) const
747 {
748   const char * result = nullstring;
749 
750   QWidget * w = this->getWidget();
751   if (w && this->isTopLevelShell()) {
752 #if QT_VERSION >= 0x040000
753     QString iconText = SoQt::getShellWidget(w)->windowIconText();
754 #else
755     QString iconText = SoQt::getShellWidget(w)->iconText();
756 #endif
757 
758     if (!iconText.isEmpty()) {
759 #if QT_VERSION >= 0x040000
760       result = iconText.toUtf8().constData();
761 #else
762     // Qt3 featured and implicit operator const char * () const
763       result = iconText;
764 #endif
765     }
766   }
767 
768   return result;
769 }
770 
771 // documented in common/SoGuiComponentCommon.cpp.in.
772 const char *
773 SoQtComponent::getWidgetName(void) const
774 {
775   const char * result = nullstring;
776 
777   if (!PRIVATE(this)->widgetname.isEmpty()) {
778 #if QT_VERSION >= 0x040000
779     result = PRIVATE(this)->widgetname.toUtf8().constData();
780 #else
781     // Qt3 featured an implicit operator const char * () const
782     result = PRIVATE(this)->widgetname;
783 #endif
784   }
785 
786   return result;
787 }
788 
789 // documented in common/SoGuiComponentCommon.cpp.in.
790 const char *
791 SoQtComponent::getClassName(void) const
792 {
793   const char * result = nullstring;
794 
795   if (!PRIVATE(this)->classname.isEmpty()) {
796 #if QT_VERSION >= 0x040000
797     result = PRIVATE(this)->classname.toUtf8().constData();
798 #else
799     // Qt3 featured and implicit operator const char * () const
800     result = PRIVATE(this)->classname;
801 #endif
802   }
803 
804   return result;
805 }
806 
807 // *************************************************************************
808 
809 // FIXME: the resize handling in SoQtComponent and derived classes is
810 // really horrible and has provided us with heaps of "interesting"
811 // bugs. Should clean it up properly.
812 //
813 // One important part of the clean-up is to make a decent set of test
814 // cases to check with (and to later use for testing for
815 // regressions). The tests should at least cover these usage contexts
816 // for SoQt component classes:
817 //
818 //
819 //   * an SoQt-component viewer embedded in other Qt-widgets
820 //   * a top-level (ie "free window") viewer
821 //
822 //   * an embedded SoQtRenderArea
823 //   * a top-level SoQtRenderArea
824 //
825 //   * viewers with and without sidebar- or bottom-decorations
826 //
827 //   * changing size for all the above cases by user interaction with
828 //     window manager decorations
829 //
830 //   * changing size programmatically with SoQtComponent::setSize(),
831 //     before and after component realization
832 //
833 // And all the above should be tested with both Qt v2 and Qt v3, on
834 // both a UNIX/X11 system and an MSWindows system (Qt is supposed to
835 // behave exactly the same on both platforms, but that's not always
836 // the case).
837 //
838 // 20021024 mortene.
839 
840 // documented in common/SoGuiComponentCommon.cpp.in.
841 void
842 SoQtComponent::setSize(const SbVec2s size)
843 {
844 #if SOQT_DEBUG
845   if((size[0] <= 0) || (size[1] <= 0)) {
846     SoDebugError::postWarning("SoQtComponent::setSize",
847                               "Invalid size setting: <%d, %d>.",
848                               size[0], size[1]);
849     return;
850   }
851 #endif // SOQT_DEBUG
852 
853 #if SOQTCOMP_RESIZE_DEBUG  // debug
854   SoDebugError::postInfo("SoQtComponent::setSize",
855                          "resize %p: (%d, %d)",
856                          PRIVATE(this)->widget,
857                          size[0], size[1]);
858 #endif // debug
859   const SbBool yetbuilt = (this->getWidget() != NULL);
860   if (yetbuilt) {
861     QWidget * shell = this->getShellWidget();
862     if (shell) { shell->resize(size[0], size[1]); }
863   }
864   PRIVATE(this)->storesize = size;
865   this->sizeChanged(size);
866 }
867 
868 // documented in common/SoGuiComponentCommon.cpp.in.
869 SbVec2s
870 SoQtComponent::getSize(void) const
871 {
872   return PRIVATE(this)->storesize;
873 }
874 
875 // documented in common/SoGuiComponentCommon.cpp.in.
876 void
877 SoQtComponent::sizeChanged(const SbVec2s & size)
878 {
879   // The default implementation does nothing.
880 }
881 
882 // *************************************************************************
883 
884 // documented in common/SoGuiComponentCommon.cpp.in.
885 void
886 SoQtComponent::setWindowCloseCallback(SoQtComponentCB * const func,
887                                       void * const data)
888 {
889   PRIVATE(this)->closeCB = func;
890   PRIVATE(this)->closeCBdata = data;
891 }
892 
893 // *************************************************************************
894 
895 // Documented in common/SoGuiComponentCommon.cpp.in.
896 void
897 SoQtComponent::afterRealizeHook(void)
898 {
899 }
900 
901 // documented in common/SoGuiComponentCommon.cpp.in.
902 SbBool
903 SoQtComponent::setFullScreen(const SbBool onoff)
904 {
905   if (onoff == PRIVATE(this)->fullscreen) { return true; }
906 
907   // FIXME: hmm.. this looks suspicious. Shouldn't we just return
908   // FALSE if the (base)widget is not a shellwidget? 20010817 mortene.
909   QWidget * w = this->getShellWidget();
910   if (w == NULL) w = this->getParentWidget();
911   if (w == NULL) w = this->getWidget();
912   if (!w) { return false; }
913 
914   // FIXME: note that the compile-time binding technique against
915   // QWidget::showFullScreen() doesn't work very well with the idea
916   // that we'll compile a distribution version of SoQt against the
917   // lowest common denominator of Qt versions we support (ie v2.0.0),
918   // as that means SoQtComponent::setFullScreen() will never work as
919   // expected in the pre-compiled distro version we make.  20010608 mortene.
920 
921   // prefer setWindowState() to showFullScreen() since
922   // setWindowState() will preserve other window flags/states.
923 #if HAVE_QWIDGET_SETWINDOWSTATE
924   if (onoff) {
925 #ifdef Q_OS_MAC
926     // Qt/Mac does not remember the window size when going fullscreen,
927     // so when going back to windowed mode, the window will be 1x1 pixels
928     // small -> we have to store the window size ourselves...
929     // FIXME: We should really figure out why this happens, and fix the
930     // problem, but I guess having a workaround is better than just having
931     // the window "disappear"... kyrah 20041118
932     PRIVATE(this)->windowsize[0] = w->size().width();
933     PRIVATE(this)->windowsize[1] = w->size().height();
934 #endif
935     w->setWindowState(w->windowState() | Qt::WindowFullScreen);
936 #ifdef Q_OS_MAC
937     // Explicit show needed for Mac OS X, otherwise the window "vanishes"
938     w->show();
939 #endif
940   } else {
941     w->setWindowState(w->windowState() & ~Qt::WindowFullScreen);
942 #ifdef Q_OS_MAC
943     w->resize(QSize(PRIVATE(this)->windowsize[0], PRIVATE(this)->windowsize[1]));
944     w->show();
945 #endif
946   }
947 #elif HAVE_QWIDGET_SHOWFULLSCREEN
948   if (onoff) w->showFullScreen();
949   else w->showNormal();
950 #else // HAVE_QWIDGET_SHOWFULLSCREEN
951   SoDebugError::postWarning("SoQtComponent::setFullScreen",
952                             "SoQt was compiled against version %s of Qt, "
953                             "which doesn't have the "
954                             "QWidget::showFullScreen() method",
955                             QT_VERSION_STR);
956   return false;
957 #endif // !HAVE_QWIDGET_SHOWFULLSCREEN && !HAVE_QWIDGET_SETWINDOWSTATE
958   PRIVATE(this)->fullscreen = onoff;
959   return true;
960 }
961 
962 // documented in common/SoGuiComponentCommon.cpp.in.
963 SbBool
964 SoQtComponent::isFullScreen(void) const
965 {
966   return PRIVATE(this)->fullscreen;
967 }
968 
969 // documented in common/SoGuiComponentCommon.cpp.in.
970 void
971 SoQtComponent::setComponentCursor(const SoQtCursor & cursor)
972 {
973   SoQtComponent::setWidgetCursor(this->getWidget(), cursor);
974 }
975 
976 // documented in common/SoGuiComponentCommon.cpp.in.
977 void
978 SoQtComponent::setWidgetCursor(QWidget * w, const SoQtCursor & cursor)
979 {
980   // FIXME: as this function is called all the time when the cursor is
981   // grabbed by the window under X11, we should really compare with
982   // the previous cursor before doing anything, to avoid spending
983   // unnecessary clockcycles during animation. 20011203 mortene.
984 
985   if (cursor.getShape() == SoQtCursor::CUSTOM_BITMAP) {
986 
987     // Custom cursors do not work correctly in Qt/Mac versions 3.1.0
988     // and 3.1.1 - the displayed graphics look totally wrong.
989     //
990     // The bug has been confirmed fixed in Qt 3.1.2, but instead they
991     // introduced another bug: when you click on the widget, the
992     // cursor disappears. The Trolls have acknowledged that this is a
993     // bug, and that it will be fixed in 3.1.3.
994 #if defined Q_OS_MAC && ((QT_VERSION == 0x030100) || (QT_VERSION == 0x030101) || (QT_VERSION == 0x030102))
995     w->setCursor(QCursor(Qt::arrowCursor));
996     // spit out a warning that this is a Qt/Mac bug, not an SoQt problem
997     static SbBool warningdisplayed = false;
998     if (!warningdisplayed) {
999       const char * env = SoAny::si()->getenv("SOQT_NO_QTMAC_BUG_WARNINGS");
1000       if (!env || !atoi(env))
1001         SoDebugError::postWarning("SoQtComponent::setWidgetCursor",
1002                                   "\nThis version of Qt/Mac contains a bug "
1003                                   "that makes it impossible to use custom\n"
1004                                   "cursors. Warnings about Qt/Mac bugs "
1005                                   "can be turned off permanently by setting\n"
1006                                   "the environment variable "
1007                                   "SOQT_NO_QTMAC_BUG_WARNINGS=1.\n");
1008       warningdisplayed = true;
1009     }
1010 #else
1011     const SoQtCursor::CustomCursor * cc = &cursor.getCustomCursor();
1012     w->setCursor(*SoQtComponentP::getNativeCursor(cc));
1013 #endif
1014   }
1015   else {
1016     switch (cursor.getShape()) {
1017     case SoQtCursor::DEFAULT:
1018       w->setCursor(QCursor(Qt::ArrowCursor));
1019       break;
1020 
1021     case SoQtCursor::BUSY:
1022       w->setCursor(QCursor(Qt::WaitCursor));
1023       break;
1024 
1025     case SoQtCursor::CROSSHAIR:
1026       w->setCursor(QCursor(Qt::CrossCursor));
1027       break;
1028 
1029     case SoQtCursor::UPARROW:
1030       w->setCursor(QCursor(Qt::UpArrowCursor));
1031       break;
1032 
1033     default:
1034       assert(false && "unsupported cursor shape type");
1035       break;
1036     }
1037   }
1038 
1039   // QWidget::setCursor() doesn't have an immediate effect with Qt/Win
1040   // if the cursor is already positioned over the widget -- and won't
1041   // change until the cursor position is moved. This is at least true
1042   // for Qt version 2.1.1.
1043   //
1044   // The code below is a simple work-around for this problem -- we
1045   // just offset the cursor position by a single pixel. There might be
1046   // a better way to get around the problem, but this seems easy
1047   // enough.
1048   //                                                        mortene
1049 
1050   // FIXME: had to disable this as it didn't work under X11 (we get
1051   // continuous calls to this function, which means the mousecursor is
1052   // quickly pushed off the screen..). 20011203 mortene.
1053 #if 0
1054   QPoint p = w->cursor().pos();
1055   p.setX(p.x() + 1);
1056   w->cursor().setPos(p);
1057 #endif // disabled
1058 }
1059 
1060 // *************************************************************************
1061 
1062 #undef PRIVATE
1063 #undef PUBLIC
1064