Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/vcl/qt5/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 31 kB image not shown  

Quelle  QtMenu.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * 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 <QtCustomStyle.hxx>
#include <vcl/themecolors.hxx>
#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 <strings.hrc>
#include <bitmaps.hlst>

#include <vcl/qt/QtUtils.hxx>
#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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        sal_uInt16 nId = pSalMenuItem->mnId;
        const QString aText = vclToQtStringWithAccelerator(mpVCLMenu->GetItemText(nId));
        vcl::KeyCode nAccelKey = mpVCLMenu->GetAccelKey(nId);

        pSalMenuItem->mpAction.reset();
        pSalMenuItem->mpMenu.reset();

        if (mbMenuBar)
        {
            // top-level menu
            if (validateQMenuBar())
            {
                QMenu* pQMenu = new QMenu(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(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(aText, nullptr);
                    pAction->setToolTip(toQString(mpVCLMenu->GetTipHelpText(nId)));
                    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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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 g;
    GetQtInstance().RunInMainThread([&] {
        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)
{
    QtInstance& rQtInstance = GetQtInstance();
    if (!rQtInstance.IsMainThread())
    {
        rQtInstance.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();
    mpQMenuBar->installEventFilter(this);
    pMainWindow->setMenuBar(mpQMenuBar);

    // open menu bar on F10, as is common in KF 6 and other toolkits:
    // https://wordsmith.social/felix-ernst/f10-for-accessibility-in-kf6
    QShortcut* pQShortcut = new QShortcut(QKeySequence(Qt::Key_F10), mpQMenuBar->window());
    connect(pQShortcut, &QShortcut::activated, this, &QtMenu::slotShortcutF10);

    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)
{
    if (mpQMenuBar && ThemeColors::VclPluginCanUseThemeColors())
        mpQMenuBar->setPalette(QtCustomStyle::GetMenuBarPalette());

    if (mpQMenu && ThemeColors::VclPluginCanUseThemeColors())
        mpQMenu->setPalette(QtCustomStyle::GetMenuPalette());

    // 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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
        QAction* pAction = pSalMenuItem->getAction();
        if (pAction)
            pAction->setText(vclToQtStringWithAccelerator(rText));
    });
}

void QtMenu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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::SetItemTooltip(SalMenuItem* pItem, const OUString& rTooltip)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);

        if (QAction* pAction = pSalMenuItem->getAction())
            pAction->setToolTip(toQString(rTooltip));
    });
}

void QtMenu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
                            const OUString& rText)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
        QAction* pAction = pSalMenuItem->getAction();
        if (pAction)
            pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
    });
}

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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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::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));
    }
}

void QtMenu::slotShortcutF10()
{
    SolarMutexGuard aGuard;

    // focus menu bar and select first item
    if (mpQMenuBar && !mpQMenuBar->actions().empty())
        mpQMenuBar->setActiveAction(mpQMenuBar->actions().at(0));
}

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 = GetQtInstance().EmscriptenLightweightRunInMainThread(
            [this] { return new QWidget(mpQMenuBar); });
        assert(!pWidget->layout());
        pLayout = GetQtInstance().EmscriptenLightweightRunInMainThread(
            [] { return new QHBoxLayout(); });
        pLayout->setContentsMargins(QMargins());
        pLayout->setSpacing(0);
        pWidget->setLayout(pLayout);
        m_pButtonGroup = GetQtInstance().EmscriptenLightweightRunInMainThread(
            [pLayout] { return new QButtonGroup(pLayout); });
        m_pButtonGroup->setObjectName(gButtonGroupKey);
        m_pButtonGroup->setExclusive(false);
        connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
                &QtMenu::slotMenuBarButtonClicked);
        GetQtInstance().EmscriptenLightweightRunInMainThread([this, pWidget] {
            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)
        RemoveMenuBarButton(nId);

    pButton
        = GetQtInstance().EmscriptenLightweightRunInMainThread([] { return 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::connectHelpShortcut(QMenu* pMenu)
{
    assert(pMenu);
    QKeySequence sequence(QKeySequence::HelpContents);
    QShortcut* pQShortcut;
    pQShortcut = GetQtInstance().EmscriptenLightweightRunInMainThread(
        [&sequence, pMenu] { return 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);

    // enable tooltips, Qt doesn't show them for menu entries by default
    pMenu->setToolTipsVisible(true);
}

void QtMenu::RemoveMenuBarButton(sal_uInt16 nId)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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);
    });
}

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)
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] {
        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);

    QtFrame* pFrame = static_cast<QtFrame*>(pWin->ImplGetFrame());
    assert(pFrame);

    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();
}

QtMenu::~QtMenu()
{
    SolarMutexGuard g;
    GetQtInstance().RunInMainThread([&] { mpOwnedQMenu.reset(); });
}

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 cinoptions=b1,g0,N-s cinkeys+=0=break: */

Messung V0.5
C=96 H=97 G=96

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.