uno grid grid a11y: Return existing cell accessible
[LibreOffice.git] / vcl / qt5 / QtMenu.cxx
blob845d1940b185cc6fd594861cbbd470838adf4592
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 <QtCustomStyle.hxx>
11 #include <vcl/themecolors.hxx>
12 #include <QtMenu.hxx>
13 #include <QtMenu.moc>
15 #include <QtFrame.hxx>
16 #include <QtInstance.hxx>
17 #include <QtMainWindow.hxx>
19 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
20 #include <QtWidgets/QActionGroup>
21 #else
22 #include <QtGui/QActionGroup>
23 #endif
25 #include <QtWidgets/QButtonGroup>
26 #include <QtWidgets/QHBoxLayout>
27 #include <QtWidgets/QMenuBar>
28 #include <QtWidgets/QPushButton>
29 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
30 #include <QtGui/QShortcut>
31 #else
32 #include <QtWidgets/QShortcut>
33 #endif
34 #include <QtWidgets/QStyle>
35 #include <QtWidgets/QToolTip>
37 #include <o3tl/safeint.hxx>
38 #include <vcl/svapp.hxx>
39 #include <sal/log.hxx>
41 #include <strings.hrc>
42 #include <bitmaps.hlst>
44 #include <vcl/qt/QtUtils.hxx>
45 #include <vcl/toolkit/floatwin.hxx>
46 #include <window.h>
48 // LO SalMenuButtonItem::mnId is sal_uInt16, so we go with -2, as -1 has a special meaning as automatic id
49 constexpr int CLOSE_BUTTON_ID = -2;
50 const QString gButtonGroupKey("QtMenu::ButtonGroup");
52 static inline void lcl_force_menubar_layout_update(QMenuBar& rMenuBar)
54 // just exists as a function to not comment it everywhere: forces reposition of the
55 // corner widget after its layout changes, which will otherwise just happen on resize.
56 // it unfortunatly has additional side effects; see QtMenu::GetMenuBarButtonRectPixel.
57 rMenuBar.adjustSize();
60 OUString QtMenu::m_sCurrentHelpId = u""_ustr;
62 QtMenu::QtMenu(bool bMenuBar)
63 : mpVCLMenu(nullptr)
64 , mpParentSalMenu(nullptr)
65 , mpFrame(nullptr)
66 , mbMenuBar(bMenuBar)
67 , mpQMenuBar(nullptr)
68 , mpQMenu(nullptr)
69 , m_pButtonGroup(nullptr)
73 bool QtMenu::eventFilter(QObject* pObject, QEvent* pEvent)
75 // manually trigger tooltip if action's tooltip is set,
76 // Qt doesn't do that for menu entries
77 if (pEvent->type() != QEvent::ToolTip)
78 return false;
80 QAction* pAction = nullptr;
81 if (QMenu* pMenu = qobject_cast<QMenu*>(pObject))
82 pAction = pMenu->activeAction();
83 else if (QMenuBar* pMenuBar = qobject_cast<QMenuBar*>(pObject))
84 pAction = pMenuBar->activeAction();
86 if (!pAction)
87 return false;
89 // QAction::toolTip() is by default based on action's text, only display if it differs
90 const QString sToolTip = pAction->toolTip();
91 if (!sToolTip.isEmpty() && sToolTip != QAction(pAction->text()).toolTip())
93 QHelpEvent* pHelpEvent = static_cast<QHelpEvent*>(pEvent);
94 QToolTip::showText(pHelpEvent->globalPos(), pAction->toolTip());
96 else
98 QToolTip::hideText();
101 return false;
104 bool QtMenu::VisibleMenuBar() { return true; }
106 void QtMenu::InsertMenuItem(QtMenuItem* pSalMenuItem, unsigned nPos)
108 sal_uInt16 nId = pSalMenuItem->mnId;
109 const QString aText = vclToQtStringWithAccelerator(mpVCLMenu->GetItemText(nId));
110 vcl::KeyCode nAccelKey = mpVCLMenu->GetAccelKey(nId);
112 pSalMenuItem->mpAction.reset();
113 pSalMenuItem->mpMenu.reset();
115 if (mbMenuBar)
117 // top-level menu
118 if (validateQMenuBar())
120 QMenu* pQMenu = new QMenu(aText, nullptr);
121 connectHelpSignalSlots(pQMenu, pSalMenuItem);
122 pSalMenuItem->mpMenu.reset(pQMenu);
124 if ((nPos != MENU_APPEND)
125 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenuBar->actions().size())))
127 mpQMenuBar->insertMenu(mpQMenuBar->actions()[nPos], pQMenu);
129 else
131 mpQMenuBar->addMenu(pQMenu);
134 // correct parent menu for generated menu
135 if (pSalMenuItem->mpSubMenu)
137 pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
140 connect(pQMenu, &QMenu::aboutToShow, this,
141 [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
142 connect(pQMenu, &QMenu::aboutToHide, this,
143 [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
146 else
148 if (!mpQMenu)
150 // no QMenu set, instantiate own one
151 mpOwnedQMenu.reset(new QMenu);
152 mpQMenu = mpOwnedQMenu.get();
153 connectHelpSignalSlots(mpQMenu, pSalMenuItem);
156 if (pSalMenuItem->mpSubMenu)
158 // submenu
159 QMenu* pQMenu = new QMenu(aText, nullptr);
160 connectHelpSignalSlots(pQMenu, pSalMenuItem);
161 pSalMenuItem->mpMenu.reset(pQMenu);
163 if ((nPos != MENU_APPEND)
164 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
166 mpQMenu->insertMenu(mpQMenu->actions()[nPos], pQMenu);
168 else
170 mpQMenu->addMenu(pQMenu);
173 // correct parent menu for generated menu
174 pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
176 ReinitializeActionGroup(nPos);
178 // clear all action groups since menu is recreated
179 pSalMenuItem->mpSubMenu->ResetAllActionGroups();
181 connect(pQMenu, &QMenu::aboutToShow, this,
182 [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
183 connect(pQMenu, &QMenu::aboutToHide, this,
184 [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
186 else
188 if (pSalMenuItem->mnType == MenuItemType::SEPARATOR)
190 QAction* pAction = new QAction(nullptr);
191 pSalMenuItem->mpAction.reset(pAction);
192 pAction->setSeparator(true);
194 if ((nPos != MENU_APPEND)
195 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
197 mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
199 else
201 mpQMenu->addAction(pAction);
204 ReinitializeActionGroup(nPos);
206 else
208 // leaf menu
209 QAction* pAction = new QAction(aText, nullptr);
210 pAction->setToolTip(toQString(mpVCLMenu->GetTipHelpText(nId)));
211 pSalMenuItem->mpAction.reset(pAction);
213 if ((nPos != MENU_APPEND)
214 && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
216 mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
218 else
220 mpQMenu->addAction(pAction);
223 ReinitializeActionGroup(nPos);
225 UpdateActionGroupItem(pSalMenuItem);
227 pAction->setShortcut(toQString(nAccelKey.GetName()));
229 connect(pAction, &QAction::triggered, this,
230 [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
231 connect(pAction, &QAction::hovered, this,
232 [pSalMenuItem] { slotMenuHovered(pSalMenuItem); });
237 QAction* pAction = pSalMenuItem->getAction();
238 if (pAction)
240 pAction->setEnabled(pSalMenuItem->mbEnabled);
241 pAction->setVisible(pSalMenuItem->mbVisible);
245 void QtMenu::ReinitializeActionGroup(unsigned nPos)
247 const unsigned nCount = GetItemCount();
249 if (nCount == 0)
251 return;
254 if (nPos == MENU_APPEND)
256 nPos = nCount - 1;
258 else if (nPos >= nCount)
260 return;
263 QtMenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr;
264 QtMenuItem* pCurrentItem = GetItemAtPos(nPos);
265 QtMenuItem* pNextItem = (nPos < nCount - 1) ? GetItemAtPos(nPos + 1) : nullptr;
267 if (pCurrentItem->mnType == MenuItemType::SEPARATOR)
269 pCurrentItem->mpActionGroup.reset();
271 // if it's inserted into middle of existing group, split it into two groups:
272 // first goes original group, after separator goes new group
273 if (pPrevItem && pPrevItem->mpActionGroup && pNextItem && pNextItem->mpActionGroup
274 && (pPrevItem->mpActionGroup == pNextItem->mpActionGroup))
276 std::shared_ptr<QActionGroup> pFirstActionGroup = pPrevItem->mpActionGroup;
277 auto pSecondActionGroup = std::make_shared<QActionGroup>(nullptr);
278 pSecondActionGroup->setExclusive(true);
280 auto actions = pFirstActionGroup->actions();
282 for (unsigned idx = nPos + 1; idx < nCount; ++idx)
284 QtMenuItem* pModifiedItem = GetItemAtPos(idx);
286 if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
288 break;
291 pModifiedItem->mpActionGroup = pSecondActionGroup;
292 auto action = pModifiedItem->getAction();
294 if (actions.contains(action))
296 pFirstActionGroup->removeAction(action);
297 pSecondActionGroup->addAction(action);
302 else
304 if (!pCurrentItem->mpActionGroup)
306 // unless element is inserted between two separators, or a separator and an end of vector, use neighbouring group since it's shared
307 if (pPrevItem && pPrevItem->mpActionGroup)
309 pCurrentItem->mpActionGroup = pPrevItem->mpActionGroup;
311 else if (pNextItem && pNextItem->mpActionGroup)
313 pCurrentItem->mpActionGroup = pNextItem->mpActionGroup;
315 else
317 pCurrentItem->mpActionGroup = std::make_shared<QActionGroup>(nullptr);
318 pCurrentItem->mpActionGroup->setExclusive(true);
322 // if there's also a different group after this element, merge it
323 if (pNextItem && pNextItem->mpActionGroup
324 && (pCurrentItem->mpActionGroup != pNextItem->mpActionGroup))
326 auto pFirstCheckedAction = pCurrentItem->mpActionGroup->checkedAction();
327 auto pSecondCheckedAction = pNextItem->mpActionGroup->checkedAction();
328 auto actions = pNextItem->mpActionGroup->actions();
330 // first move all actions from second group to first one, and if first group already has checked action,
331 // and second group also has a checked action, uncheck action from second group
332 for (auto action : actions)
334 pNextItem->mpActionGroup->removeAction(action);
336 if (pFirstCheckedAction && pSecondCheckedAction && (action == pSecondCheckedAction))
338 action->setChecked(false);
341 pCurrentItem->mpActionGroup->addAction(action);
344 // now replace all pointers to second group with pointers to first group
345 for (unsigned idx = nPos + 1; idx < nCount; ++idx)
347 QtMenuItem* pModifiedItem = GetItemAtPos(idx);
349 if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
351 break;
354 pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup;
360 void QtMenu::ResetAllActionGroups()
362 for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem)
364 QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
365 pSalMenuItem->mpActionGroup.reset();
369 void QtMenu::UpdateActionGroupItem(const QtMenuItem* pSalMenuItem)
371 QAction* pAction = pSalMenuItem->getAction();
372 if (!pAction)
373 return;
375 bool bChecked = mpVCLMenu->IsItemChecked(pSalMenuItem->mnId);
376 MenuItemBits itemBits = mpVCLMenu->GetItemBits(pSalMenuItem->mnId);
378 if (itemBits & MenuItemBits::RADIOCHECK)
380 pAction->setCheckable(true);
382 if (pSalMenuItem->mpActionGroup)
384 pSalMenuItem->mpActionGroup->addAction(pAction);
387 pAction->setChecked(bChecked);
389 else
391 pAction->setActionGroup(nullptr);
393 if (itemBits & MenuItemBits::CHECKABLE)
395 pAction->setCheckable(true);
396 pAction->setChecked(bChecked);
398 else
400 pAction->setChecked(false);
401 pAction->setCheckable(false);
406 void QtMenu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos)
408 SolarMutexGuard aGuard;
409 QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
411 if (nPos == MENU_APPEND)
412 maItems.push_back(pItem);
413 else
414 maItems.insert(maItems.begin() + nPos, pItem);
416 pItem->mpParentMenu = this;
418 InsertMenuItem(pItem, nPos);
421 void QtMenu::RemoveItem(unsigned nPos)
423 SolarMutexGuard aGuard;
425 if (nPos >= maItems.size())
426 return;
428 QtMenuItem* pItem = maItems[nPos];
429 pItem->mpAction.reset();
430 pItem->mpMenu.reset();
432 maItems.erase(maItems.begin() + nPos);
434 // Recalculate action groups if necessary:
435 // if separator between two QActionGroups was removed,
436 // it may be needed to merge them
437 if (nPos > 0)
439 ReinitializeActionGroup(nPos - 1);
443 void QtMenu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos)
445 SolarMutexGuard aGuard;
446 QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
447 QtMenu* pQSubMenu = static_cast<QtMenu*>(pSubMenu);
449 pItem->mpSubMenu = pQSubMenu;
450 // at this point the pointer to parent menu may be outdated, update it too
451 pItem->mpParentMenu = this;
453 if (pQSubMenu != nullptr)
455 pQSubMenu->mpParentSalMenu = this;
456 pQSubMenu->mpQMenu = pItem->mpMenu.get();
459 // if it's not a menu bar item, then convert it to corresponding item if type if necessary.
460 // If submenu is present and it's an action, convert it to menu.
461 // If submenu is not present and it's a menu, convert it to action.
462 // It may be fine to proceed in any case, but by skipping other cases
463 // amount of unneeded actions taken should be reduced.
464 if (pItem->mpParentMenu->mbMenuBar || (pQSubMenu && pItem->mpMenu)
465 || ((!pQSubMenu) && pItem->mpAction))
467 return;
470 InsertMenuItem(pItem, nPos);
473 void QtMenu::SetFrame(const SalFrame* pFrame)
475 QtInstance& rQtInstance = GetQtInstance();
476 if (!rQtInstance.IsMainThread())
478 rQtInstance.RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
479 return;
482 SolarMutexGuard aGuard;
483 assert(mbMenuBar);
484 mpFrame = const_cast<QtFrame*>(static_cast<const QtFrame*>(pFrame));
486 mpFrame->SetMenu(this);
488 QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
489 if (!pMainWindow)
490 return;
492 mpQMenuBar = new QMenuBar();
493 mpQMenuBar->installEventFilter(this);
494 pMainWindow->setMenuBar(mpQMenuBar);
496 // open menu bar on F10, as is common in KF 6 and other toolkits:
497 // https://wordsmith.social/felix-ernst/f10-for-accessibility-in-kf6
498 QShortcut* pQShortcut = new QShortcut(QKeySequence(Qt::Key_F10), mpQMenuBar->window());
499 connect(pQShortcut, &QShortcut::activated, this, &QtMenu::slotShortcutF10);
501 QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
502 if (pWidget)
504 m_pButtonGroup = pWidget->findChild<QButtonGroup*>(gButtonGroupKey);
505 assert(m_pButtonGroup);
506 connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
507 &QtMenu::slotMenuBarButtonClicked);
508 QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
509 if (pButton)
510 connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
512 else
513 m_pButtonGroup = nullptr;
514 mpQMenu = nullptr;
516 DoFullMenuUpdate(mpVCLMenu);
519 void QtMenu::DoFullMenuUpdate(Menu* pMenuBar)
521 if (mpQMenuBar && ThemeColors::VclPluginCanUseThemeColors())
522 mpQMenuBar->setPalette(QtCustomStyle::GetMenuBarPalette());
524 if (mpQMenu && ThemeColors::VclPluginCanUseThemeColors())
525 mpQMenu->setPalette(QtCustomStyle::GetMenuPalette());
527 // clear action groups since menu is rebuilt
528 ResetAllActionGroups();
529 ShowCloseButton(false);
531 for (sal_Int32 nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++)
533 QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
534 InsertMenuItem(pSalMenuItem, nItem);
535 SetItemImage(nItem, pSalMenuItem, pSalMenuItem->maImage);
536 const bool bShowDisabled
537 = bool(pMenuBar->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries)
538 || !bool(pMenuBar->GetMenuFlags() & MenuFlags::HideDisabledEntries);
539 const bool bVisible = pSalMenuItem->mbVisible
540 && (bShowDisabled || mpVCLMenu->IsItemEnabled(pSalMenuItem->mnId));
541 pSalMenuItem->getAction()->setVisible(bVisible);
543 if (pSalMenuItem->mpSubMenu != nullptr)
545 pMenuBar->HandleMenuActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
546 pSalMenuItem->mpSubMenu->DoFullMenuUpdate(pMenuBar);
547 pMenuBar->HandleMenuDeActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
552 void QtMenu::ShowItem(unsigned nPos, bool bShow)
554 if (nPos < maItems.size())
556 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
557 QAction* pAction = pSalMenuItem->getAction();
558 if (pAction)
559 pAction->setVisible(bShow);
560 pSalMenuItem->mbVisible = bShow;
564 void QtMenu::SetItemBits(unsigned nPos, MenuItemBits)
566 if (nPos < maItems.size())
568 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
569 UpdateActionGroupItem(pSalMenuItem);
573 void QtMenu::CheckItem(unsigned nPos, bool bChecked)
575 if (nPos < maItems.size())
577 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
578 QAction* pAction = pSalMenuItem->getAction();
579 if (pAction)
581 pAction->setCheckable(true);
582 pAction->setChecked(bChecked);
587 void QtMenu::EnableItem(unsigned nPos, bool bEnable)
589 if (nPos < maItems.size())
591 QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
592 QAction* pAction = pSalMenuItem->getAction();
593 if (pAction)
594 pAction->setEnabled(bEnable);
595 pSalMenuItem->mbEnabled = bEnable;
599 void QtMenu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText)
601 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
602 QAction* pAction = pSalMenuItem->getAction();
603 if (pAction)
605 pAction->setText(vclToQtStringWithAccelerator(rText));
609 void QtMenu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
611 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
613 // Save new image to use it in DoFullMenuUpdate
614 pSalMenuItem->maImage = rImage;
616 QAction* pAction = pSalMenuItem->getAction();
617 if (!pAction)
618 return;
620 pAction->setIcon(QPixmap::fromImage(toQImage(rImage)));
623 void QtMenu::SetItemTooltip(SalMenuItem* pItem, const OUString& rTooltip)
625 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
627 if (QAction* pAction = pSalMenuItem->getAction())
628 pAction->setToolTip(toQString(rTooltip));
631 void QtMenu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
632 const OUString& rText)
634 QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
635 QAction* pAction = pSalMenuItem->getAction();
636 if (pAction)
637 pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
640 QtMenu* QtMenu::GetTopLevel()
642 QtMenu* pMenu = this;
643 while (pMenu->mpParentSalMenu)
644 pMenu = pMenu->mpParentSalMenu;
645 return pMenu;
648 bool QtMenu::validateQMenuBar() const
650 if (!mpQMenuBar)
651 return false;
652 assert(mpFrame);
653 QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
654 assert(pMainWindow);
655 const bool bValid = mpQMenuBar == pMainWindow->menuBar();
656 if (!bValid)
658 QtMenu* thisPtr = const_cast<QtMenu*>(this);
659 thisPtr->mpQMenuBar = nullptr;
661 return bValid;
664 void QtMenu::ShowMenuBar(bool bVisible)
666 if (!validateQMenuBar())
667 return;
669 mpQMenuBar->setVisible(bVisible);
670 if (bVisible)
671 lcl_force_menubar_layout_update(*mpQMenuBar);
674 void QtMenu::slotMenuHovered(QtMenuItem* pItem)
676 const OUString sHelpId = pItem->mpParentMenu->GetMenu()->GetHelpId(pItem->mnId);
677 m_sCurrentHelpId = sHelpId;
680 void QtMenu::slotShowHelp()
682 SolarMutexGuard aGuard;
683 Help* pHelp = Application::GetHelp();
684 if (pHelp && !m_sCurrentHelpId.isEmpty())
686 pHelp->Start(m_sCurrentHelpId);
690 void QtMenu::slotMenuTriggered(QtMenuItem* pQItem)
692 if (!pQItem)
693 return;
695 QtMenu* pSalMenu = pQItem->mpParentMenu;
696 QtMenu* pTopLevel = pSalMenu->GetTopLevel();
698 Menu* pMenu = pSalMenu->GetMenu();
699 auto mnId = pQItem->mnId;
701 // HACK to allow HandleMenuCommandEvent to "not-set" the checked button
702 // LO expects a signal before an item state change, so reset the check item
703 if (pQItem->mpAction->isCheckable()
704 && (!pQItem->mpActionGroup || pQItem->mpActionGroup->actions().size() <= 1))
705 pQItem->mpAction->setChecked(!pQItem->mpAction->isChecked());
706 pTopLevel->GetMenu()->HandleMenuCommandEvent(pMenu, mnId);
709 void QtMenu::slotMenuAboutToShow(QtMenuItem* pQItem)
711 if (pQItem)
713 QtMenu* pSalMenu = pQItem->mpSubMenu;
714 QtMenu* pTopLevel = pSalMenu->GetTopLevel();
716 Menu* pMenu = pSalMenu->GetMenu();
718 // following function may update the menu
719 pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu);
723 void QtMenu::slotMenuAboutToHide(QtMenuItem* pQItem)
725 if (pQItem)
727 QtMenu* pSalMenu = pQItem->mpSubMenu;
728 QtMenu* pTopLevel = pSalMenu->GetTopLevel();
730 Menu* pMenu = pSalMenu->GetMenu();
732 pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu);
736 void QtMenu::slotCloseDocument()
738 MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
739 if (pVclMenuBar)
740 Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
743 void QtMenu::slotMenuBarButtonClicked(QAbstractButton* pButton)
745 MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
746 if (pVclMenuBar)
748 SolarMutexGuard aGuard;
749 pVclMenuBar->HandleMenuButtonEvent(m_pButtonGroup->id(pButton));
753 void QtMenu::slotShortcutF10()
755 SolarMutexGuard aGuard;
757 // focus menu bar and select first item
758 if (mpQMenuBar && !mpQMenuBar->actions().empty())
759 mpQMenuBar->setActiveAction(mpQMenuBar->actions().at(0));
762 QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& rIcon, const QString& rToolTip, int nId)
764 if (!validateQMenuBar())
765 return nullptr;
767 QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
768 QHBoxLayout* pLayout;
769 if (!pWidget)
771 assert(!m_pButtonGroup);
772 pWidget = new QWidget(mpQMenuBar);
773 assert(!pWidget->layout());
774 pLayout = new QHBoxLayout();
775 pLayout->setContentsMargins(QMargins());
776 pLayout->setSpacing(0);
777 pWidget->setLayout(pLayout);
778 m_pButtonGroup = new QButtonGroup(pLayout);
779 m_pButtonGroup->setObjectName(gButtonGroupKey);
780 m_pButtonGroup->setExclusive(false);
781 connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
782 &QtMenu::slotMenuBarButtonClicked);
783 pWidget->show();
784 mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
786 else
787 pLayout = static_cast<QHBoxLayout*>(pWidget->layout());
788 assert(m_pButtonGroup);
789 assert(pLayout);
791 QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
792 if (pButton)
793 ImplRemoveMenuBarButton(nId);
795 pButton = new QPushButton();
796 // we don't want the button to increase the QMenuBar height, so a fixed size square it is
797 const int nFixedLength
798 = mpQMenuBar->height() - 2 * mpQMenuBar->style()->pixelMetric(QStyle::PM_MenuBarVMargin);
799 pButton->setFixedSize(nFixedLength, nFixedLength);
800 pButton->setIcon(rIcon);
801 pButton->setFlat(true);
802 pButton->setFocusPolicy(Qt::NoFocus);
803 pButton->setToolTip(rToolTip);
805 m_pButtonGroup->addButton(pButton, nId);
806 int nPos = pLayout->count();
807 if (m_pButtonGroup->button(CLOSE_BUTTON_ID))
808 nPos--;
809 pLayout->insertWidget(nPos, pButton, 0, Qt::AlignCenter);
810 // show must happen after adding the button to the layout, otherwise the button is
811 // shown, but not correct in the layout, if at all! Some times the layout ignores it.
812 pButton->show();
814 lcl_force_menubar_layout_update(*mpQMenuBar);
816 return pButton;
819 bool QtMenu::AddMenuBarButton(const SalMenuButtonItem& rItem)
821 if (!validateQMenuBar())
822 return false;
823 return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem.maImage))),
824 toQString(rItem.maToolTipText), rItem.mnId);
827 void QtMenu::ImplRemoveMenuBarButton(int nId)
829 if (!validateQMenuBar())
830 return;
832 assert(m_pButtonGroup);
833 auto* pButton = m_pButtonGroup->button(nId);
834 assert(pButton);
835 QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
836 assert(pWidget);
837 QLayout* pLayout = pWidget->layout();
838 m_pButtonGroup->removeButton(pButton);
839 pLayout->removeWidget(pButton);
840 delete pButton;
842 lcl_force_menubar_layout_update(*mpQMenuBar);
845 void QtMenu::connectHelpShortcut(QMenu* pMenu)
847 assert(pMenu);
848 QKeySequence sequence(QKeySequence::HelpContents);
849 QShortcut* pQShortcut = new QShortcut(sequence, pMenu);
850 connect(pQShortcut, &QShortcut::activated, this, QtMenu::slotShowHelp);
851 connect(pQShortcut, &QShortcut::activatedAmbiguously, this, QtMenu::slotShowHelp);
854 void QtMenu::connectHelpSignalSlots(QMenu* pMenu, QtMenuItem* pSalMenuItem)
856 // connect hovered signal of the menu's own action
857 QAction* pAction = pMenu->menuAction();
858 assert(pAction);
859 connect(pAction, &QAction::hovered, this, [pSalMenuItem] { slotMenuHovered(pSalMenuItem); });
861 // connect slot to handle Help key (F1)
862 connectHelpShortcut(pMenu);
864 // install event filter in order to show tooltip on tooltip event
865 pMenu->installEventFilter(this);
868 void QtMenu::RemoveMenuBarButton(sal_uInt16 nId) { ImplRemoveMenuBarButton(nId); }
870 tools::Rectangle QtMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pFrame)
872 #ifdef NDEBUG
873 Q_UNUSED(pFrame);
874 #endif
875 if (!validateQMenuBar())
876 return tools::Rectangle();
878 assert(mpFrame == static_cast<QtFrame*>(pFrame));
879 assert(m_pButtonGroup);
880 auto* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
881 assert(pButton);
883 // unfortunatly, calling lcl_force_menubar_layout_update results in a temporary wrong menubar size,
884 // but it's the correct minimal size AFAIK and the layout seems correct, so just adjust the width.
885 QPoint aPos = pButton->mapTo(mpFrame->asChild(), QPoint());
886 aPos.rx() += (mpFrame->asChild()->width() - mpQMenuBar->width());
887 return tools::Rectangle(toPoint(aPos), toSize(pButton->size()));
890 void QtMenu::ShowCloseButton(bool bShow)
892 if (!validateQMenuBar())
893 return;
895 if (!bShow && !m_pButtonGroup)
896 return;
898 QPushButton* pButton = nullptr;
899 if (m_pButtonGroup)
900 pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
901 if (!bShow && !pButton)
902 return;
904 if (!pButton)
906 QIcon aIcon;
907 if (QIcon::hasThemeIcon("window-close-symbolic"))
908 aIcon = QIcon::fromTheme("window-close-symbolic");
909 else
910 aIcon = QIcon(
911 QPixmap::fromImage(toQImage(Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC))));
912 pButton = ImplAddMenuBarButton(aIcon, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)),
913 CLOSE_BUTTON_ID);
914 connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
917 if (bShow)
918 pButton->show();
919 else
920 pButton->hide();
922 lcl_force_menubar_layout_update(*mpQMenuBar);
925 bool QtMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
926 FloatWinPopupFlags nFlags)
928 assert(mpQMenu);
929 DoFullMenuUpdate(mpVCLMenu);
930 mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));
932 const VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
933 AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
935 QtFrame* pFrame = static_cast<QtFrame*>(pWin->ImplGetFrame());
936 assert(pFrame);
938 const QRect aRect = toQRect(aFloatRect, 1 / pFrame->devicePixelRatioF());
939 mpQMenu->exec(aRect.bottomLeft());
941 return true;
944 int QtMenu::GetMenuBarHeight() const
946 if (!validateQMenuBar() || mpQMenuBar->isHidden())
947 return 0;
949 return mpQMenuBar->height();
952 QtMenuItem::QtMenuItem(const SalItemParams* pItemData)
953 : mpParentMenu(nullptr)
954 , mpSubMenu(nullptr)
955 , mnId(pItemData->nId)
956 , mnType(pItemData->eType)
957 , mbVisible(true)
958 , mbEnabled(true)
959 , maImage(pItemData->aImage)
963 QAction* QtMenuItem::getAction() const
965 if (mpMenu)
966 return mpMenu->menuAction();
967 if (mpAction)
968 return mpAction.get();
969 return nullptr;
972 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */