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 /*!
34 \class QtNativePopupMenu Inventor/Qt/widgets/QtNativePopupMenu.h
35 \brief The QtNativePopupMenu class implements a common interface for popup
36 menu management for all the Coin GUI toolkit libraries.
37 */
38
39 // *************************************************************************
40
41 // FIXME: create a new Qt4NativePopupMenu. There are just too
42 // many differences between Qt3 menu handling and Qt4 menu handling.
43
44 #include <qmetaobject.h>
45
46 // *************************************************************************
47
48 // Take care of class name incompatibilities between Qt 3 and Qt 4.
49
50 #if QT_VERSION < 0x040000 // pre Qt 4
51 #include <qaction.h>
52 #include <qpopupmenu.h>
53 #define QPOPUPMENU_CLASS QPopupMenu
54 #else // Qt 4.0.0+
55 #include <QMenu>
56 #define QPOPUPMENU_CLASS QMenu
57 #endif // Qt 4.0.0+
58
59 // ************************************************************************
60
61 #include <Inventor/SoLists.h>
62 #include <Inventor/errors/SoDebugError.h>
63
64 #include <Inventor/Qt/widgets/QtNativePopupMenu.h>
65 #include <Inventor/Qt/widgets/moc_QtNativePopupMenu.icc>
66
67 #include <soqtdefs.h>
68
69 // *************************************************************************
70
71 struct MenuRecord {
72 int menuid;
73 char * name;
74 char * title;
75 QAction * action;
76 QPOPUPMENU_CLASS * menu;
77 QPOPUPMENU_CLASS * parent;
78 }; // struct MenuRecord
79
80 struct ItemRecord {
81 int itemid;
82 int flags;
83 char * name;
84 char * title;
85 QAction * action;
86 QPOPUPMENU_CLASS * parent;
87 }; // struct ItemRecord
88
89 #define ITEM_MARKED 0x0001
90 #define ITEM_SEPARATOR 0x0002
91 #define ITEM_ENABLED 0x0004
92
93 // *************************************************************************
94
95 /*!
96 The constructor.
97 */
98
QtNativePopupMenu(void)99 QtNativePopupMenu::QtNativePopupMenu(
100 void)
101 {
102 this->menus = new SbPList;
103 this->items = new SbPList;
104 } // QtNativePopupMenu()
105
106 /*!
107 Destructor.
108 */
109
~QtNativePopupMenu()110 QtNativePopupMenu::~QtNativePopupMenu()
111 {
112 const int numMenus = this->menus->getLength();
113
114 int i;
115 for (i = 0; i < numMenus; i++) {
116 MenuRecord * rec = (MenuRecord *) (*this->menus)[i];
117 delete [] rec->name;
118 delete [] rec->title;
119 // We used to delete only the root menu, and trust that Qt cleaned
120 // up all submenus, but after doing some testing with huge menus,
121 // we found that Qt does _not_ automatically delete submenus when
122 // the parent is destructed. We therefore delete all menus
123 // manually here. pederb, 2005-12-05
124 delete rec->menu;
125 delete rec;
126 }
127
128 const int numItems = this->items->getLength();
129 for (i = 0; i < numItems; i++) {
130 ItemRecord * rec = (ItemRecord *) (*this->items)[i];
131 delete [] rec->name;
132 delete [] rec->title;
133 delete rec;
134 }
135
136 delete this->menus;
137 delete this->items;
138
139 } // ~QtNativePopupMenu()
140
141 // *************************************************************************
142
143 /*!
144 */
145
146 int
newMenu(const char * name,int menuid)147 QtNativePopupMenu::newMenu(
148 const char * name,
149 int menuid)
150 {
151 // FIXME: this function is the same in the other So-libraries --
152 // move to common abstraction layer SoGuiPopupMenu of
153 // possible. 20031012 mortene.
154
155 int id = menuid;
156 if (id == -1) {
157 id = 1;
158 while (this->getMenuRecord(id) != NULL) id++;
159 } else {
160 assert(this->getMenuRecord(id) == NULL &&
161 "requested menuid already taken");
162 }
163
164 // id contains ok ID
165 MenuRecord * rec = this->createMenuRecord(name);
166 rec->menuid = id;
167 this->menus->append((void *) rec);
168 return id;
169 } // newMenu()
170
171 /*!
172 */
173
174 int
getMenu(const char * name)175 QtNativePopupMenu::getMenu(
176 const char * name)
177 {
178 const int numMenus = this->menus->getLength();
179 int i;
180 for (i = 0; i < numMenus; i++) {
181 MenuRecord * rec = (MenuRecord *) (*this->menus)[i];
182 if (strcmp(rec->name, name) == 0)
183 return rec->menuid;
184 }
185 return -1;
186 } // getMenu()
187
188 /*!
189 */
190
191 void
setMenuTitle(int menuid,const char * title)192 QtNativePopupMenu::setMenuTitle(
193 int menuid,
194 const char * title)
195 {
196 MenuRecord * rec = this->getMenuRecord(menuid);
197 assert(rec && "no such menu");
198 delete [] rec->title;
199 rec->title = strcpy(new char [strlen(title)+1], title);
200 #if QT_VERSION >= 200
201 if (rec->parent) {
202 //rec->parent->actionAt(QPoint(rec->menuid,0))->setText(rec->title);
203 getMenuRecord(rec->menuid)->action->setText(rec->title);
204 }
205 #else // Qt version < 2.0.0
206 // This QMenuData::changeItem() method is being obsoleted from Qt
207 // from version 2.0.0 onwards.
208 if (rec->parent)
209 rec->parent->changeItem(rec->title, rec->menuid);
210 #endif // Qt version < 2.0.0
211 } // setMenuTitle()
212
213 /*!
214 */
215
216 const char *
getMenuTitle(int menuid)217 QtNativePopupMenu::getMenuTitle(
218 int menuid)
219 {
220 MenuRecord * rec = this->getMenuRecord(menuid);
221 assert(rec && "no such menu");
222 return rec->title;
223 } // getMenuTitle()
224
225 // *************************************************************************
226
227 /*!
228 */
229
230 int
newMenuItem(const char * name,int itemid)231 QtNativePopupMenu::newMenuItem(
232 const char * name,
233 int itemid)
234 {
235 int id = itemid;
236 if (id == -1) {
237 id = 1;
238 while (this->getItemRecord(id) != NULL) id++;
239 } else {
240 assert(this->getItemRecord(itemid) == NULL &&
241 "requested itemid already taken");
242 }
243 ItemRecord * rec = createItemRecord(name);
244 rec->itemid = id;
245 this->items->append(rec);
246 return id;
247 } // newMenuItem()
248
249 /*!
250 */
251
252 int
getMenuItem(const char * name)253 QtNativePopupMenu::getMenuItem(
254 const char * name)
255 {
256 const int numItems = this->items->getLength();
257 int i;
258 for (i = 0; i < numItems; i++) {
259 ItemRecord * rec = (ItemRecord *) (*this->items)[i];
260 if (strcmp(rec->name, name) == 0)
261 return rec->itemid;
262 }
263 return -1;
264 } // getMenuItem()
265
266 /*!
267 */
268
269 void
setMenuItemTitle(int itemid,const char * title)270 QtNativePopupMenu::setMenuItemTitle(
271 int itemid,
272 const char * title)
273 {
274 ItemRecord * rec = this->getItemRecord(itemid);
275 assert(rec && "no such menu");
276 delete [] rec->title;
277 rec->title = strcpy(new char [strlen(title)+1], title);
278 #if QT_VERSION >= 200
279 if (rec->parent)
280 #if QT_VERSION >= 0x040000
281 rec->parent->actionAt(QPoint(rec->itemid,0))->setText(rec->title);
282 #else
283 rec->parent->changeItem(rec->itemid, QString(rec->title));
284 #endif
285 #else // Qt version < 2.0.0
286 // This QMenuData::changeItem() method is being obsoleted from Qt
287 // from version 2.0.0 onwards.
288 if (rec->parent)
289 rec->parent->changeItem(rec->title, rec->itemid);
290 #endif // Qt version < 2.0.0
291 } // setMenuItemTitle()
292
293 /*!
294 */
295
296 const char *
getMenuItemTitle(int itemid)297 QtNativePopupMenu::getMenuItemTitle(
298 int itemid)
299 {
300 ItemRecord * rec = this->getItemRecord(itemid);
301 assert(rec && "no such menu");
302 return rec->title;
303 } // getMenuItemTitle()
304
305 /*!
306 */
307
308 void
setMenuItemEnabled(int itemid,SbBool enabled)309 QtNativePopupMenu::setMenuItemEnabled(int itemid,
310 SbBool enabled)
311 {
312 ItemRecord * rec = this->getItemRecord(itemid);
313 if (rec) {
314 #if QT_VERSION >= 0x040000
315 rec->parent->actionAt(QPoint(rec->itemid,0))->setEnabled(enabled ? true : false);
316 #else
317 rec->parent->setItemEnabled(rec->itemid, enabled ? true : false);
318 #endif
319 return;
320 }
321 MenuRecord * mrec = this->getMenuRecord(itemid);
322 assert(mrec && "no such menu");
323 assert(mrec->parent && "a menuitem must have a parent to be enabled/disabled");
324
325 #if QT_VERSION >= 0x040000
326 mrec->parent->actionAt(QPoint(mrec->menuid,0))->setEnabled(enabled ? true : false);
327 #else
328 mrec->parent->setItemEnabled(mrec->menuid, enabled ? true : false);
329 #endif
330 } // setMenuItemEnabled()
331
332 /*!
333 */
334
335 SbBool
getMenuItemEnabled(int itemid)336 QtNativePopupMenu::getMenuItemEnabled(int itemid)
337 {
338 ItemRecord * rec = this->getItemRecord(itemid);
339
340 if (rec) {
341 #if QT_VERSION >= 0x040000
342 return rec->parent->actionAt(QPoint(rec->itemid,0))->isEnabled();
343 #else
344 return rec->parent->isItemEnabled(rec->itemid) ? TRUE : FALSE;
345 #endif
346 }
347
348 MenuRecord * mrec = this->getMenuRecord(itemid);
349 assert(mrec && "no such menu");
350 assert(mrec->parent && "a menuitem must have a parent to be enabled/disabled");
351
352 #if QT_VERSION >= 0x040000
353 return mrec->parent->actionAt(QPoint(mrec->menuid,0))->isEnabled();
354 #else
355 return mrec->parent->isItemEnabled(mrec->menuid) ? TRUE : FALSE;
356 #endif
357 } // getMenuItemEnabled()
358
359 /*!
360 */
361
362 void
_setMenuItemMarked(int itemid,SbBool marked)363 QtNativePopupMenu::_setMenuItemMarked(int itemid, SbBool marked)
364 {
365 ItemRecord * rec = this->getItemRecord(itemid);
366 if (rec == NULL)
367 return;
368 if (marked)
369 rec->flags |= ITEM_MARKED;
370 else
371 rec->flags &= ~ITEM_MARKED;
372
373 if (rec->parent != NULL) {
374 #if QT_VERSION >= 0x040000
375 QAction * action = rec->action;
376 if (action) {
377 //FIXME: Could this be done on creation? - BFG 20090910
378 action->setCheckable(true);
379 action->setChecked(marked ? true : false);
380 }
381 #else
382 rec->parent->setItemChecked(rec->itemid, marked ? true : false);
383 #endif
384 }
385 }
386
387 /*!
388 */
389
390 SbBool
getMenuItemMarked(int itemid)391 QtNativePopupMenu::getMenuItemMarked(
392 int itemid)
393 {
394 ItemRecord * rec = this->getItemRecord(itemid);
395 assert(rec && "no such menu");
396 if (rec->parent == NULL)
397 return (rec->flags & ITEM_MARKED) ? TRUE : FALSE;
398
399 #if QT_VERSION >= 0x040000
400 QAction * action = rec->action;
401 if (!action) return false;
402 return action->isChecked();
403 #else
404 return rec->parent->isItemChecked(rec->itemid) ? TRUE : FALSE;
405 #endif
406 } // getMenuItemMarked()
407
408 // *************************************************************************
409
410 /*!
411 */
412
413 void
addMenu(int menuid,int submenuid,int pos)414 QtNativePopupMenu::addMenu(int menuid,
415 int submenuid,
416 int pos)
417 {
418 MenuRecord * super = this->getMenuRecord(menuid);
419 MenuRecord * sub = this->getMenuRecord(submenuid);
420 assert(super && sub && "no such menu");
421
422 // disconnect the submenu from the activated() signal. In Qt 4.0
423 // only the parent menu should be connected, since both the parent
424 // menu and the submenu will generate a call to
425 // itemActivation(). Multiple calls to itemActivation() causes a
426 // segfault when selecting Quadbuffer stereo, at least when it's not
427 // supported. (20050726 frodo)
428
429 // this was changed/fixed again in Qt 4.4.0, so now we shouldn't
430 // disconnect menu items if this version is detected...
431 // (20070530 pederb)
432 #if (QT_VERSION >= 0x040000) && (QT_VERSION < 0x040400)
433 QObject::disconnect(sub->menu, SIGNAL(activated(int)),
434 this, SLOT(itemActivation(int)));
435 #endif // QT-version >= 400 && QT-version < 4.4.0
436
437 #if QT_VERSION >= 0x040000
438 QAction * action;
439 if (pos == -1) {
440 action = super->menu->addMenu(sub->menu);
441 }
442 else {
443 QAction * before = super->menu->actionAt(QPoint(pos,0));
444 action = super->menu->insertMenu(before,sub->menu);
445 }
446 action->setText(QString(sub->title));
447 #else
448 if (pos == -1)
449 super->menu->insertItem(QString(sub->title), sub->menu, sub->menuid);
450 else
451 super->menu->insertItem(QString(sub->title),
452 sub->menu, sub->menuid, pos);
453 #endif
454 sub->parent = super->menu;
455 } // addMenu()
456
457 /*!
458 */
459
460 void
addMenuItem(int menuid,int itemid,int pos)461 QtNativePopupMenu::addMenuItem(int menuid,
462 int itemid,
463 int pos)
464 {
465 MenuRecord * menu = this->getMenuRecord(menuid);
466 assert(menu && "invalid parent menu id");
467 ItemRecord * item = this->getItemRecord(itemid);
468 assert(item && "invalid child menu id");
469
470 #if QT_VERSION >= 0x040000
471 item->action = new QAction(menu->menu);
472 item->action->setText(item->title);
473 if (pos == -1) {
474 menu->menu->addAction(item->action);
475 }
476 else {
477 menu->menu->insertAction(menu->menu->actionAt(QPoint(pos,0)),item->action);
478 }
479 #else
480 if (pos == -1)
481 menu->menu->insertItem(QString(item->title), item->itemid);
482 else
483 menu->menu->insertItem(QString(item->title), item->itemid, pos);
484 #endif
485 item->parent = menu->menu;
486
487 #if QT_VERSION >= 0x040000
488 // FIXME: is this really safe? (20050726 frodo)
489 QAction * action = (QAction *) item->parent->actionAt(QPoint(itemid,0));
490 if (action) action->setCheckable(true);
491 #endif // Qt 4.*
492
493 if (item->flags & ITEM_MARKED) {
494 #if QT_VERSION >= 0x040000
495 if (action) action->setChecked(true);
496 #else
497 item->parent->setItemChecked(item->itemid, true);
498 #endif
499 }
500 } // addMenuItem()
501
502 void
addSeparator(int menuid,int pos)503 QtNativePopupMenu::addSeparator(int menuid,
504 int pos)
505 {
506 MenuRecord * menu = this->getMenuRecord(menuid);
507 assert(menu && "no such menu");
508
509 ItemRecord * rec = createItemRecord("separator");
510 #if QT_VERSION >= 0x040000
511 menu->menu->insertSeparator(menu->menu->actionAt(QPoint(pos,0)));
512 #else
513 menu->menu->insertSeparator(pos);
514 #endif
515 rec->flags |= ITEM_SEPARATOR;
516 this->items->append(rec);
517 } // addSeparator()
518
519 /*!
520 This method removes the submenu with the given \a menuid.
521
522 A removed menu can be attached again later - its menuid will still be
523 allocated.
524 */
525
526 void
removeMenu(int menuid)527 QtNativePopupMenu::removeMenu(int menuid)
528 {
529 MenuRecord * rec = this->getMenuRecord(menuid);
530 assert(rec && "no such menu");
531
532 if (rec->menuid == 0) {
533 #if SOQT_DEBUG
534 SoDebugError::postInfo("QtNativePopupMenu::RemoveMenu", "can't remove root");
535 #endif // SOQT_DEBUG
536 return;
537 }
538 if (rec->parent == NULL) {
539 #if SOQT_DEBUG
540 SoDebugError::postInfo("QtNativePopupMenu::RemoveMenu", "menu not attached");
541 #endif // SOQT_DEBUG
542 return;
543 }
544 #if QT_VERSION >= 0x040000
545 rec->parent->removeAction(rec->parent->actionAt(QPoint(rec->menuid,0)));
546 #else
547 rec->parent->removeItem(rec->menuid);
548 #endif
549 rec->parent = NULL;
550 } // removeMenu()
551
552 /*!
553 This method removes the menu item with the given \a itemid.
554
555 A removed menu item can be attached again later - its itemid will still
556 be allocated.
557 */
558
559 void
removeMenuItem(int itemid)560 QtNativePopupMenu::removeMenuItem(int itemid)
561 {
562 ItemRecord * rec = this->getItemRecord(itemid);
563 assert(rec && "no such item");
564
565 if (rec->parent == NULL) {
566 #if SOQT_DEBUG
567 SoDebugError::postInfo("QtNativePopupMenu::RemoveMenu", "item not attached");
568 #endif // SOQT_DEBUG
569 return;
570 }
571 #if QT_VERSION >= 0x040000
572 rec->parent->removeAction(rec->parent->actionAt(QPoint(rec->itemid,0)));
573 #else
574 rec->parent->removeItem(rec->itemid);
575 #endif
576 rec->parent = NULL;
577 } // removeMenuItem()
578
579 // *************************************************************************
580
581 // Doc in superclass.
582 void
popUp(QWidget * inside,int x,int y)583 QtNativePopupMenu::popUp(QWidget * inside, int x, int y)
584 {
585 MenuRecord * rec = this->getMenuRecord(0);
586
587 // Use exec() and not popup(). popup() doesn't seem to work properly
588 // with a GL widget (the GL-redraw seems to overwrite the popup
589 // widget or something). pederb, 2003-10-01
590 //
591 // An additional piece of information: the popup-menu misbehavior is
592 // that at first attempt just a "shadow" comes up, and then on the
593 // second RMB-press the menu actually comes up. This problem has
594 // only been seen on a few Windows 2000 platforms, though, so in
595 // case we ever need to revert to using popup() again, make sure the
596 // code is properly tested over a wide range of platforms.
597 // mortene, 2003-10-01
598 //
599 // Ignore return value. We use callbacks.
600 (void) rec->menu->exec(inside->mapToGlobal(QPoint(x, y)));
601 }
602
603 // *************************************************************************
604
605 /*!
606 */
607
608 MenuRecord *
getMenuRecord(int menuid)609 QtNativePopupMenu::getMenuRecord(
610 int menuid)
611 {
612 const int numMenus = this->menus->getLength();
613 int i;
614 for (i = 0; i < numMenus; i++)
615 if (((MenuRecord *) (*this->menus)[i])->menuid == menuid)
616 return (MenuRecord *) (*this->menus)[i];
617 return (MenuRecord *) NULL;
618 } // getMenuRecord()
619
620 /*!
621 */
622 ItemRecord *
getItemRecord(int itemid)623 QtNativePopupMenu::getItemRecord(int itemid)
624 {
625 const int numItems = this->items->getLength();
626 for (int i = 0; i < numItems; i++) {
627 const int recid = ((ItemRecord *) (*this->items)[i])->itemid;
628 if (recid == itemid) { return (ItemRecord *) (*this->items)[i]; }
629 }
630
631 return (ItemRecord *) NULL;
632 }
633
634 // *************************************************************************
635
636 /*!
637 */
638 ItemRecord *
getItemRecordFromAction(QAction * action)639 QtNativePopupMenu::getItemRecordFromAction(QAction * action)
640 {
641 const int numItems = this->items->getLength();
642 for (int i = 0; i < numItems; i++) {
643 const ItemRecord * rec = static_cast<ItemRecord *>((*this->items)[i]);
644 if (rec->action == action) { return (ItemRecord *) (*this->items)[i]; }
645 }
646
647 return (ItemRecord *) NULL;
648 }
649
650 // *************************************************************************
651
652 /*!
653 */
654
655 MenuRecord *
createMenuRecord(const char * name)656 QtNativePopupMenu::createMenuRecord(
657 const char * name)
658 {
659 MenuRecord * rec = new MenuRecord;
660 rec->menuid = -1;
661 rec->name = strcpy(new char [strlen(name)+1], name);
662 rec->title = strcpy(new char [strlen(name)+1], name);
663
664 #if QT_VERSION >= 0x040000
665 rec->menu = new QPOPUPMENU_CLASS(QString(name));
666 QObject::connect(rec->menu, SIGNAL(triggered(QAction *)),
667 this, SLOT(itemActivation(QAction *)));
668 #else
669 rec->menu = new QPOPUPMENU_CLASS((QWidget *) NULL, name);
670 QObject::connect(rec->menu, SIGNAL(activated(int)),
671 this, SLOT(itemActivation(int)));
672 #endif
673
674 rec->parent = NULL;
675 rec->action = NULL;
676 return rec;
677 } // create()
678
679 /*!
680 */
681
682 ItemRecord *
createItemRecord(const char * name)683 QtNativePopupMenu::createItemRecord(
684 const char * name)
685 {
686 ItemRecord * rec = new ItemRecord;
687 rec->itemid = -1;
688 rec->flags = 0;
689 rec->name = strcpy(new char [strlen(name)+1], name);
690 rec->title = strcpy(new char [strlen(name)+1], name);
691 rec->parent = NULL;
692 rec->action = NULL;
693 return rec;
694 } // create()
695
696 // *************************************************************************
697
698 void
itemActivation(QAction * action)699 QtNativePopupMenu::itemActivation(// private slot
700 QAction * action
701 )
702 {
703 ItemRecord * rec = getItemRecordFromAction(action);
704 assert(rec);
705 inherited::invokeMenuSelection(rec->itemid);
706 } // menuSelection()
707
708 void
itemActivation(int itemid)709 QtNativePopupMenu::itemActivation(// private slot used for Qt3 code path
710 int itemid
711 )
712 {
713 inherited::invokeMenuSelection(itemid);
714 }
715
716 // *************************************************************************
717