menu.cpp

来自「A*算法 A*算法 A*算法 A*算法A*算法A*算法」· C++ 代码 · 共 2,300 行 · 第 1/5 页

CPP
2,300
字号
        {
            wxLogLastError(_T("SetWindowPos(HWND_TOP)"));
        }

        Refresh();
    }
#endif // __WXMSW__
}

void wxPopupMenuWindow::Dismiss()
{
    if ( HasOpenSubmenu() )
    {
        wxMenuItem *item = GetCurrentItem();
        wxCHECK_RET( item && item->IsSubMenu(), _T("where is our open submenu?") );

        wxPopupMenuWindow *win = item->GetSubMenu()->m_popupMenu;
        wxCHECK_RET( win, _T("opened submenu is not opened?") );

        win->Dismiss();
        OnSubmenuDismiss( false );
    }

    wxPopupTransientWindow::Dismiss();

    ResetCurrent();
}

void wxPopupMenuWindow::OnDismiss()
{
    // when we are dismissed because the user clicked elsewhere or we lost
    // focus in any other way, hide the parent menu as well
    HandleDismiss(true);
}

void wxPopupMenuWindow::OnSubmenuDismiss(bool WXUNUSED(dismissParent))
{
    m_hasOpenSubMenu = false;
}

void wxPopupMenuWindow::HandleDismiss(bool dismissParent)
{
    m_menu->OnDismiss(dismissParent);
}

void wxPopupMenuWindow::DismissAndNotify()
{
    Dismiss();
    HandleDismiss(true);
}

// ----------------------------------------------------------------------------
// wxPopupMenuWindow geometry
// ----------------------------------------------------------------------------

wxMenuItemList::compatibility_iterator
wxPopupMenuWindow::GetMenuItemFromPoint(const wxPoint& pt) const
{
    // we only use the y coord normally, but still check x in case the point is
    // outside the window completely
    if ( wxWindow::HitTest(pt) == wxHT_WINDOW_INSIDE )
    {
        wxCoord y = 0;
        for ( wxMenuItemList::compatibility_iterator node = m_menu->GetMenuItems().GetFirst();
              node;
              node = node->GetNext() )
        {
            wxMenuItem *item = node->GetData();
            y += item->GetHeight();
            if ( y > pt.y )
            {
                // found
                return node;
            }
        }
    }

#if wxUSE_STL
    return wxMenuItemList::compatibility_iterator();
#else
    return NULL;
#endif
}

// ----------------------------------------------------------------------------
// wxPopupMenuWindow drawing
// ----------------------------------------------------------------------------

void wxPopupMenuWindow::RefreshItem(wxMenuItem *item)
{
    wxCHECK_RET( item, _T("can't refresh NULL item") );

    wxASSERT_MSG( IsShown(), _T("can't refresh menu which is not shown") );

    // FIXME: -1 here because of SetLogicalOrigin(1, 1) in DoDraw()
    RefreshRect(wxRect(0, item->GetPosition() - 1,
                m_menu->GetGeometryInfo().GetSize().x, item->GetHeight()));
}

void wxPopupMenuWindow::DoDraw(wxControlRenderer *renderer)
{
    // no clipping so far - do we need it? I don't think so as the menu is
    // never partially covered as it is always on top of everything

    wxDC& dc = renderer->GetDC();
    dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));

    // FIXME: this should be done in the renderer, however when it is fixed
    //        wxPopupMenuWindow::RefreshItem() should be changed too!
    dc.SetLogicalOrigin(1, 1);

    wxRenderer *rend = renderer->GetRenderer();

    wxCoord y = 0;
    const wxMenuGeometryInfo& gi = m_menu->GetGeometryInfo();
    for ( wxMenuItemList::compatibility_iterator node = m_menu->GetMenuItems().GetFirst();
          node;
          node = node->GetNext() )
    {
        wxMenuItem *item = node->GetData();

        if ( item->IsSeparator() )
        {
            rend->DrawMenuSeparator(dc, y, gi);
        }
        else // not a separator
        {
            int flags = 0;
            if ( item->IsCheckable() )
            {
                flags |= wxCONTROL_CHECKABLE;

                if ( item->IsChecked() )
                {
                    flags |= wxCONTROL_CHECKED;
                }
            }

            if ( !item->IsEnabled() )
                flags |= wxCONTROL_DISABLED;

            if ( item->IsSubMenu() )
                flags |= wxCONTROL_ISSUBMENU;

            if ( item == GetCurrentItem() )
                flags |= wxCONTROL_SELECTED;

            wxBitmap bmp;

            if ( !item->IsEnabled() )
            {
                bmp = item->GetDisabledBitmap();
            }

            if ( !bmp.Ok() )
            {
                // strangely enough, for unchecked item we use the
                // "checked" bitmap because this is the default one - this
                // explains this strange boolean expression
                bmp = item->GetBitmap(!item->IsCheckable() || item->IsChecked());
            }

            rend->DrawMenuItem
                  (
                     dc,
                     y,
                     gi,
                     item->GetLabel(),
                     item->GetAccelString(),
                     bmp,
                     flags,
                     item->GetAccelIndex()
                  );
        }

        y += item->GetHeight();
    }
}

// ----------------------------------------------------------------------------
// wxPopupMenuWindow actions
// ----------------------------------------------------------------------------

void wxPopupMenuWindow::ClickItem(wxMenuItem *item)
{
    wxCHECK_RET( item, _T("can't click NULL item") );

    wxASSERT_MSG( !item->IsSeparator() && !item->IsSubMenu(),
                  _T("can't click this item") );

    wxMenu* menu = m_menu;

    // close all menus
    DismissAndNotify();

    menu->ClickItem(item);
}

void wxPopupMenuWindow::OpenSubmenu(wxMenuItem *item, InputMethod how)
{
    wxCHECK_RET( item, _T("can't open NULL submenu") );

    wxMenu *submenu = item->GetSubMenu();
    wxCHECK_RET( submenu, _T("can only open submenus!") );

    // FIXME: should take into account the border width
    submenu->Popup(ClientToScreen(wxPoint(0, item->GetPosition())),
                   wxSize(m_menu->GetGeometryInfo().GetSize().x, 0),
                   how == WithKeyboard /* preselect first item then */);

    m_hasOpenSubMenu = true;
}

bool wxPopupMenuWindow::ActivateItem(wxMenuItem *item, InputMethod how)
{
    // don't activate disabled items
    if ( !item || !item->IsEnabled() )
    {
        return false;
    }

    // normal menu items generate commands, submenus can be opened and
    // the separators don't do anything
    if ( item->IsSubMenu() )
    {
        OpenSubmenu(item, how);
    }
    else if ( !item->IsSeparator() )
    {
        ClickItem(item);
    }
    else // separator, can't activate
    {
        return false;
    }

    return true;
}

// ----------------------------------------------------------------------------
// wxPopupMenuWindow input handling
// ----------------------------------------------------------------------------

bool wxPopupMenuWindow::ProcessLeftDown(wxMouseEvent& event)
{
    // wxPopupWindowHandler dismisses the window when the mouse is clicked
    // outside it which is usually just fine, but there is one case when we
    // don't want to do it: if the mouse was clicked on the parent submenu item
    // which opens this menu, so check for it

    wxPoint pos = event.GetPosition();
    if ( HitTest(pos.x, pos.y) == wxHT_WINDOW_OUTSIDE )
    {
        wxMenu *menu = m_menu->GetParent();
        if ( menu )
        {
            wxPopupMenuWindow *win = menu->m_popupMenu;

            wxCHECK_MSG( win, false, _T("parent menu not shown?") );

            pos = ClientToScreen(pos);
            if ( win->GetMenuItemFromPoint(win->ScreenToClient(pos)) )
            {
                // eat the event
                return true;
            }
            //else: it is outside the parent menu as well, do dismiss this one
        }
    }

    return false;
}

void wxPopupMenuWindow::OnLeftUp(wxMouseEvent& event)
{
    wxMenuItemList::compatibility_iterator node = GetMenuItemFromPoint(event.GetPosition());
    if ( node )
    {
        ActivateItem(node->GetData(), WithMouse);
    }
}

void wxPopupMenuWindow::OnMouseMove(wxMouseEvent& event)
{
    const wxPoint pt = event.GetPosition();

    // we need to ignore extra mouse events: example when this happens is when
    // the mouse is on the menu and we open a submenu from keyboard - Windows
    // then sends us a dummy mouse move event, we (correctly) determine that it
    // happens in the parent menu and so immediately close the just opened
    // submenu!
#ifdef __WXMSW__
    static wxPoint s_ptLast;
    wxPoint ptCur = ClientToScreen(pt);
    if ( ptCur == s_ptLast )
    {
        return;
    }

    s_ptLast = ptCur;
#endif // __WXMSW__

    ProcessMouseMove(pt);

    event.Skip();
}

void wxPopupMenuWindow::ProcessMouseMove(const wxPoint& pt)
{
    wxMenuItemList::compatibility_iterator node = GetMenuItemFromPoint(pt);

    // don't reset current to NULL here, we only do it when the mouse leaves
    // the window (see below)
    if ( node )
    {
        if ( node != m_nodeCurrent )
        {
            ChangeCurrent(node);

            wxMenuItem *item = GetCurrentItem();
            if ( CanOpen(item) )
            {
                OpenSubmenu(item, WithMouse);
            }
        }
        //else: same item, nothing to do
    }
    else // not on an item
    {
        // the last open submenu forwards the mouse move messages to its
        // parent, so if the mouse moves to another item of the parent menu,
        // this menu is closed and this other item is selected - in the similar
        // manner, the top menu forwards the mouse moves to the menubar which
        // allows to select another top level menu by just moving the mouse

        // we need to translate our client coords to the client coords of the
        // window we forward this event to
        wxPoint ptScreen = ClientToScreen(pt);

        // if the mouse is outside this menu, let the parent one to
        // process it
        wxMenu *menuParent = m_menu->GetParent();
        if ( menuParent )
        {
            wxPopupMenuWindow *win = menuParent->m_popupMenu;

            // if we're shown, the parent menu must be also shown
            wxCHECK_RET( win, _T("parent menu is not shown?") );

            win->ProcessMouseMove(win->ScreenToClient(ptScreen));
        }
        else // no parent menu
        {
            wxMenuBar *menubar = m_menu->GetMenuBar();
            if ( menubar )
            {
                if ( menubar->ProcessMouseEvent(
                            menubar->ScreenToClient(ptScreen)) )
                {
                    // menubar has closed this menu and opened another one, probably
                    return;
                }
            }
        }
        //else: top level popup menu, no other processing to do
    }
}

void wxPopupMenuWindow::OnMouseLeave(wxMouseEvent& event)
{
    // due to the artefact of mouse events generation under MSW, we actually
    // may get the mouse leave event after the menu had been already dismissed
    // and calling ChangeCurrent() would then assert, so don't do it
    if ( IsShown() )
    {
        // we shouldn't change the current them if our submenu is opened and
        // mouse moved there, in this case the submenu is responsable for
        // handling it
        bool resetCurrent;
        if ( HasOpenSubmenu() )
        {
            wxMenuItem *item = GetCurrentItem();
            wxCHECK_RET( CanOpen(item), _T("where is our open submenu?") );

            wxPopupMenuWindow *win = item->GetSubMenu()->m_popupMenu;
            wxCHECK_RET( win, _T("submenu is opened but not shown?") );

            // only handle this event if the mouse is not inside the submenu
            wxPoint pt = ClientToScreen(event.GetPosition());
            resetCurrent =
                win->HitTest(win->ScreenToClient(pt)) == wxHT_WINDOW_OUTSIDE;
        }
        else
        {
            // this menu is the last opened
            resetCurrent = true;
        }

        if ( resetCurrent )
        {
#if wxUSE_STL
            ChangeCurrent(wxMenuItemList::compatibility_iterator());
#else
            ChangeCurrent(NULL);
#endif
        }
    }

    event.Skip();
}

void wxPopupMenuWindow::OnKeyDown(wxKeyEvent& event)
{
    wxMenuBar *menubar = m_menu->GetMenuBar();

    if ( menubar )
    {
        menubar->ProcessEvent(event);
    }
    else if ( !ProcessKeyDown(event.GetKeyCode()) )
    {
        event.Skip();
    }
}

bool wxPopupMenuWindow::ProcessKeyDown(int key)
{
    wxMenuItem *item = GetCurrentItem();

    // first let the opened submenu to have it (no test for IsEnabled() here,
    // the keys navigate even in a disabled submenu if we had somehow managed
    // to open it inspit of this)
    if ( HasOpenSubmenu() )
    {
        wxCHECK_MSG( CanOpen(item), false,
                     _T("has open submenu but another item selected?") );

        if ( item->GetSubMenu()->ProcessKeyDown(key) )
            return true;
    }

    bool processed = true;

    // handle the up/down arrows, home, end, esc and return here, pass the
    // left/right arrows to the menu bar except when the right arrow can be
    // used to open a submenu
    switch ( key )
    {
        case WXK_LEFT:
            // if we're not a top level menu, close us, else leave this to the
            // menubar
            if ( !m_menu->GetParent() )
            {
                processed = false;
                break;
            }

            // fall through

        case WXK_ESCAPE:

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?