put floating frames under managed links control
[LibreOffice.git] / vcl / qt5 / QtMenu.cxx
blob0646c9232d5d74c38d38e24e60eaed608f31ff3c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <QtMenu.hxx>
11 #include <QtMenu.moc>
13 #include <QtFrame.hxx>
14 #include <QtInstance.hxx>
15 #include <QtMainWindow.hxx>
17 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
18 #include <QtWidgets/QActionGroup>
19 #else
20 #include <QtGui/QActionGroup>
21 #endif
23 #include <QtWidgets/QMenuBar>
24 #include <QtWidgets/QPushButton>
26 #include <o3tl/safeint.hxx>
27 #include <vcl/svapp.hxx>
28 #include <sal/log.hxx>
30 #include <strings.hrc>
31 #include <bitmaps.hlst>
33 #include <vcl/toolkit/floatwin.hxx>
34 #include <window.h>
36 QtMenu::QtMenu(bool bMenuBar)
37 : mpVCLMenu(nullptr)
38 , mpParentSalMenu(nullptr)
39 , mpFrame(nullptr)
40 , mbMenuBar(bMenuBar)
41 , mpQMenuBar(nullptr)
42 , mpQMenu(nullptr)
46 bool QtMenu::VisibleMenuBar() { return true; }
48 void QtMenu::InsertMenuItem(QtMenuItem* pSalMenuItem, unsigned nPos)
50 sal_uInt16 nId = pSalMenuItem->mnId;
51 OUString aText = mpVCLMenu->GetItemText(nId);
52 NativeItemText(aText);
53 vcl::KeyCode nAccelKey = mpVCLMenu->GetAccelKey(nId);
55 pSalMenuItem->mpAction.reset();
56 pSalMenuItem->mpMenu.reset();
58 if (mbMenuBar)
60 // top-level menu
61 if (validateQMenuBar())
63 QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
64 pSalMenuItem->mpMenu.reset(pQMenu);
66 if ((nPos != MENU_APPEND)
67 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenuBar->actions().size())))
69 mpQMenuBar->insertMenu(mpQMenuBar->actions()[nPos], pQMenu);
71 else
73 mpQMenuBar->addMenu(pQMenu);
76 // correct parent menu for generated menu
77 if (pSalMenuItem->mpSubMenu)
79 pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
82 connect(pQMenu, &QMenu::aboutToShow, this,
83 [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
84 connect(pQMenu, &QMenu::aboutToHide, this,
85 [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
88 else
90 if (!mpQMenu)
92 // no QMenu set, instantiate own one
93 mpOwnedQMenu.reset(new QMenu);
94 mpQMenu = mpOwnedQMenu.get();
97 if (pSalMenuItem->mpSubMenu)
99 // submenu
100 QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
101 pSalMenuItem->mpMenu.reset(pQMenu);
103 if ((nPos != MENU_APPEND)
104 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
106 mpQMenu->insertMenu(mpQMenu->actions()[nPos], pQMenu);
108 else
110 mpQMenu->addMenu(pQMenu);
113 // correct parent menu for generated menu
114 pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
116 ReinitializeActionGroup(nPos);
118 // clear all action groups since menu is recreated
119 pSalMenuItem->mpSubMenu->ResetAllActionGroups();
121 connect(pQMenu, &QMenu::aboutToShow, this,
122 [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
123 connect(pQMenu, &QMenu::aboutToHide, this,
124 [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
126 else
128 if (pSalMenuItem->mnType == MenuItemType::SEPARATOR)
130 QAction* pAction = new QAction(nullptr);
131 pSalMenuItem->mpAction.reset(pAction);
132 pAction->setSeparator(true);
134 if ((nPos != MENU_APPEND)
135 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
137 mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
139 else
141 mpQMenu->addAction(pAction);
144 ReinitializeActionGroup(nPos);
146 else
148 // leaf menu
149 QAction* pAction = new QAction(toQString(aText), nullptr);
150 pSalMenuItem->mpAction.reset(pAction);
152 if ((nPos != MENU_APPEND)
153 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
155 mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
157 else
159 mpQMenu->addAction(pAction);
162 ReinitializeActionGroup(nPos);
164 UpdateActionGroupItem(pSalMenuItem);
166 pAction->setShortcut(toQString(nAccelKey.GetName()));
168 connect(pAction, &QAction::triggered, this,
169 [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
174 QAction* pAction = pSalMenuItem->getAction();
175 if (pAction)
177 pAction->setEnabled(pSalMenuItem->mbEnabled);
178 pAction->setVisible(pSalMenuItem->mbVisible);
182 void QtMenu::ReinitializeActionGroup(unsigned nPos)
184 const unsigned nCount = GetItemCount();
186 if (nCount == 0)
188 return;
191 if (nPos == MENU_APPEND)
193 nPos = nCount - 1;
195 else if (nPos >= nCount)
197 return;
200 QtMenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr;
201 QtMenuItem* pCurrentItem = GetItemAtPos(nPos);
202 QtMenuItem* pNextItem = (nPos < nCount - 1) ? GetItemAtPos(nPos + 1) : nullptr;
204 if (pCurrentItem->mnType == MenuItemType::SEPARATOR)
206 pCurrentItem->mpActionGroup.reset();
208 // if it's inserted into middle of existing group, split it into two groups:
209 // first goes original group, after separator goes new group
210 if (pPrevItem && pPrevItem->mpActionGroup && pNextItem && pNextItem->mpActionGroup
211 && (pPrevItem->mpActionGroup == pNextItem->mpActionGroup))
213 std::shared_ptr<QActionGroup> pFirstActionGroup = pPrevItem->mpActionGroup;
214 auto pSecondActionGroup = std::make_shared<QActionGroup>(nullptr);
215 pSecondActionGroup->setExclusive(true);
217 auto actions = pFirstActionGroup->actions();
219 for (unsigned idx = nPos + 1; idx < nCount; ++idx)
221 QtMenuItem* pModifiedItem = GetItemAtPos(idx);
223 if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
225 break;
228 pModifiedItem->mpActionGroup = pSecondActionGroup;
229 auto action = pModifiedItem->getAction();
231 if (actions.contains(action))
233 pFirstActionGroup->removeAction(action);
234 pSecondActionGroup->addAction(action);
239 else
241 if (!pCurrentItem->mpActionGroup)
243 // unless element is inserted between two separators, or a separator and an end of vector, use neighbouring group since it's shared
244 if (pPrevItem && pPrevItem->mpActionGroup)
246 pCurrentItem->mpActionGroup = pPrevItem->mpActionGroup;
248 else if (pNextItem && pNextItem->mpActionGroup)
250 pCurrentItem->mpActionGroup = pNextItem->mpActionGroup;
252 else
254 pCurrentItem->mpActionGroup = std::make_shared<QActionGroup>(nullptr);
255 pCurrentItem->mpActionGroup->setExclusive(true);
259 // if there's also a different group after this element, merge it
260 if (pNextItem && pNextItem->mpActionGroup
261 && (pCurrentItem->mpActionGroup != pNextItem->mpActionGroup))
263 auto pFirstCheckedAction = pCurrentItem->mpActionGroup->checkedAction();
264 auto pSecondCheckedAction = pNextItem->mpActionGroup->checkedAction();
265 auto actions = pNextItem->mpActionGroup->actions();
267 // first move all actions from second group to first one, and if first group already has checked action,
268 // and second group also has a checked action, uncheck action from second group
269 for (auto action : actions)
271 pNextItem->mpActionGroup->removeAction(action);
273 if (pFirstCheckedAction && pSecondCheckedAction && (action == pSecondCheckedAction))
275 action->setChecked(false);
278 pCurrentItem->mpActionGroup->addAction(action);
281 // now replace all pointers to second group with pointers to first group
282 for (unsigned idx = nPos + 1; idx < nCount; ++idx)
284 QtMenuItem* pModifiedItem = GetItemAtPos(idx);
286 if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
288 break;
291 pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup;
297 void QtMenu::ResetAllActionGroups()
299 for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem)
301 QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
302 pSalMenuItem->mpActionGroup.reset();
306 void QtMenu::UpdateActionGroupItem(const QtMenuItem* pSalMenuItem)
308 QAction* pAction = pSalMenuItem->getAction();
309 if (!pAction)
310 return;
312 bool bChecked = mpVCLMenu->IsItemChecked(pSalMenuItem->mnId);
313 MenuItemBits itemBits = mpVCLMenu->GetItemBits(pSalMenuItem->mnId);
315 if (itemBits & MenuItemBits::RADIOCHECK)
317 pAction->setCheckable(true);
319 if (pSalMenuItem->mpActionGroup)
321 pSalMenuItem->mpActionGroup->addAction(pAction);
324 pAction->setChecked(bChecked);
326 else
328 pAction->setActionGroup(nullptr);
330 if (itemBits & MenuItemBits::CHECKABLE)
332 pAction->setCheckable(true);
333 pAction->setChecked(bChecked);
335 else
337 pAction->setChecked(false);
338 pAction->setCheckable(false);
343 void QtMenu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos)
345 SolarMutexGuard aGuard;
346 QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
348 if (nPos == MENU_APPEND)
349 maItems.push_back(pItem);
350 else
351 maItems.insert(maItems.begin() + nPos, pItem);
353 pItem->mpParentMenu = this;
355 InsertMenuItem(pItem, nPos);
358 void QtMenu::RemoveItem(unsigned nPos)
360 SolarMutexGuard aGuard;
362 if (nPos >= maItems.size())
363 return;
365 QtMenuItem* pItem = maItems[nPos];
366 pItem->mpAction.reset();
367 pItem->mpMenu.reset();
369 maItems.erase(maItems.begin() + nPos);
371 // Recalculate action groups if necessary:
372 // if separator between two QActionGroups was removed,
373 // it may be needed to merge them
374 if (nPos > 0)
376 ReinitializeActionGroup(nPos - 1);
380 void QtMenu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos)
382 SolarMutexGuard aGuard;
383 QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
384 QtMenu* pQSubMenu = static_cast<QtMenu*>(pSubMenu);
386 pItem->mpSubMenu = pQSubMenu;
387 // at this point the pointer to parent menu may be outdated, update it too
388 pItem->mpParentMenu = this;
390 if (pQSubMenu != nullptr)
392 pQSubMenu->mpParentSalMenu = this;
393 pQSubMenu->mpQMenu = pItem->mpMenu.get();
396 // if it's not a menu bar item, then convert it to corresponding item if type if necessary.
397 // If submenu is present and it's an action, convert it to menu.
398 // If submenu is not present and it's a menu, convert it to action.
399 // It may be fine to proceed in any case, but by skipping other cases
400 // amount of unneeded actions taken should be reduced.
401 if (pItem->mpParentMenu->mbMenuBar || (pQSubMenu && pItem->mpMenu)
402 || ((!pQSubMenu) && pItem->mpAction))
404 return;
407 InsertMenuItem(pItem, nPos);
410 void QtMenu::SetFrame(const SalFrame* pFrame)
412 auto* pSalInst(static_cast<QtInstance*>(GetSalData()->m_pInstance));
413 assert(pSalInst);
414 if (!pSalInst->IsMainThread())
416 pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
417 return;
420 SolarMutexGuard aGuard;
421 assert(mbMenuBar);
422 mpFrame = const_cast<QtFrame*>(static_cast<const QtFrame*>(pFrame));
424 mpFrame->SetMenu(this);
426 QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
427 if (!pMainWindow)
428 return;
430 mpQMenuBar = new QMenuBar();
431 pMainWindow->setMenuBar(mpQMenuBar);
433 QPushButton* pButton = static_cast<QPushButton*>(mpQMenuBar->cornerWidget(Qt::TopRightCorner));
434 if (pButton)
435 connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
436 mpQMenu = nullptr;
438 DoFullMenuUpdate(mpVCLMenu);
441 void QtMenu::DoFullMenuUpdate(Menu* pMenuBar)
443 // clear action groups since menu is rebuilt
444 ResetAllActionGroups();
445 ShowCloseButton(false);
447 for (sal_Int32 nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++)
449 QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
450 InsertMenuItem(pSalMenuItem, nItem);
451 SetItemImage(nItem, pSalMenuItem, pSalMenuItem->maImage);
452 const bool bShowDisabled
453 = bool(pMenuBar->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries)
454 || !bool(pMenuBar->GetMenuFlags() & MenuFlags::HideDisabledEntries);
455 const bool bVisible = bShowDisabled || mpVCLMenu->IsItemEnabled(pSalMenuItem->mnId);
456 pSalMenuItem->getAction()->setVisible(bVisible);
458 if (pSalMenuItem->mpSubMenu != nullptr)
460 pMenuBar->HandleMenuActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
461 pSalMenuItem->mpSubMenu->DoFullMenuUpdate(pMenuBar);
462 pMenuBar->HandleMenuDeActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
467 void QtMenu::ShowItem(unsigned nPos, bool bShow)
469 if (nPos < maItems.size())
471 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
472 QAction* pAction = pSalMenuItem->getAction();
473 if (pAction)
474 pAction->setVisible(bShow);
475 pSalMenuItem->mbVisible = bShow;
479 void QtMenu::SetItemBits(unsigned nPos, MenuItemBits)
481 if (nPos < maItems.size())
483 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
484 UpdateActionGroupItem(pSalMenuItem);
488 void QtMenu::CheckItem(unsigned nPos, bool bChecked)
490 if (nPos < maItems.size())
492 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
493 QAction* pAction = pSalMenuItem->getAction();
494 if (pAction)
496 pAction->setCheckable(true);
497 pAction->setChecked(bChecked);
502 void QtMenu::EnableItem(unsigned nPos, bool bEnable)
504 if (nPos < maItems.size())
506 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
507 QAction* pAction = pSalMenuItem->getAction();
508 if (pAction)
509 pAction->setEnabled(bEnable);
510 pSalMenuItem->mbEnabled = bEnable;
514 void QtMenu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText)
516 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
517 QAction* pAction = pSalMenuItem->getAction();
518 if (pAction)
520 OUString aText(rText);
521 NativeItemText(aText);
522 pAction->setText(toQString(aText));
526 void QtMenu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
528 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
530 // Save new image to use it in DoFullMenuUpdate
531 pSalMenuItem->maImage = rImage;
533 QAction* pAction = pSalMenuItem->getAction();
534 if (!pAction)
535 return;
537 pAction->setIcon(QPixmap::fromImage(toQImage(rImage)));
540 void QtMenu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
541 const OUString& rText)
543 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
544 QAction* pAction = pSalMenuItem->getAction();
545 if (pAction)
546 pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
549 void QtMenu::GetSystemMenuData(SystemMenuData*) {}
551 QtMenu* QtMenu::GetTopLevel()
553 QtMenu* pMenu = this;
554 while (pMenu->mpParentSalMenu)
555 pMenu = pMenu->mpParentSalMenu;
556 return pMenu;
559 bool QtMenu::validateQMenuBar()
561 if (!mpQMenuBar)
562 return false;
563 assert(mpFrame);
564 QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
565 assert(pMainWindow);
566 const bool bValid = mpQMenuBar == pMainWindow->menuBar();
567 if (!bValid)
568 mpQMenuBar = nullptr;
569 return bValid;
572 void QtMenu::ShowMenuBar(bool bVisible)
574 if (validateQMenuBar())
575 mpQMenuBar->setVisible(bVisible);
578 const QtFrame* QtMenu::GetFrame() const
580 SolarMutexGuard aGuard;
581 const QtMenu* pMenu = this;
582 while (pMenu && !pMenu->mpFrame)
583 pMenu = pMenu->mpParentSalMenu;
584 return pMenu ? pMenu->mpFrame : nullptr;
587 void QtMenu::slotMenuTriggered(QtMenuItem* pQItem)
589 if (!pQItem)
590 return;
592 QtMenu* pSalMenu = pQItem->mpParentMenu;
593 QtMenu* pTopLevel = pSalMenu->GetTopLevel();
595 Menu* pMenu = pSalMenu->GetMenu();
596 auto mnId = pQItem->mnId;
598 // HACK to allow HandleMenuCommandEvent to "not-set" the checked button
599 // LO expects a signal before an item state change, so reset the check item
600 if (pQItem->mpAction->isCheckable()
601 && (!pQItem->mpActionGroup || pQItem->mpActionGroup->actions().size() <= 1))
602 pQItem->mpAction->setChecked(!pQItem->mpAction->isChecked());
603 pTopLevel->GetMenu()->HandleMenuCommandEvent(pMenu, mnId);
606 void QtMenu::slotMenuAboutToShow(QtMenuItem* pQItem)
608 if (pQItem)
610 QtMenu* pSalMenu = pQItem->mpSubMenu;
611 QtMenu* pTopLevel = pSalMenu->GetTopLevel();
613 Menu* pMenu = pSalMenu->GetMenu();
615 // following function may update the menu
616 pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu);
620 void QtMenu::slotMenuAboutToHide(QtMenuItem* pQItem)
622 if (pQItem)
624 QtMenu* pSalMenu = pQItem->mpSubMenu;
625 QtMenu* pTopLevel = pSalMenu->GetTopLevel();
627 Menu* pMenu = pSalMenu->GetMenu();
629 pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu);
633 void QtMenu::NativeItemText(OUString& rItemText)
635 // preserve literal '&'s in menu texts
636 rItemText = rItemText.replaceAll("&", "&&");
638 rItemText = rItemText.replace('~', '&');
641 void QtMenu::slotCloseDocument()
643 MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
644 if (pVclMenuBar)
645 Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
648 void QtMenu::ShowCloseButton(bool bShow)
650 if (!validateQMenuBar())
651 return;
653 QPushButton* pButton = static_cast<QPushButton*>(mpQMenuBar->cornerWidget(Qt::TopRightCorner));
654 if (!pButton && !bShow)
655 return;
656 if (!pButton)
658 QIcon aIcon;
659 if (QIcon::hasThemeIcon("window-close-symbolic"))
660 aIcon = QIcon::fromTheme("window-close-symbolic");
661 else
662 aIcon = QIcon(
663 QPixmap::fromImage(toQImage(Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC))));
664 pButton = new QPushButton(mpQMenuBar);
665 pButton->setIcon(aIcon);
666 pButton->setFlat(true);
667 pButton->setFocusPolicy(Qt::NoFocus);
668 pButton->setToolTip(toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)));
669 mpQMenuBar->setCornerWidget(pButton, Qt::TopRightCorner);
670 connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
673 if (bShow)
674 pButton->show();
675 else
676 pButton->hide();
679 bool QtMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
680 FloatWinPopupFlags nFlags)
682 assert(mpQMenu);
683 DoFullMenuUpdate(mpVCLMenu);
684 mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));
686 const VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
687 const QtFrame* pFrame = static_cast<QtFrame*>(xParent->ImplGetFrame());
688 assert(pFrame);
689 const tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
690 const QRect aRect = toQRect(aFloatRect, 1 / pFrame->devicePixelRatioF());
691 mpQMenu->exec(aRect.topLeft());
693 return true;
696 QtMenuItem::QtMenuItem(const SalItemParams* pItemData)
697 : mpParentMenu(nullptr)
698 , mpSubMenu(nullptr)
699 , mnId(pItemData->nId)
700 , mnType(pItemData->eType)
701 , mbVisible(true)
702 , mbEnabled(true)
703 , maImage(pItemData->aImage)
707 QAction* QtMenuItem::getAction() const
709 if (mpMenu)
710 return mpMenu->menuAction();
711 if (mpAction)
712 return mpAction.get();
713 return nullptr;
716 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */