1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
10 #include <QtCustomStyle.hxx>
11 #include <vcl/themecolors.hxx>
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>
22 #include <QtGui/QActionGroup>
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>
32 #include <QtWidgets/QShortcut>
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>
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
)
64 , mpParentSalMenu(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
)
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();
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());
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();
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
);
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
); });
150 // no QMenu set, instantiate own one
151 mpOwnedQMenu
.reset(new QMenu
);
152 mpQMenu
= mpOwnedQMenu
.get();
153 connectHelpSignalSlots(mpQMenu
, pSalMenuItem
);
156 if (pSalMenuItem
->mpSubMenu
)
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
);
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
); });
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
);
201 mpQMenu
->addAction(pAction
);
204 ReinitializeActionGroup(nPos
);
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
);
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();
240 pAction
->setEnabled(pSalMenuItem
->mbEnabled
);
241 pAction
->setVisible(pSalMenuItem
->mbVisible
);
245 void QtMenu::ReinitializeActionGroup(unsigned nPos
)
247 const unsigned nCount
= GetItemCount();
254 if (nPos
== MENU_APPEND
)
258 else if (nPos
>= nCount
)
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
))
291 pModifiedItem
->mpActionGroup
= pSecondActionGroup
;
292 auto action
= pModifiedItem
->getAction();
294 if (actions
.contains(action
))
296 pFirstActionGroup
->removeAction(action
);
297 pSecondActionGroup
->addAction(action
);
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
;
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
))
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();
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
);
391 pAction
->setActionGroup(nullptr);
393 if (itemBits
& MenuItemBits::CHECKABLE
)
395 pAction
->setCheckable(true);
396 pAction
->setChecked(bChecked
);
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
);
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())
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
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
))
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
); });
482 SolarMutexGuard aGuard
;
484 mpFrame
= const_cast<QtFrame
*>(static_cast<const QtFrame
*>(pFrame
));
486 mpFrame
->SetMenu(this);
488 QtMainWindow
* pMainWindow
= mpFrame
->GetTopLevelWindow();
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
);
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
));
510 connect(pButton
, &QPushButton::clicked
, this, &QtMenu::slotCloseDocument
);
513 m_pButtonGroup
= 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();
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();
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();
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();
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();
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();
637 pAction
->setShortcut(QKeySequence(toQString(rText
), QKeySequence::PortableText
));
640 QtMenu
* QtMenu::GetTopLevel()
642 QtMenu
* pMenu
= this;
643 while (pMenu
->mpParentSalMenu
)
644 pMenu
= pMenu
->mpParentSalMenu
;
648 bool QtMenu::validateQMenuBar() const
653 QtMainWindow
* pMainWindow
= mpFrame
->GetTopLevelWindow();
655 const bool bValid
= mpQMenuBar
== pMainWindow
->menuBar();
658 QtMenu
* thisPtr
= const_cast<QtMenu
*>(this);
659 thisPtr
->mpQMenuBar
= nullptr;
664 void QtMenu::ShowMenuBar(bool bVisible
)
666 if (!validateQMenuBar())
669 mpQMenuBar
->setVisible(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
)
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
)
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
)
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());
740 Application::PostUserEvent(pVclMenuBar
->GetCloseButtonClickHdl());
743 void QtMenu::slotMenuBarButtonClicked(QAbstractButton
* pButton
)
745 MenuBar
* pVclMenuBar
= static_cast<MenuBar
*>(mpVCLMenu
.get());
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())
767 QWidget
* pWidget
= mpQMenuBar
->cornerWidget(Qt::TopRightCorner
);
768 QHBoxLayout
* pLayout
;
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
);
784 mpQMenuBar
->setCornerWidget(pWidget
, Qt::TopRightCorner
);
787 pLayout
= static_cast<QHBoxLayout
*>(pWidget
->layout());
788 assert(m_pButtonGroup
);
791 QPushButton
* pButton
= static_cast<QPushButton
*>(m_pButtonGroup
->button(nId
));
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
))
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.
814 lcl_force_menubar_layout_update(*mpQMenuBar
);
819 bool QtMenu::AddMenuBarButton(const SalMenuButtonItem
& rItem
)
821 if (!validateQMenuBar())
823 return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem
.maImage
))),
824 toQString(rItem
.maToolTipText
), rItem
.mnId
);
827 void QtMenu::ImplRemoveMenuBarButton(int nId
)
829 if (!validateQMenuBar())
832 assert(m_pButtonGroup
);
833 auto* pButton
= m_pButtonGroup
->button(nId
);
835 QWidget
* pWidget
= mpQMenuBar
->cornerWidget(Qt::TopRightCorner
);
837 QLayout
* pLayout
= pWidget
->layout();
838 m_pButtonGroup
->removeButton(pButton
);
839 pLayout
->removeWidget(pButton
);
842 lcl_force_menubar_layout_update(*mpQMenuBar
);
845 void QtMenu::connectHelpShortcut(QMenu
* 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();
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
)
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
));
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())
895 if (!bShow
&& !m_pButtonGroup
)
898 QPushButton
* pButton
= nullptr;
900 pButton
= static_cast<QPushButton
*>(m_pButtonGroup
->button(CLOSE_BUTTON_ID
));
901 if (!bShow
&& !pButton
)
907 if (QIcon::hasThemeIcon("window-close-symbolic"))
908 aIcon
= QIcon::fromTheme("window-close-symbolic");
911 QPixmap::fromImage(toQImage(Image(StockImage::Yes
, SV_RESID_BITMAP_CLOSEDOC
))));
912 pButton
= ImplAddMenuBarButton(aIcon
, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT
)),
914 connect(pButton
, &QPushButton::clicked
, this, &QtMenu::slotCloseDocument
);
922 lcl_force_menubar_layout_update(*mpQMenuBar
);
925 bool QtMenu::ShowNativePopupMenu(FloatingWindow
* pWin
, const tools::Rectangle
& rRect
,
926 FloatWinPopupFlags nFlags
)
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());
938 const QRect aRect
= toQRect(aFloatRect
, 1 / pFrame
->devicePixelRatioF());
939 mpQMenu
->exec(aRect
.bottomLeft());
944 int QtMenu::GetMenuBarHeight() const
946 if (!validateQMenuBar() || mpQMenuBar
->isHidden())
949 return mpQMenuBar
->height();
952 QtMenuItem::QtMenuItem(const SalItemParams
* pItemData
)
953 : mpParentMenu(nullptr)
955 , mnId(pItemData
->nId
)
956 , mnType(pItemData
->eType
)
959 , maImage(pItemData
->aImage
)
963 QAction
* QtMenuItem::getAction() const
966 return mpMenu
->menuAction();
968 return mpAction
.get();
972 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */