diff options
Diffstat (limited to 'vcl/qt5/QtMenu.cxx')
-rw-r--r-- | vcl/qt5/QtMenu.cxx | 920 |
1 files changed, 920 insertions, 0 deletions
diff --git a/vcl/qt5/QtMenu.cxx b/vcl/qt5/QtMenu.cxx new file mode 100644 index 0000000000..93f3d6f5a3 --- /dev/null +++ b/vcl/qt5/QtMenu.cxx @@ -0,0 +1,920 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <QtMenu.hxx> +#include <QtMenu.moc> + +#include <QtFrame.hxx> +#include <QtInstance.hxx> +#include <QtMainWindow.hxx> + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include <QtWidgets/QActionGroup> +#else +#include <QtGui/QActionGroup> +#endif + +#include <QtWidgets/QButtonGroup> +#include <QtWidgets/QHBoxLayout> +#include <QtWidgets/QMenuBar> +#include <QtWidgets/QPushButton> +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include <QtGui/QShortcut> +#else +#include <QtWidgets/QShortcut> +#endif +#include <QtWidgets/QStyle> + +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +#include <strings.hrc> +#include <bitmaps.hlst> + +#include <vcl/toolkit/floatwin.hxx> +#include <window.h> + +// LO SalMenuButtonItem::mnId is sal_uInt16, so we go with -2, as -1 has a special meaning as automatic id +constexpr int CLOSE_BUTTON_ID = -2; +const QString gButtonGroupKey("QtMenu::ButtonGroup"); + +static inline void lcl_force_menubar_layout_update(QMenuBar& rMenuBar) +{ + // just exists as a function to not comment it everywhere: forces reposition of the + // corner widget after its layout changes, which will otherwise just happen on resize. + // it unfortunatly has additional side effects; see QtMenu::GetMenuBarButtonRectPixel. + rMenuBar.adjustSize(); +} + +OUString QtMenu::m_sCurrentHelpId = u""_ustr; + +QtMenu::QtMenu(bool bMenuBar) + : mpVCLMenu(nullptr) + , mpParentSalMenu(nullptr) + , mpFrame(nullptr) + , mbMenuBar(bMenuBar) + , mpQMenuBar(nullptr) + , mpQMenu(nullptr) + , m_pButtonGroup(nullptr) +{ +} + +bool QtMenu::VisibleMenuBar() { return true; } + +void QtMenu::InsertMenuItem(QtMenuItem* pSalMenuItem, unsigned nPos) +{ + sal_uInt16 nId = pSalMenuItem->mnId; + OUString aText = mpVCLMenu->GetItemText(nId); + NativeItemText(aText); + vcl::KeyCode nAccelKey = mpVCLMenu->GetAccelKey(nId); + + pSalMenuItem->mpAction.reset(); + pSalMenuItem->mpMenu.reset(); + + if (mbMenuBar) + { + // top-level menu + if (validateQMenuBar()) + { + QMenu* pQMenu = new QMenu(toQString(aText), nullptr); + connectHelpSignalSlots(pQMenu, pSalMenuItem); + pSalMenuItem->mpMenu.reset(pQMenu); + + if ((nPos != MENU_APPEND) + && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenuBar->actions().size()))) + { + mpQMenuBar->insertMenu(mpQMenuBar->actions()[nPos], pQMenu); + } + else + { + mpQMenuBar->addMenu(pQMenu); + } + + // correct parent menu for generated menu + if (pSalMenuItem->mpSubMenu) + { + pSalMenuItem->mpSubMenu->mpQMenu = pQMenu; + } + + connect(pQMenu, &QMenu::aboutToShow, this, + [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); }); + connect(pQMenu, &QMenu::aboutToHide, this, + [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); }); + } + } + else + { + if (!mpQMenu) + { + // no QMenu set, instantiate own one + mpOwnedQMenu.reset(new QMenu); + mpQMenu = mpOwnedQMenu.get(); + connectHelpSignalSlots(mpQMenu, pSalMenuItem); + } + + if (pSalMenuItem->mpSubMenu) + { + // submenu + QMenu* pQMenu = new QMenu(toQString(aText), nullptr); + connectHelpSignalSlots(pQMenu, pSalMenuItem); + pSalMenuItem->mpMenu.reset(pQMenu); + + if ((nPos != MENU_APPEND) + && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size()))) + { + mpQMenu->insertMenu(mpQMenu->actions()[nPos], pQMenu); + } + else + { + mpQMenu->addMenu(pQMenu); + } + + // correct parent menu for generated menu + pSalMenuItem->mpSubMenu->mpQMenu = pQMenu; + + ReinitializeActionGroup(nPos); + + // clear all action groups since menu is recreated + pSalMenuItem->mpSubMenu->ResetAllActionGroups(); + + connect(pQMenu, &QMenu::aboutToShow, this, + [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); }); + connect(pQMenu, &QMenu::aboutToHide, this, + [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); }); + } + else + { + if (pSalMenuItem->mnType == MenuItemType::SEPARATOR) + { + QAction* pAction = new QAction(nullptr); + pSalMenuItem->mpAction.reset(pAction); + pAction->setSeparator(true); + + if ((nPos != MENU_APPEND) + && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size()))) + { + mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction); + } + else + { + mpQMenu->addAction(pAction); + } + + ReinitializeActionGroup(nPos); + } + else + { + // leaf menu + QAction* pAction = new QAction(toQString(aText), nullptr); + pSalMenuItem->mpAction.reset(pAction); + + if ((nPos != MENU_APPEND) + && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size()))) + { + mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction); + } + else + { + mpQMenu->addAction(pAction); + } + + ReinitializeActionGroup(nPos); + + UpdateActionGroupItem(pSalMenuItem); + + pAction->setShortcut(toQString(nAccelKey.GetName())); + + connect(pAction, &QAction::triggered, this, + [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); }); + connect(pAction, &QAction::hovered, this, + [pSalMenuItem] { slotMenuHovered(pSalMenuItem); }); + } + } + } + + QAction* pAction = pSalMenuItem->getAction(); + if (pAction) + { + pAction->setEnabled(pSalMenuItem->mbEnabled); + pAction->setVisible(pSalMenuItem->mbVisible); + } +} + +void QtMenu::ReinitializeActionGroup(unsigned nPos) +{ + const unsigned nCount = GetItemCount(); + + if (nCount == 0) + { + return; + } + + if (nPos == MENU_APPEND) + { + nPos = nCount - 1; + } + else if (nPos >= nCount) + { + return; + } + + QtMenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr; + QtMenuItem* pCurrentItem = GetItemAtPos(nPos); + QtMenuItem* pNextItem = (nPos < nCount - 1) ? GetItemAtPos(nPos + 1) : nullptr; + + if (pCurrentItem->mnType == MenuItemType::SEPARATOR) + { + pCurrentItem->mpActionGroup.reset(); + + // if it's inserted into middle of existing group, split it into two groups: + // first goes original group, after separator goes new group + if (pPrevItem && pPrevItem->mpActionGroup && pNextItem && pNextItem->mpActionGroup + && (pPrevItem->mpActionGroup == pNextItem->mpActionGroup)) + { + std::shared_ptr<QActionGroup> pFirstActionGroup = pPrevItem->mpActionGroup; + auto pSecondActionGroup = std::make_shared<QActionGroup>(nullptr); + pSecondActionGroup->setExclusive(true); + + auto actions = pFirstActionGroup->actions(); + + for (unsigned idx = nPos + 1; idx < nCount; ++idx) + { + QtMenuItem* pModifiedItem = GetItemAtPos(idx); + + if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup)) + { + break; + } + + pModifiedItem->mpActionGroup = pSecondActionGroup; + auto action = pModifiedItem->getAction(); + + if (actions.contains(action)) + { + pFirstActionGroup->removeAction(action); + pSecondActionGroup->addAction(action); + } + } + } + } + else + { + if (!pCurrentItem->mpActionGroup) + { + // unless element is inserted between two separators, or a separator and an end of vector, use neighbouring group since it's shared + if (pPrevItem && pPrevItem->mpActionGroup) + { + pCurrentItem->mpActionGroup = pPrevItem->mpActionGroup; + } + else if (pNextItem && pNextItem->mpActionGroup) + { + pCurrentItem->mpActionGroup = pNextItem->mpActionGroup; + } + else + { + pCurrentItem->mpActionGroup = std::make_shared<QActionGroup>(nullptr); + pCurrentItem->mpActionGroup->setExclusive(true); + } + } + + // if there's also a different group after this element, merge it + if (pNextItem && pNextItem->mpActionGroup + && (pCurrentItem->mpActionGroup != pNextItem->mpActionGroup)) + { + auto pFirstCheckedAction = pCurrentItem->mpActionGroup->checkedAction(); + auto pSecondCheckedAction = pNextItem->mpActionGroup->checkedAction(); + auto actions = pNextItem->mpActionGroup->actions(); + + // first move all actions from second group to first one, and if first group already has checked action, + // and second group also has a checked action, uncheck action from second group + for (auto action : actions) + { + pNextItem->mpActionGroup->removeAction(action); + + if (pFirstCheckedAction && pSecondCheckedAction && (action == pSecondCheckedAction)) + { + action->setChecked(false); + } + + pCurrentItem->mpActionGroup->addAction(action); + } + + // now replace all pointers to second group with pointers to first group + for (unsigned idx = nPos + 1; idx < nCount; ++idx) + { + QtMenuItem* pModifiedItem = GetItemAtPos(idx); + + if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup)) + { + break; + } + + pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup; + } + } + } +} + +void QtMenu::ResetAllActionGroups() +{ + for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem) + { + QtMenuItem* pSalMenuItem = GetItemAtPos(nItem); + pSalMenuItem->mpActionGroup.reset(); + } +} + +void QtMenu::UpdateActionGroupItem(const QtMenuItem* pSalMenuItem) +{ + QAction* pAction = pSalMenuItem->getAction(); + if (!pAction) + return; + + bool bChecked = mpVCLMenu->IsItemChecked(pSalMenuItem->mnId); + MenuItemBits itemBits = mpVCLMenu->GetItemBits(pSalMenuItem->mnId); + + if (itemBits & MenuItemBits::RADIOCHECK) + { + pAction->setCheckable(true); + + if (pSalMenuItem->mpActionGroup) + { + pSalMenuItem->mpActionGroup->addAction(pAction); + } + + pAction->setChecked(bChecked); + } + else + { + pAction->setActionGroup(nullptr); + + if (itemBits & MenuItemBits::CHECKABLE) + { + pAction->setCheckable(true); + pAction->setChecked(bChecked); + } + else + { + pAction->setChecked(false); + pAction->setCheckable(false); + } + } +} + +void QtMenu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos) +{ + SolarMutexGuard aGuard; + QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem); + + if (nPos == MENU_APPEND) + maItems.push_back(pItem); + else + maItems.insert(maItems.begin() + nPos, pItem); + + pItem->mpParentMenu = this; + + InsertMenuItem(pItem, nPos); +} + +void QtMenu::RemoveItem(unsigned nPos) +{ + SolarMutexGuard aGuard; + + if (nPos >= maItems.size()) + return; + + QtMenuItem* pItem = maItems[nPos]; + pItem->mpAction.reset(); + pItem->mpMenu.reset(); + + maItems.erase(maItems.begin() + nPos); + + // Recalculate action groups if necessary: + // if separator between two QActionGroups was removed, + // it may be needed to merge them + if (nPos > 0) + { + ReinitializeActionGroup(nPos - 1); + } +} + +void QtMenu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos) +{ + SolarMutexGuard aGuard; + QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem); + QtMenu* pQSubMenu = static_cast<QtMenu*>(pSubMenu); + + pItem->mpSubMenu = pQSubMenu; + // at this point the pointer to parent menu may be outdated, update it too + pItem->mpParentMenu = this; + + if (pQSubMenu != nullptr) + { + pQSubMenu->mpParentSalMenu = this; + pQSubMenu->mpQMenu = pItem->mpMenu.get(); + } + + // if it's not a menu bar item, then convert it to corresponding item if type if necessary. + // If submenu is present and it's an action, convert it to menu. + // If submenu is not present and it's a menu, convert it to action. + // It may be fine to proceed in any case, but by skipping other cases + // amount of unneeded actions taken should be reduced. + if (pItem->mpParentMenu->mbMenuBar || (pQSubMenu && pItem->mpMenu) + || ((!pQSubMenu) && pItem->mpAction)) + { + return; + } + + InsertMenuItem(pItem, nPos); +} + +void QtMenu::SetFrame(const SalFrame* pFrame) +{ + auto* pSalInst(GetQtInstance()); + assert(pSalInst); + if (!pSalInst->IsMainThread()) + { + pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); }); + return; + } + + SolarMutexGuard aGuard; + assert(mbMenuBar); + mpFrame = const_cast<QtFrame*>(static_cast<const QtFrame*>(pFrame)); + + mpFrame->SetMenu(this); + + QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow(); + if (!pMainWindow) + return; + + mpQMenuBar = new QMenuBar(); + pMainWindow->setMenuBar(mpQMenuBar); + + QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner); + if (pWidget) + { + m_pButtonGroup = pWidget->findChild<QButtonGroup*>(gButtonGroupKey); + assert(m_pButtonGroup); + connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this, + &QtMenu::slotMenuBarButtonClicked); + QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID)); + if (pButton) + connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument); + } + else + m_pButtonGroup = nullptr; + mpQMenu = nullptr; + + DoFullMenuUpdate(mpVCLMenu); +} + +void QtMenu::DoFullMenuUpdate(Menu* pMenuBar) +{ + // clear action groups since menu is rebuilt + ResetAllActionGroups(); + ShowCloseButton(false); + + for (sal_Int32 nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++) + { + QtMenuItem* pSalMenuItem = GetItemAtPos(nItem); + InsertMenuItem(pSalMenuItem, nItem); + SetItemImage(nItem, pSalMenuItem, pSalMenuItem->maImage); + const bool bShowDisabled + = bool(pMenuBar->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries) + || !bool(pMenuBar->GetMenuFlags() & MenuFlags::HideDisabledEntries); + const bool bVisible = pSalMenuItem->mbVisible + && (bShowDisabled || mpVCLMenu->IsItemEnabled(pSalMenuItem->mnId)); + pSalMenuItem->getAction()->setVisible(bVisible); + + if (pSalMenuItem->mpSubMenu != nullptr) + { + pMenuBar->HandleMenuActivateEvent(pSalMenuItem->mpSubMenu->GetMenu()); + pSalMenuItem->mpSubMenu->DoFullMenuUpdate(pMenuBar); + pMenuBar->HandleMenuDeActivateEvent(pSalMenuItem->mpSubMenu->GetMenu()); + } + } +} + +void QtMenu::ShowItem(unsigned nPos, bool bShow) +{ + if (nPos < maItems.size()) + { + QtMenuItem* pSalMenuItem = GetItemAtPos(nPos); + QAction* pAction = pSalMenuItem->getAction(); + if (pAction) + pAction->setVisible(bShow); + pSalMenuItem->mbVisible = bShow; + } +} + +void QtMenu::SetItemBits(unsigned nPos, MenuItemBits) +{ + if (nPos < maItems.size()) + { + QtMenuItem* pSalMenuItem = GetItemAtPos(nPos); + UpdateActionGroupItem(pSalMenuItem); + } +} + +void QtMenu::CheckItem(unsigned nPos, bool bChecked) +{ + if (nPos < maItems.size()) + { + QtMenuItem* pSalMenuItem = GetItemAtPos(nPos); + QAction* pAction = pSalMenuItem->getAction(); + if (pAction) + { + pAction->setCheckable(true); + pAction->setChecked(bChecked); + } + } +} + +void QtMenu::EnableItem(unsigned nPos, bool bEnable) +{ + if (nPos < maItems.size()) + { + QtMenuItem* pSalMenuItem = GetItemAtPos(nPos); + QAction* pAction = pSalMenuItem->getAction(); + if (pAction) + pAction->setEnabled(bEnable); + pSalMenuItem->mbEnabled = bEnable; + } +} + +void QtMenu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText) +{ + QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem); + QAction* pAction = pSalMenuItem->getAction(); + if (pAction) + { + OUString aText(rText); + NativeItemText(aText); + pAction->setText(toQString(aText)); + } +} + +void QtMenu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage) +{ + QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem); + + // Save new image to use it in DoFullMenuUpdate + pSalMenuItem->maImage = rImage; + + QAction* pAction = pSalMenuItem->getAction(); + if (!pAction) + return; + + pAction->setIcon(QPixmap::fromImage(toQImage(rImage))); +} + +void QtMenu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&, + const OUString& rText) +{ + QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem); + QAction* pAction = pSalMenuItem->getAction(); + if (pAction) + pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText)); +} + +void QtMenu::GetSystemMenuData(SystemMenuData*) {} + +QtMenu* QtMenu::GetTopLevel() +{ + QtMenu* pMenu = this; + while (pMenu->mpParentSalMenu) + pMenu = pMenu->mpParentSalMenu; + return pMenu; +} + +bool QtMenu::validateQMenuBar() const +{ + if (!mpQMenuBar) + return false; + assert(mpFrame); + QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow(); + assert(pMainWindow); + const bool bValid = mpQMenuBar == pMainWindow->menuBar(); + if (!bValid) + { + QtMenu* thisPtr = const_cast<QtMenu*>(this); + thisPtr->mpQMenuBar = nullptr; + } + return bValid; +} + +void QtMenu::ShowMenuBar(bool bVisible) +{ + if (!validateQMenuBar()) + return; + + mpQMenuBar->setVisible(bVisible); + if (bVisible) + lcl_force_menubar_layout_update(*mpQMenuBar); +} + +void QtMenu::slotMenuHovered(QtMenuItem* pItem) +{ + const OUString sHelpId = pItem->mpParentMenu->GetMenu()->GetHelpId(pItem->mnId); + m_sCurrentHelpId = sHelpId; +} + +void QtMenu::slotShowHelp() +{ + SolarMutexGuard aGuard; + Help* pHelp = Application::GetHelp(); + if (pHelp && !m_sCurrentHelpId.isEmpty()) + { + pHelp->Start(m_sCurrentHelpId); + } +} + +void QtMenu::slotMenuTriggered(QtMenuItem* pQItem) +{ + if (!pQItem) + return; + + QtMenu* pSalMenu = pQItem->mpParentMenu; + QtMenu* pTopLevel = pSalMenu->GetTopLevel(); + + Menu* pMenu = pSalMenu->GetMenu(); + auto mnId = pQItem->mnId; + + // HACK to allow HandleMenuCommandEvent to "not-set" the checked button + // LO expects a signal before an item state change, so reset the check item + if (pQItem->mpAction->isCheckable() + && (!pQItem->mpActionGroup || pQItem->mpActionGroup->actions().size() <= 1)) + pQItem->mpAction->setChecked(!pQItem->mpAction->isChecked()); + pTopLevel->GetMenu()->HandleMenuCommandEvent(pMenu, mnId); +} + +void QtMenu::slotMenuAboutToShow(QtMenuItem* pQItem) +{ + if (pQItem) + { + QtMenu* pSalMenu = pQItem->mpSubMenu; + QtMenu* pTopLevel = pSalMenu->GetTopLevel(); + + Menu* pMenu = pSalMenu->GetMenu(); + + // following function may update the menu + pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu); + } +} + +void QtMenu::slotMenuAboutToHide(QtMenuItem* pQItem) +{ + if (pQItem) + { + QtMenu* pSalMenu = pQItem->mpSubMenu; + QtMenu* pTopLevel = pSalMenu->GetTopLevel(); + + Menu* pMenu = pSalMenu->GetMenu(); + + pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu); + } +} + +void QtMenu::NativeItemText(OUString& rItemText) +{ + // preserve literal '&'s in menu texts + rItemText = rItemText.replaceAll("&", "&&"); + + rItemText = rItemText.replace('~', '&'); +} + +void QtMenu::slotCloseDocument() +{ + MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get()); + if (pVclMenuBar) + Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl()); +} + +void QtMenu::slotMenuBarButtonClicked(QAbstractButton* pButton) +{ + MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get()); + if (pVclMenuBar) + { + SolarMutexGuard aGuard; + pVclMenuBar->HandleMenuButtonEvent(m_pButtonGroup->id(pButton)); + } +} + +QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& rIcon, const QString& rToolTip, int nId) +{ + if (!validateQMenuBar()) + return nullptr; + + QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner); + QHBoxLayout* pLayout; + if (!pWidget) + { + assert(!m_pButtonGroup); + pWidget = new QWidget(mpQMenuBar); + assert(!pWidget->layout()); + pLayout = new QHBoxLayout(); + pLayout->setContentsMargins(QMargins()); + pLayout->setSpacing(0); + pWidget->setLayout(pLayout); + m_pButtonGroup = new QButtonGroup(pLayout); + m_pButtonGroup->setObjectName(gButtonGroupKey); + m_pButtonGroup->setExclusive(false); + connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this, + &QtMenu::slotMenuBarButtonClicked); + pWidget->show(); + mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner); + } + else + pLayout = static_cast<QHBoxLayout*>(pWidget->layout()); + assert(m_pButtonGroup); + assert(pLayout); + + QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId)); + if (pButton) + ImplRemoveMenuBarButton(nId); + + pButton = new QPushButton(); + // we don't want the button to increase the QMenuBar height, so a fixed size square it is + const int nFixedLength + = mpQMenuBar->height() - 2 * mpQMenuBar->style()->pixelMetric(QStyle::PM_MenuBarVMargin); + pButton->setFixedSize(nFixedLength, nFixedLength); + pButton->setIcon(rIcon); + pButton->setFlat(true); + pButton->setFocusPolicy(Qt::NoFocus); + pButton->setToolTip(rToolTip); + + m_pButtonGroup->addButton(pButton, nId); + int nPos = pLayout->count(); + if (m_pButtonGroup->button(CLOSE_BUTTON_ID)) + nPos--; + pLayout->insertWidget(nPos, pButton, 0, Qt::AlignCenter); + // show must happen after adding the button to the layout, otherwise the button is + // shown, but not correct in the layout, if at all! Some times the layout ignores it. + pButton->show(); + + lcl_force_menubar_layout_update(*mpQMenuBar); + + return pButton; +} + +bool QtMenu::AddMenuBarButton(const SalMenuButtonItem& rItem) +{ + if (!validateQMenuBar()) + return false; + return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem.maImage))), + toQString(rItem.maToolTipText), rItem.mnId); +} + +void QtMenu::ImplRemoveMenuBarButton(int nId) +{ + if (!validateQMenuBar()) + return; + + assert(m_pButtonGroup); + auto* pButton = m_pButtonGroup->button(nId); + assert(pButton); + QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner); + assert(pWidget); + QLayout* pLayout = pWidget->layout(); + m_pButtonGroup->removeButton(pButton); + pLayout->removeWidget(pButton); + delete pButton; + + lcl_force_menubar_layout_update(*mpQMenuBar); +} + +void QtMenu::connectHelpShortcut(QMenu* pMenu) +{ + assert(pMenu); + QKeySequence sequence(QKeySequence::HelpContents); + QShortcut* pQShortcut = new QShortcut(sequence, pMenu); + connect(pQShortcut, &QShortcut::activated, this, QtMenu::slotShowHelp); + connect(pQShortcut, &QShortcut::activatedAmbiguously, this, QtMenu::slotShowHelp); +} + +void QtMenu::connectHelpSignalSlots(QMenu* pMenu, QtMenuItem* pSalMenuItem) +{ + // connect hovered signal of the menu's own action + QAction* pAction = pMenu->menuAction(); + assert(pAction); + connect(pAction, &QAction::hovered, this, [pSalMenuItem] { slotMenuHovered(pSalMenuItem); }); + + // connect slot to handle Help key (F1) + connectHelpShortcut(pMenu); +} + +void QtMenu::RemoveMenuBarButton(sal_uInt16 nId) { ImplRemoveMenuBarButton(nId); } + +tools::Rectangle QtMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pFrame) +{ +#ifdef NDEBUG + Q_UNUSED(pFrame); +#endif + if (!validateQMenuBar()) + return tools::Rectangle(); + + assert(mpFrame == static_cast<QtFrame*>(pFrame)); + assert(m_pButtonGroup); + auto* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId)); + assert(pButton); + + // unfortunatly, calling lcl_force_menubar_layout_update results in a temporary wrong menubar size, + // but it's the correct minimal size AFAIK and the layout seems correct, so just adjust the width. + QPoint aPos = pButton->mapTo(mpFrame->asChild(), QPoint()); + aPos.rx() += (mpFrame->asChild()->width() - mpQMenuBar->width()); + return tools::Rectangle(toPoint(aPos), toSize(pButton->size())); +} + +void QtMenu::ShowCloseButton(bool bShow) +{ + if (!validateQMenuBar()) + return; + + if (!bShow && !m_pButtonGroup) + return; + + QPushButton* pButton = nullptr; + if (m_pButtonGroup) + pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID)); + if (!bShow && !pButton) + return; + + if (!pButton) + { + QIcon aIcon; + if (QIcon::hasThemeIcon("window-close-symbolic")) + aIcon = QIcon::fromTheme("window-close-symbolic"); + else + aIcon = QIcon( + QPixmap::fromImage(toQImage(Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC)))); + pButton = ImplAddMenuBarButton(aIcon, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)), + CLOSE_BUTTON_ID); + connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument); + } + + if (bShow) + pButton->show(); + else + pButton->hide(); + + lcl_force_menubar_layout_update(*mpQMenuBar); +} + +bool QtMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect, + FloatWinPopupFlags nFlags) +{ + assert(mpQMenu); + DoFullMenuUpdate(mpVCLMenu); + mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff)); + + const VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent; + AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect); + + // tdf#154447 Menu bar height has to be added + QtFrame* pFrame = static_cast<QtFrame*>(pWin->ImplGetFrame()); + assert(pFrame); + aFloatRect.SetPosY(aFloatRect.getY() + pFrame->menuBarOffset()); + + const QRect aRect = toQRect(aFloatRect, 1 / pFrame->devicePixelRatioF()); + mpQMenu->exec(aRect.bottomLeft()); + + return true; +} + +int QtMenu::GetMenuBarHeight() const +{ + if (!validateQMenuBar() || mpQMenuBar->isHidden()) + return 0; + + return mpQMenuBar->height(); +} + +QtMenuItem::QtMenuItem(const SalItemParams* pItemData) + : mpParentMenu(nullptr) + , mpSubMenu(nullptr) + , mnId(pItemData->nId) + , mnType(pItemData->eType) + , mbVisible(true) + , mbEnabled(true) + , maImage(pItemData->aImage) +{ +} + +QAction* QtMenuItem::getAction() const +{ + if (mpMenu) + return mpMenu->menuAction(); + if (mpAction) + return mpAction.get(); + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |