1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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>
20 #include <QtGui/QActionGroup>
23 #include <QtWidgets/QButtonGroup>
24 #include <QtWidgets/QHBoxLayout>
25 #include <QtWidgets/QMenuBar>
26 #include <QtWidgets/QPushButton>
27 #include <QtWidgets/QStyle>
29 #include <o3tl/safeint.hxx>
30 #include <vcl/svapp.hxx>
31 #include <sal/log.hxx>
33 #include <strings.hrc>
34 #include <bitmaps.hlst>
36 #include <vcl/toolkit/floatwin.hxx>
39 // LO SalMenuButtonItem::mnId is sal_uInt16, so we go with -2, as -1 has a special meaning as automatic id
40 constexpr int CLOSE_BUTTON_ID
= -2;
41 const QString
gButtonGroupKey("QtMenu::ButtonGroup");
43 static inline void lcl_force_menubar_layout_update(QMenuBar
& rMenuBar
)
45 // just exists as a function to not comment it everywhere: forces reposition of the
46 // corner widget after its layout changes, which will otherwise just happen on resize.
47 // it unfortunatly has additional side effects; see QtMenu::GetMenuBarButtonRectPixel.
48 rMenuBar
.adjustSize();
51 QtMenu::QtMenu(bool bMenuBar
)
53 , mpParentSalMenu(nullptr)
58 , m_pButtonGroup(nullptr)
62 bool QtMenu::VisibleMenuBar() { return true; }
64 void QtMenu::InsertMenuItem(QtMenuItem
* pSalMenuItem
, unsigned nPos
)
66 sal_uInt16 nId
= pSalMenuItem
->mnId
;
67 OUString aText
= mpVCLMenu
->GetItemText(nId
);
68 NativeItemText(aText
);
69 vcl::KeyCode nAccelKey
= mpVCLMenu
->GetAccelKey(nId
);
71 pSalMenuItem
->mpAction
.reset();
72 pSalMenuItem
->mpMenu
.reset();
77 if (validateQMenuBar())
79 QMenu
* pQMenu
= new QMenu(toQString(aText
), nullptr);
80 pSalMenuItem
->mpMenu
.reset(pQMenu
);
82 if ((nPos
!= MENU_APPEND
)
83 && (static_cast<size_t>(nPos
) < o3tl::make_unsigned(mpQMenuBar
->actions().size())))
85 mpQMenuBar
->insertMenu(mpQMenuBar
->actions()[nPos
], pQMenu
);
89 mpQMenuBar
->addMenu(pQMenu
);
92 // correct parent menu for generated menu
93 if (pSalMenuItem
->mpSubMenu
)
95 pSalMenuItem
->mpSubMenu
->mpQMenu
= pQMenu
;
98 connect(pQMenu
, &QMenu::aboutToShow
, this,
99 [pSalMenuItem
] { slotMenuAboutToShow(pSalMenuItem
); });
100 connect(pQMenu
, &QMenu::aboutToHide
, this,
101 [pSalMenuItem
] { slotMenuAboutToHide(pSalMenuItem
); });
108 // no QMenu set, instantiate own one
109 mpOwnedQMenu
.reset(new QMenu
);
110 mpQMenu
= mpOwnedQMenu
.get();
113 if (pSalMenuItem
->mpSubMenu
)
116 QMenu
* pQMenu
= new QMenu(toQString(aText
), nullptr);
117 pSalMenuItem
->mpMenu
.reset(pQMenu
);
119 if ((nPos
!= MENU_APPEND
)
120 && (static_cast<size_t>(nPos
) < o3tl::make_unsigned(mpQMenu
->actions().size())))
122 mpQMenu
->insertMenu(mpQMenu
->actions()[nPos
], pQMenu
);
126 mpQMenu
->addMenu(pQMenu
);
129 // correct parent menu for generated menu
130 pSalMenuItem
->mpSubMenu
->mpQMenu
= pQMenu
;
132 ReinitializeActionGroup(nPos
);
134 // clear all action groups since menu is recreated
135 pSalMenuItem
->mpSubMenu
->ResetAllActionGroups();
137 connect(pQMenu
, &QMenu::aboutToShow
, this,
138 [pSalMenuItem
] { slotMenuAboutToShow(pSalMenuItem
); });
139 connect(pQMenu
, &QMenu::aboutToHide
, this,
140 [pSalMenuItem
] { slotMenuAboutToHide(pSalMenuItem
); });
144 if (pSalMenuItem
->mnType
== MenuItemType::SEPARATOR
)
146 QAction
* pAction
= new QAction(nullptr);
147 pSalMenuItem
->mpAction
.reset(pAction
);
148 pAction
->setSeparator(true);
150 if ((nPos
!= MENU_APPEND
)
151 && (static_cast<size_t>(nPos
) < o3tl::make_unsigned(mpQMenu
->actions().size())))
153 mpQMenu
->insertAction(mpQMenu
->actions()[nPos
], pAction
);
157 mpQMenu
->addAction(pAction
);
160 ReinitializeActionGroup(nPos
);
165 QAction
* pAction
= new QAction(toQString(aText
), nullptr);
166 pSalMenuItem
->mpAction
.reset(pAction
);
168 if ((nPos
!= MENU_APPEND
)
169 && (static_cast<size_t>(nPos
) < o3tl::make_unsigned(mpQMenu
->actions().size())))
171 mpQMenu
->insertAction(mpQMenu
->actions()[nPos
], pAction
);
175 mpQMenu
->addAction(pAction
);
178 ReinitializeActionGroup(nPos
);
180 UpdateActionGroupItem(pSalMenuItem
);
182 pAction
->setShortcut(toQString(nAccelKey
.GetName()));
184 connect(pAction
, &QAction::triggered
, this,
185 [pSalMenuItem
] { slotMenuTriggered(pSalMenuItem
); });
190 QAction
* pAction
= pSalMenuItem
->getAction();
193 pAction
->setEnabled(pSalMenuItem
->mbEnabled
);
194 pAction
->setVisible(pSalMenuItem
->mbVisible
);
198 void QtMenu::ReinitializeActionGroup(unsigned nPos
)
200 const unsigned nCount
= GetItemCount();
207 if (nPos
== MENU_APPEND
)
211 else if (nPos
>= nCount
)
216 QtMenuItem
* pPrevItem
= (nPos
> 0) ? GetItemAtPos(nPos
- 1) : nullptr;
217 QtMenuItem
* pCurrentItem
= GetItemAtPos(nPos
);
218 QtMenuItem
* pNextItem
= (nPos
< nCount
- 1) ? GetItemAtPos(nPos
+ 1) : nullptr;
220 if (pCurrentItem
->mnType
== MenuItemType::SEPARATOR
)
222 pCurrentItem
->mpActionGroup
.reset();
224 // if it's inserted into middle of existing group, split it into two groups:
225 // first goes original group, after separator goes new group
226 if (pPrevItem
&& pPrevItem
->mpActionGroup
&& pNextItem
&& pNextItem
->mpActionGroup
227 && (pPrevItem
->mpActionGroup
== pNextItem
->mpActionGroup
))
229 std::shared_ptr
<QActionGroup
> pFirstActionGroup
= pPrevItem
->mpActionGroup
;
230 auto pSecondActionGroup
= std::make_shared
<QActionGroup
>(nullptr);
231 pSecondActionGroup
->setExclusive(true);
233 auto actions
= pFirstActionGroup
->actions();
235 for (unsigned idx
= nPos
+ 1; idx
< nCount
; ++idx
)
237 QtMenuItem
* pModifiedItem
= GetItemAtPos(idx
);
239 if ((!pModifiedItem
) || (!pModifiedItem
->mpActionGroup
))
244 pModifiedItem
->mpActionGroup
= pSecondActionGroup
;
245 auto action
= pModifiedItem
->getAction();
247 if (actions
.contains(action
))
249 pFirstActionGroup
->removeAction(action
);
250 pSecondActionGroup
->addAction(action
);
257 if (!pCurrentItem
->mpActionGroup
)
259 // unless element is inserted between two separators, or a separator and an end of vector, use neighbouring group since it's shared
260 if (pPrevItem
&& pPrevItem
->mpActionGroup
)
262 pCurrentItem
->mpActionGroup
= pPrevItem
->mpActionGroup
;
264 else if (pNextItem
&& pNextItem
->mpActionGroup
)
266 pCurrentItem
->mpActionGroup
= pNextItem
->mpActionGroup
;
270 pCurrentItem
->mpActionGroup
= std::make_shared
<QActionGroup
>(nullptr);
271 pCurrentItem
->mpActionGroup
->setExclusive(true);
275 // if there's also a different group after this element, merge it
276 if (pNextItem
&& pNextItem
->mpActionGroup
277 && (pCurrentItem
->mpActionGroup
!= pNextItem
->mpActionGroup
))
279 auto pFirstCheckedAction
= pCurrentItem
->mpActionGroup
->checkedAction();
280 auto pSecondCheckedAction
= pNextItem
->mpActionGroup
->checkedAction();
281 auto actions
= pNextItem
->mpActionGroup
->actions();
283 // first move all actions from second group to first one, and if first group already has checked action,
284 // and second group also has a checked action, uncheck action from second group
285 for (auto action
: actions
)
287 pNextItem
->mpActionGroup
->removeAction(action
);
289 if (pFirstCheckedAction
&& pSecondCheckedAction
&& (action
== pSecondCheckedAction
))
291 action
->setChecked(false);
294 pCurrentItem
->mpActionGroup
->addAction(action
);
297 // now replace all pointers to second group with pointers to first group
298 for (unsigned idx
= nPos
+ 1; idx
< nCount
; ++idx
)
300 QtMenuItem
* pModifiedItem
= GetItemAtPos(idx
);
302 if ((!pModifiedItem
) || (!pModifiedItem
->mpActionGroup
))
307 pModifiedItem
->mpActionGroup
= pCurrentItem
->mpActionGroup
;
313 void QtMenu::ResetAllActionGroups()
315 for (unsigned nItem
= 0; nItem
< GetItemCount(); ++nItem
)
317 QtMenuItem
* pSalMenuItem
= GetItemAtPos(nItem
);
318 pSalMenuItem
->mpActionGroup
.reset();
322 void QtMenu::UpdateActionGroupItem(const QtMenuItem
* pSalMenuItem
)
324 QAction
* pAction
= pSalMenuItem
->getAction();
328 bool bChecked
= mpVCLMenu
->IsItemChecked(pSalMenuItem
->mnId
);
329 MenuItemBits itemBits
= mpVCLMenu
->GetItemBits(pSalMenuItem
->mnId
);
331 if (itemBits
& MenuItemBits::RADIOCHECK
)
333 pAction
->setCheckable(true);
335 if (pSalMenuItem
->mpActionGroup
)
337 pSalMenuItem
->mpActionGroup
->addAction(pAction
);
340 pAction
->setChecked(bChecked
);
344 pAction
->setActionGroup(nullptr);
346 if (itemBits
& MenuItemBits::CHECKABLE
)
348 pAction
->setCheckable(true);
349 pAction
->setChecked(bChecked
);
353 pAction
->setChecked(false);
354 pAction
->setCheckable(false);
359 void QtMenu::InsertItem(SalMenuItem
* pSalMenuItem
, unsigned nPos
)
361 SolarMutexGuard aGuard
;
362 QtMenuItem
* pItem
= static_cast<QtMenuItem
*>(pSalMenuItem
);
364 if (nPos
== MENU_APPEND
)
365 maItems
.push_back(pItem
);
367 maItems
.insert(maItems
.begin() + nPos
, pItem
);
369 pItem
->mpParentMenu
= this;
371 InsertMenuItem(pItem
, nPos
);
374 void QtMenu::RemoveItem(unsigned nPos
)
376 SolarMutexGuard aGuard
;
378 if (nPos
>= maItems
.size())
381 QtMenuItem
* pItem
= maItems
[nPos
];
382 pItem
->mpAction
.reset();
383 pItem
->mpMenu
.reset();
385 maItems
.erase(maItems
.begin() + nPos
);
387 // Recalculate action groups if necessary:
388 // if separator between two QActionGroups was removed,
389 // it may be needed to merge them
392 ReinitializeActionGroup(nPos
- 1);
396 void QtMenu::SetSubMenu(SalMenuItem
* pSalMenuItem
, SalMenu
* pSubMenu
, unsigned nPos
)
398 SolarMutexGuard aGuard
;
399 QtMenuItem
* pItem
= static_cast<QtMenuItem
*>(pSalMenuItem
);
400 QtMenu
* pQSubMenu
= static_cast<QtMenu
*>(pSubMenu
);
402 pItem
->mpSubMenu
= pQSubMenu
;
403 // at this point the pointer to parent menu may be outdated, update it too
404 pItem
->mpParentMenu
= this;
406 if (pQSubMenu
!= nullptr)
408 pQSubMenu
->mpParentSalMenu
= this;
409 pQSubMenu
->mpQMenu
= pItem
->mpMenu
.get();
412 // if it's not a menu bar item, then convert it to corresponding item if type if necessary.
413 // If submenu is present and it's an action, convert it to menu.
414 // If submenu is not present and it's a menu, convert it to action.
415 // It may be fine to proceed in any case, but by skipping other cases
416 // amount of unneeded actions taken should be reduced.
417 if (pItem
->mpParentMenu
->mbMenuBar
|| (pQSubMenu
&& pItem
->mpMenu
)
418 || ((!pQSubMenu
) && pItem
->mpAction
))
423 InsertMenuItem(pItem
, nPos
);
426 void QtMenu::SetFrame(const SalFrame
* pFrame
)
428 auto* pSalInst(GetQtInstance());
430 if (!pSalInst
->IsMainThread())
432 pSalInst
->RunInMainThread([this, pFrame
]() { SetFrame(pFrame
); });
436 SolarMutexGuard aGuard
;
438 mpFrame
= const_cast<QtFrame
*>(static_cast<const QtFrame
*>(pFrame
));
440 mpFrame
->SetMenu(this);
442 QtMainWindow
* pMainWindow
= mpFrame
->GetTopLevelWindow();
446 mpQMenuBar
= new QMenuBar();
447 pMainWindow
->setMenuBar(mpQMenuBar
);
449 QWidget
* pWidget
= mpQMenuBar
->cornerWidget(Qt::TopRightCorner
);
452 m_pButtonGroup
= pWidget
->findChild
<QButtonGroup
*>(gButtonGroupKey
);
453 assert(m_pButtonGroup
);
454 connect(m_pButtonGroup
, QOverload
<QAbstractButton
*>::of(&QButtonGroup::buttonClicked
), this,
455 &QtMenu::slotMenuBarButtonClicked
);
456 QPushButton
* pButton
= static_cast<QPushButton
*>(m_pButtonGroup
->button(CLOSE_BUTTON_ID
));
458 connect(pButton
, &QPushButton::clicked
, this, &QtMenu::slotCloseDocument
);
461 m_pButtonGroup
= nullptr;
464 DoFullMenuUpdate(mpVCLMenu
);
467 void QtMenu::DoFullMenuUpdate(Menu
* pMenuBar
)
469 // clear action groups since menu is rebuilt
470 ResetAllActionGroups();
471 ShowCloseButton(false);
473 for (sal_Int32 nItem
= 0; nItem
< static_cast<sal_Int32
>(GetItemCount()); nItem
++)
475 QtMenuItem
* pSalMenuItem
= GetItemAtPos(nItem
);
476 InsertMenuItem(pSalMenuItem
, nItem
);
477 SetItemImage(nItem
, pSalMenuItem
, pSalMenuItem
->maImage
);
478 const bool bShowDisabled
479 = bool(pMenuBar
->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries
)
480 || !bool(pMenuBar
->GetMenuFlags() & MenuFlags::HideDisabledEntries
);
481 const bool bVisible
= pSalMenuItem
->mbVisible
482 && (bShowDisabled
|| mpVCLMenu
->IsItemEnabled(pSalMenuItem
->mnId
));
483 pSalMenuItem
->getAction()->setVisible(bVisible
);
485 if (pSalMenuItem
->mpSubMenu
!= nullptr)
487 pMenuBar
->HandleMenuActivateEvent(pSalMenuItem
->mpSubMenu
->GetMenu());
488 pSalMenuItem
->mpSubMenu
->DoFullMenuUpdate(pMenuBar
);
489 pMenuBar
->HandleMenuDeActivateEvent(pSalMenuItem
->mpSubMenu
->GetMenu());
494 void QtMenu::ShowItem(unsigned nPos
, bool bShow
)
496 if (nPos
< maItems
.size())
498 QtMenuItem
* pSalMenuItem
= GetItemAtPos(nPos
);
499 QAction
* pAction
= pSalMenuItem
->getAction();
501 pAction
->setVisible(bShow
);
502 pSalMenuItem
->mbVisible
= bShow
;
506 void QtMenu::SetItemBits(unsigned nPos
, MenuItemBits
)
508 if (nPos
< maItems
.size())
510 QtMenuItem
* pSalMenuItem
= GetItemAtPos(nPos
);
511 UpdateActionGroupItem(pSalMenuItem
);
515 void QtMenu::CheckItem(unsigned nPos
, bool bChecked
)
517 if (nPos
< maItems
.size())
519 QtMenuItem
* pSalMenuItem
= GetItemAtPos(nPos
);
520 QAction
* pAction
= pSalMenuItem
->getAction();
523 pAction
->setCheckable(true);
524 pAction
->setChecked(bChecked
);
529 void QtMenu::EnableItem(unsigned nPos
, bool bEnable
)
531 if (nPos
< maItems
.size())
533 QtMenuItem
* pSalMenuItem
= GetItemAtPos(nPos
);
534 QAction
* pAction
= pSalMenuItem
->getAction();
536 pAction
->setEnabled(bEnable
);
537 pSalMenuItem
->mbEnabled
= bEnable
;
541 void QtMenu::SetItemText(unsigned, SalMenuItem
* pItem
, const OUString
& rText
)
543 QtMenuItem
* pSalMenuItem
= static_cast<QtMenuItem
*>(pItem
);
544 QAction
* pAction
= pSalMenuItem
->getAction();
547 OUString
aText(rText
);
548 NativeItemText(aText
);
549 pAction
->setText(toQString(aText
));
553 void QtMenu::SetItemImage(unsigned, SalMenuItem
* pItem
, const Image
& rImage
)
555 QtMenuItem
* pSalMenuItem
= static_cast<QtMenuItem
*>(pItem
);
557 // Save new image to use it in DoFullMenuUpdate
558 pSalMenuItem
->maImage
= rImage
;
560 QAction
* pAction
= pSalMenuItem
->getAction();
564 pAction
->setIcon(QPixmap::fromImage(toQImage(rImage
)));
567 void QtMenu::SetAccelerator(unsigned, SalMenuItem
* pItem
, const vcl::KeyCode
&,
568 const OUString
& rText
)
570 QtMenuItem
* pSalMenuItem
= static_cast<QtMenuItem
*>(pItem
);
571 QAction
* pAction
= pSalMenuItem
->getAction();
573 pAction
->setShortcut(QKeySequence(toQString(rText
), QKeySequence::PortableText
));
576 void QtMenu::GetSystemMenuData(SystemMenuData
*) {}
578 QtMenu
* QtMenu::GetTopLevel()
580 QtMenu
* pMenu
= this;
581 while (pMenu
->mpParentSalMenu
)
582 pMenu
= pMenu
->mpParentSalMenu
;
586 bool QtMenu::validateQMenuBar() const
591 QtMainWindow
* pMainWindow
= mpFrame
->GetTopLevelWindow();
593 const bool bValid
= mpQMenuBar
== pMainWindow
->menuBar();
596 QtMenu
* thisPtr
= const_cast<QtMenu
*>(this);
597 thisPtr
->mpQMenuBar
= nullptr;
602 void QtMenu::ShowMenuBar(bool bVisible
)
604 if (!validateQMenuBar())
607 mpQMenuBar
->setVisible(bVisible
);
609 lcl_force_menubar_layout_update(*mpQMenuBar
);
612 const QtFrame
* QtMenu::GetFrame() const
614 SolarMutexGuard aGuard
;
615 const QtMenu
* pMenu
= this;
616 while (pMenu
&& !pMenu
->mpFrame
)
617 pMenu
= pMenu
->mpParentSalMenu
;
618 return pMenu
? pMenu
->mpFrame
: nullptr;
621 void QtMenu::slotMenuTriggered(QtMenuItem
* pQItem
)
626 QtMenu
* pSalMenu
= pQItem
->mpParentMenu
;
627 QtMenu
* pTopLevel
= pSalMenu
->GetTopLevel();
629 Menu
* pMenu
= pSalMenu
->GetMenu();
630 auto mnId
= pQItem
->mnId
;
632 // HACK to allow HandleMenuCommandEvent to "not-set" the checked button
633 // LO expects a signal before an item state change, so reset the check item
634 if (pQItem
->mpAction
->isCheckable()
635 && (!pQItem
->mpActionGroup
|| pQItem
->mpActionGroup
->actions().size() <= 1))
636 pQItem
->mpAction
->setChecked(!pQItem
->mpAction
->isChecked());
637 pTopLevel
->GetMenu()->HandleMenuCommandEvent(pMenu
, mnId
);
640 void QtMenu::slotMenuAboutToShow(QtMenuItem
* pQItem
)
644 QtMenu
* pSalMenu
= pQItem
->mpSubMenu
;
645 QtMenu
* pTopLevel
= pSalMenu
->GetTopLevel();
647 Menu
* pMenu
= pSalMenu
->GetMenu();
649 // following function may update the menu
650 pTopLevel
->GetMenu()->HandleMenuActivateEvent(pMenu
);
654 void QtMenu::slotMenuAboutToHide(QtMenuItem
* pQItem
)
658 QtMenu
* pSalMenu
= pQItem
->mpSubMenu
;
659 QtMenu
* pTopLevel
= pSalMenu
->GetTopLevel();
661 Menu
* pMenu
= pSalMenu
->GetMenu();
663 pTopLevel
->GetMenu()->HandleMenuDeActivateEvent(pMenu
);
667 void QtMenu::NativeItemText(OUString
& rItemText
)
669 // preserve literal '&'s in menu texts
670 rItemText
= rItemText
.replaceAll("&", "&&");
672 rItemText
= rItemText
.replace('~', '&');
675 void QtMenu::slotCloseDocument()
677 MenuBar
* pVclMenuBar
= static_cast<MenuBar
*>(mpVCLMenu
.get());
679 Application::PostUserEvent(pVclMenuBar
->GetCloseButtonClickHdl());
682 void QtMenu::slotMenuBarButtonClicked(QAbstractButton
* pButton
)
684 MenuBar
* pVclMenuBar
= static_cast<MenuBar
*>(mpVCLMenu
.get());
687 SolarMutexGuard aGuard
;
688 pVclMenuBar
->HandleMenuButtonEvent(m_pButtonGroup
->id(pButton
));
692 QPushButton
* QtMenu::ImplAddMenuBarButton(const QIcon
& rIcon
, const QString
& rToolTip
, int nId
)
694 if (!validateQMenuBar())
697 QWidget
* pWidget
= mpQMenuBar
->cornerWidget(Qt::TopRightCorner
);
698 QHBoxLayout
* pLayout
;
701 assert(!m_pButtonGroup
);
702 pWidget
= new QWidget(mpQMenuBar
);
703 assert(!pWidget
->layout());
704 pLayout
= new QHBoxLayout();
705 pLayout
->setContentsMargins(QMargins());
706 pLayout
->setSpacing(0);
707 pWidget
->setLayout(pLayout
);
708 m_pButtonGroup
= new QButtonGroup(pLayout
);
709 m_pButtonGroup
->setObjectName(gButtonGroupKey
);
710 m_pButtonGroup
->setExclusive(false);
711 connect(m_pButtonGroup
, QOverload
<QAbstractButton
*>::of(&QButtonGroup::buttonClicked
), this,
712 &QtMenu::slotMenuBarButtonClicked
);
714 mpQMenuBar
->setCornerWidget(pWidget
, Qt::TopRightCorner
);
717 pLayout
= static_cast<QHBoxLayout
*>(pWidget
->layout());
718 assert(m_pButtonGroup
);
721 QPushButton
* pButton
= static_cast<QPushButton
*>(m_pButtonGroup
->button(nId
));
723 ImplRemoveMenuBarButton(nId
);
725 pButton
= new QPushButton();
726 // we don't want the button to increase the QMenuBar height, so a fixed size square it is
727 const int nFixedLength
728 = mpQMenuBar
->height() - 2 * mpQMenuBar
->style()->pixelMetric(QStyle::PM_MenuBarVMargin
);
729 pButton
->setFixedSize(nFixedLength
, nFixedLength
);
730 pButton
->setIcon(rIcon
);
731 pButton
->setFlat(true);
732 pButton
->setFocusPolicy(Qt::NoFocus
);
733 pButton
->setToolTip(rToolTip
);
735 m_pButtonGroup
->addButton(pButton
, nId
);
736 int nPos
= pLayout
->count();
737 if (m_pButtonGroup
->button(CLOSE_BUTTON_ID
))
739 pLayout
->insertWidget(nPos
, pButton
, 0, Qt::AlignCenter
);
740 // show must happen after adding the button to the layout, otherwise the button is
741 // shown, but not correct in the layout, if at all! Some times the layout ignores it.
744 lcl_force_menubar_layout_update(*mpQMenuBar
);
749 bool QtMenu::AddMenuBarButton(const SalMenuButtonItem
& rItem
)
751 if (!validateQMenuBar())
753 return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem
.maImage
))),
754 toQString(rItem
.maToolTipText
), rItem
.mnId
);
757 void QtMenu::ImplRemoveMenuBarButton(int nId
)
759 if (!validateQMenuBar())
762 assert(m_pButtonGroup
);
763 auto* pButton
= m_pButtonGroup
->button(nId
);
765 QWidget
* pWidget
= mpQMenuBar
->cornerWidget(Qt::TopRightCorner
);
767 QLayout
* pLayout
= pWidget
->layout();
768 m_pButtonGroup
->removeButton(pButton
);
769 pLayout
->removeWidget(pButton
);
772 lcl_force_menubar_layout_update(*mpQMenuBar
);
775 void QtMenu::RemoveMenuBarButton(sal_uInt16 nId
) { ImplRemoveMenuBarButton(nId
); }
777 tools::Rectangle
QtMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId
, SalFrame
* pFrame
)
782 if (!validateQMenuBar())
783 return tools::Rectangle();
785 assert(mpFrame
== static_cast<QtFrame
*>(pFrame
));
786 assert(m_pButtonGroup
);
787 auto* pButton
= static_cast<QPushButton
*>(m_pButtonGroup
->button(nId
));
790 // unfortunatly, calling lcl_force_menubar_layout_update results in a temporary wrong menubar size,
791 // but it's the correct minimal size AFAIK and the layout seems correct, so just adjust the width.
792 QPoint aPos
= pButton
->mapTo(mpFrame
->asChild(), QPoint());
793 aPos
.rx() += (mpFrame
->asChild()->width() - mpQMenuBar
->width());
794 return tools::Rectangle(toPoint(aPos
), toSize(pButton
->size()));
797 void QtMenu::ShowCloseButton(bool bShow
)
799 if (!validateQMenuBar())
802 if (!bShow
&& !m_pButtonGroup
)
805 QPushButton
* pButton
= nullptr;
807 pButton
= static_cast<QPushButton
*>(m_pButtonGroup
->button(CLOSE_BUTTON_ID
));
808 if (!bShow
&& !pButton
)
814 if (QIcon::hasThemeIcon("window-close-symbolic"))
815 aIcon
= QIcon::fromTheme("window-close-symbolic");
818 QPixmap::fromImage(toQImage(Image(StockImage::Yes
, SV_RESID_BITMAP_CLOSEDOC
))));
819 pButton
= ImplAddMenuBarButton(aIcon
, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT
)),
821 connect(pButton
, &QPushButton::clicked
, this, &QtMenu::slotCloseDocument
);
829 lcl_force_menubar_layout_update(*mpQMenuBar
);
832 bool QtMenu::ShowNativePopupMenu(FloatingWindow
* pWin
, const tools::Rectangle
& rRect
,
833 FloatWinPopupFlags nFlags
)
836 DoFullMenuUpdate(mpVCLMenu
);
837 mpQMenu
->setTearOffEnabled(bool(nFlags
& FloatWinPopupFlags::AllowTearOff
));
839 const VclPtr
<vcl::Window
> xParent
= pWin
->ImplGetWindowImpl()->mpRealParent
;
840 tools::Rectangle aFloatRect
= FloatingWindow::ImplConvertToAbsPos(xParent
, rRect
);
842 // tdf#154447 Menu bar height has to be added
843 QtFrame
* pFrame
= static_cast<QtFrame
*>(pWin
->ImplGetFrame());
845 aFloatRect
.SetPosY(aFloatRect
.getY() + pFrame
->menuBarOffset());
847 const QRect aRect
= toQRect(aFloatRect
, 1 / pFrame
->devicePixelRatioF());
848 mpQMenu
->exec(aRect
.bottomLeft());
853 int QtMenu::GetMenuBarHeight() const
855 if (!validateQMenuBar() || mpQMenuBar
->isHidden())
858 return mpQMenuBar
->height();
861 QtMenuItem::QtMenuItem(const SalItemParams
* pItemData
)
862 : mpParentMenu(nullptr)
864 , mnId(pItemData
->nId
)
865 , mnType(pItemData
->eType
)
868 , maImage(pItemData
->aImage
)
872 QAction
* QtMenuItem::getAction() const
875 return mpMenu
->menuAction();
877 return mpAction
.get();
881 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */