bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / qt5 / QtMenu.cxx
blobb976fa3f5739c7e23116186932a554961e2f34a9
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/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>
37 #include <window.h>
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)
52 : mpVCLMenu(nullptr)
53 , mpParentSalMenu(nullptr)
54 , mpFrame(nullptr)
55 , mbMenuBar(bMenuBar)
56 , mpQMenuBar(nullptr)
57 , mpQMenu(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();
74 if (mbMenuBar)
76 // top-level menu
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);
87 else
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); });
104 else
106 if (!mpQMenu)
108 // no QMenu set, instantiate own one
109 mpOwnedQMenu.reset(new QMenu);
110 mpQMenu = mpOwnedQMenu.get();
113 if (pSalMenuItem->mpSubMenu)
115 // submenu
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);
124 else
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); });
142 else
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);
155 else
157 mpQMenu->addAction(pAction);
160 ReinitializeActionGroup(nPos);
162 else
164 // leaf menu
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);
173 else
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();
191 if (pAction)
193 pAction->setEnabled(pSalMenuItem->mbEnabled);
194 pAction->setVisible(pSalMenuItem->mbVisible);
198 void QtMenu::ReinitializeActionGroup(unsigned nPos)
200 const unsigned nCount = GetItemCount();
202 if (nCount == 0)
204 return;
207 if (nPos == MENU_APPEND)
209 nPos = nCount - 1;
211 else if (nPos >= nCount)
213 return;
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))
241 break;
244 pModifiedItem->mpActionGroup = pSecondActionGroup;
245 auto action = pModifiedItem->getAction();
247 if (actions.contains(action))
249 pFirstActionGroup->removeAction(action);
250 pSecondActionGroup->addAction(action);
255 else
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;
268 else
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))
304 break;
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();
325 if (!pAction)
326 return;
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);
342 else
344 pAction->setActionGroup(nullptr);
346 if (itemBits & MenuItemBits::CHECKABLE)
348 pAction->setCheckable(true);
349 pAction->setChecked(bChecked);
351 else
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);
366 else
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())
379 return;
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
390 if (nPos > 0)
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))
420 return;
423 InsertMenuItem(pItem, nPos);
426 void QtMenu::SetFrame(const SalFrame* pFrame)
428 auto* pSalInst(GetQtInstance());
429 assert(pSalInst);
430 if (!pSalInst->IsMainThread())
432 pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
433 return;
436 SolarMutexGuard aGuard;
437 assert(mbMenuBar);
438 mpFrame = const_cast<QtFrame*>(static_cast<const QtFrame*>(pFrame));
440 mpFrame->SetMenu(this);
442 QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
443 if (!pMainWindow)
444 return;
446 mpQMenuBar = new QMenuBar();
447 pMainWindow->setMenuBar(mpQMenuBar);
449 QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
450 if (pWidget)
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));
457 if (pButton)
458 connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
460 else
461 m_pButtonGroup = nullptr;
462 mpQMenu = 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();
500 if (pAction)
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();
521 if (pAction)
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();
535 if (pAction)
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();
545 if (pAction)
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();
561 if (!pAction)
562 return;
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();
572 if (pAction)
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;
583 return pMenu;
586 bool QtMenu::validateQMenuBar() const
588 if (!mpQMenuBar)
589 return false;
590 assert(mpFrame);
591 QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
592 assert(pMainWindow);
593 const bool bValid = mpQMenuBar == pMainWindow->menuBar();
594 if (!bValid)
596 QtMenu* thisPtr = const_cast<QtMenu*>(this);
597 thisPtr->mpQMenuBar = nullptr;
599 return bValid;
602 void QtMenu::ShowMenuBar(bool bVisible)
604 if (!validateQMenuBar())
605 return;
607 mpQMenuBar->setVisible(bVisible);
608 if (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)
623 if (!pQItem)
624 return;
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)
642 if (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)
656 if (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());
678 if (pVclMenuBar)
679 Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
682 void QtMenu::slotMenuBarButtonClicked(QAbstractButton* pButton)
684 MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
685 if (pVclMenuBar)
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())
695 return nullptr;
697 QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
698 QHBoxLayout* pLayout;
699 if (!pWidget)
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);
713 pWidget->show();
714 mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
716 else
717 pLayout = static_cast<QHBoxLayout*>(pWidget->layout());
718 assert(m_pButtonGroup);
719 assert(pLayout);
721 QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
722 if (pButton)
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))
738 nPos--;
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.
742 pButton->show();
744 lcl_force_menubar_layout_update(*mpQMenuBar);
746 return pButton;
749 bool QtMenu::AddMenuBarButton(const SalMenuButtonItem& rItem)
751 if (!validateQMenuBar())
752 return false;
753 return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem.maImage))),
754 toQString(rItem.maToolTipText), rItem.mnId);
757 void QtMenu::ImplRemoveMenuBarButton(int nId)
759 if (!validateQMenuBar())
760 return;
762 assert(m_pButtonGroup);
763 auto* pButton = m_pButtonGroup->button(nId);
764 assert(pButton);
765 QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
766 assert(pWidget);
767 QLayout* pLayout = pWidget->layout();
768 m_pButtonGroup->removeButton(pButton);
769 pLayout->removeWidget(pButton);
770 delete 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)
779 #ifdef NDEBUG
780 Q_UNUSED(pFrame);
781 #endif
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));
788 assert(pButton);
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())
800 return;
802 if (!bShow && !m_pButtonGroup)
803 return;
805 QPushButton* pButton = nullptr;
806 if (m_pButtonGroup)
807 pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
808 if (!bShow && !pButton)
809 return;
811 if (!pButton)
813 QIcon aIcon;
814 if (QIcon::hasThemeIcon("window-close-symbolic"))
815 aIcon = QIcon::fromTheme("window-close-symbolic");
816 else
817 aIcon = QIcon(
818 QPixmap::fromImage(toQImage(Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC))));
819 pButton = ImplAddMenuBarButton(aIcon, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)),
820 CLOSE_BUTTON_ID);
821 connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
824 if (bShow)
825 pButton->show();
826 else
827 pButton->hide();
829 lcl_force_menubar_layout_update(*mpQMenuBar);
832 bool QtMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
833 FloatWinPopupFlags nFlags)
835 assert(mpQMenu);
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());
844 assert(pFrame);
845 aFloatRect.SetPosY(aFloatRect.getY() + pFrame->menuBarOffset());
847 const QRect aRect = toQRect(aFloatRect, 1 / pFrame->devicePixelRatioF());
848 mpQMenu->exec(aRect.bottomLeft());
850 return true;
853 int QtMenu::GetMenuBarHeight() const
855 if (!validateQMenuBar() || mpQMenuBar->isHidden())
856 return 0;
858 return mpQMenuBar->height();
861 QtMenuItem::QtMenuItem(const SalItemParams* pItemData)
862 : mpParentMenu(nullptr)
863 , mpSubMenu(nullptr)
864 , mnId(pItemData->nId)
865 , mnType(pItemData->eType)
866 , mbVisible(true)
867 , mbEnabled(true)
868 , maImage(pItemData->aImage)
872 QAction* QtMenuItem::getAction() const
874 if (mpMenu)
875 return mpMenu->menuAction();
876 if (mpAction)
877 return mpAction.get();
878 return nullptr;
881 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */