Files
wxWidgets/src/msw/combobox.cpp
Vadim Zeitlin 6bb45751e5 Remove redundant ShouldForwardFromEditToCombo() check
No real changes, just avoid calling ShouldForwardFromEditToCombo() in
MSWProcessEditMsg() because this function is only called when the
message should be forwarded.

Also make the comment about the return value more clear.
2018-02-17 14:17:13 +01:00

748 lines
22 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/msw/combobox.cpp
// Purpose: wxComboBox class
// Author: Julian Smart
// Modified by:
// Created: 01/02/97
// Copyright: (c) Julian Smart
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_COMBOBOX
#include "wx/combobox.h"
#ifndef WX_PRECOMP
#include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
#include "wx/settings.h"
#include "wx/log.h"
// for wxEVT_TEXT_ENTER
#include "wx/textctrl.h"
#include "wx/app.h"
#include "wx/brush.h"
#endif
#include "wx/clipbrd.h"
#include "wx/wupdlock.h"
#include "wx/msw/private.h"
#include "wx/msw/private/winstyle.h"
#if wxUSE_UXTHEME
#include "wx/msw/uxtheme.h"
#endif
#if wxUSE_TOOLTIPS
#include "wx/tooltip.h"
#endif // wxUSE_TOOLTIPS
// ----------------------------------------------------------------------------
// wxWin macros
// ----------------------------------------------------------------------------
wxBEGIN_EVENT_TABLE(wxComboBox, wxControl)
EVT_MENU(wxID_CUT, wxComboBox::OnCut)
EVT_MENU(wxID_COPY, wxComboBox::OnCopy)
EVT_MENU(wxID_PASTE, wxComboBox::OnPaste)
EVT_MENU(wxID_UNDO, wxComboBox::OnUndo)
EVT_MENU(wxID_REDO, wxComboBox::OnRedo)
EVT_MENU(wxID_CLEAR, wxComboBox::OnDelete)
EVT_MENU(wxID_SELECTALL, wxComboBox::OnSelectAll)
EVT_UPDATE_UI(wxID_CUT, wxComboBox::OnUpdateCut)
EVT_UPDATE_UI(wxID_COPY, wxComboBox::OnUpdateCopy)
EVT_UPDATE_UI(wxID_PASTE, wxComboBox::OnUpdatePaste)
EVT_UPDATE_UI(wxID_UNDO, wxComboBox::OnUpdateUndo)
EVT_UPDATE_UI(wxID_REDO, wxComboBox::OnUpdateRedo)
EVT_UPDATE_UI(wxID_CLEAR, wxComboBox::OnUpdateDelete)
EVT_UPDATE_UI(wxID_SELECTALL, wxComboBox::OnUpdateSelectAll)
wxEND_EVENT_TABLE()
// ----------------------------------------------------------------------------
// function prototypes
// ----------------------------------------------------------------------------
LRESULT APIENTRY
wxComboEditWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// ---------------------------------------------------------------------------
// global vars
// ---------------------------------------------------------------------------
// the pointer to standard radio button wnd proc
static WNDPROC gs_wndprocEdit = (WNDPROC)NULL;
// ============================================================================
// implementation
// ============================================================================
namespace
{
// Check if the given message should be forwarded from the edit control which
// is part of the combobox to wxComboBox itself. All messages generating the
// events that the code using wxComboBox could be interested in must be
// forwarded.
bool ShouldForwardFromEditToCombo(UINT message)
{
switch ( message )
{
case WM_KEYUP:
case WM_KEYDOWN:
case WM_CHAR:
case WM_SYSCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SETFOCUS:
case WM_KILLFOCUS:
case WM_CUT:
case WM_COPY:
case WM_PASTE:
return true;
}
return false;
}
} // anonymous namespace
// ----------------------------------------------------------------------------
// wnd proc for subclassed edit control
// ----------------------------------------------------------------------------
LRESULT APIENTRY
wxComboEditWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND hwndCombo = ::GetParent(hWnd);
wxWindow *win = wxFindWinFromHandle((WXHWND)hwndCombo);
if ( ShouldForwardFromEditToCombo(message) )
{
wxComboBox *combo = wxDynamicCast(win, wxComboBox);
if ( !combo )
{
// we can get WM_KILLFOCUS while our parent is already half
// destroyed and hence doesn't look like a combobx any
// longer, check for it to avoid bogus assert failures
if ( !win->IsBeingDeleted() )
{
wxFAIL_MSG( wxT("should have combo as parent") );
}
}
else if ( combo->MSWProcessEditMsg(message, wParam, lParam) )
{
// handled by parent
return 0;
}
}
else if ( message == WM_GETDLGCODE )
{
wxCHECK_MSG( win, 0, wxT("should have a parent") );
if ( win->GetWindowStyle() & wxTE_PROCESS_ENTER )
{
// need to return a custom dlg code or we'll never get it
return DLGC_WANTMESSAGE;
}
}
return ::CallWindowProc(CASTWNDPROC gs_wndprocEdit, hWnd, message, wParam, lParam);
}
// ----------------------------------------------------------------------------
// wxComboBox callbacks
// ----------------------------------------------------------------------------
WXLRESULT wxComboBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
// TODO: handle WM_CTLCOLOR messages from our EDIT control to be able to
// set its colour correctly (to be the same as our own one)
switch ( nMsg )
{
case WM_SIZE:
// wxStaticBox can generate this message, when modifying the control's style.
// This causes the content of the combobox to be selected, for some reason.
case WM_STYLECHANGED:
{
// combobox selection sometimes spontaneously changes when its
// size changes, restore it to the old value if necessary
if ( !GetEditHWNDIfAvailable() )
break;
long fromOld, toOld;
GetSelection(&fromOld, &toOld);
// if an editable combobox has a not empty text not from the
// list, it tries to autocomplete it from the list when it is
// resized, but we don't want this to happen as it doesn't seem
// to make any sense, so we forcefully restore the old text
wxString textOld;
if ( !HasFlag(wxCB_READONLY) && GetCurrentSelection() == -1 )
textOld = GetValue();
// eliminate flickering during following hacks
wxWindowUpdateLocker lock(this);
WXLRESULT result = wxChoice::MSWWindowProc(nMsg, wParam, lParam);
if ( !textOld.empty() && GetValue() != textOld )
SetLabel(textOld);
long fromNew, toNew;
GetSelection(&fromNew, &toNew);
if ( fromOld != fromNew || toOld != toNew )
{
SetSelection(fromOld, toOld);
}
return result;
}
}
return wxChoice::MSWWindowProc(nMsg, wParam, lParam);
}
bool wxComboBox::MSWProcessEditSpecialKey(WXWPARAM vkey)
{
// for compatibility with wxTextCtrl, generate a special message
// when Enter is pressed
switch ( vkey )
{
case VK_RETURN:
{
if (SendMessage(GetHwnd(), CB_GETDROPPEDSTATE, 0, 0))
break;
wxCommandEvent event(wxEVT_TEXT_ENTER, m_windowId);
const int sel = GetSelection();
event.SetInt(sel);
event.SetString(GetValue());
InitCommandEventWithItems(event, sel);
if ( ProcessCommand(event) )
{
// don't let the event through to the native control
// because it doesn't need it and may generate an annoying
// beep if it gets it
return true;
}
}
break;
case VK_TAB:
// If we have wxTE_PROCESS_ENTER style, we get all char
// events, including those for TAB which are usually used
// for keyboard navigation, but we should not process them
// unless we also have wxTE_PROCESS_TAB style.
if ( !HasFlag(wxTE_PROCESS_TAB) )
{
int flags = 0;
if ( !wxIsShiftDown() )
flags |= wxNavigationKeyEvent::IsForward;
if ( wxIsCtrlDown() )
flags |= wxNavigationKeyEvent::WinChange;
if ( Navigate(flags) )
return true;
}
break;
}
return false;
}
bool wxComboBox::MSWProcessEditMsg(WXUINT msg, WXWPARAM wParam, WXLPARAM lParam)
{
switch ( msg )
{
case WM_CHAR:
if ( MSWProcessEditSpecialKey(wParam) )
return true;
break;
}
// For all the messages forwarded from the edit control the result is not
// used and 0 must be returned if the message is handled.
WXLRESULT result;
return MSWHandleMessage(&result, msg, wParam, lParam);
}
bool wxComboBox::MSWCommand(WXUINT param, WXWORD id)
{
int sel = -1;
wxString value;
switch ( param )
{
case CBN_DROPDOWN:
// remember the last selection, just as wxChoice does
m_lastAcceptedSelection = GetCurrentSelection();
{
wxCommandEvent event(wxEVT_COMBOBOX_DROPDOWN, GetId());
event.SetEventObject(this);
ProcessCommand(event);
}
break;
case CBN_CLOSEUP:
// Do the same thing as in wxChoice but using different event type.
if ( m_pendingSelection != wxID_NONE )
{
SendSelectionChangedEvent(wxEVT_COMBOBOX);
m_pendingSelection = wxID_NONE;
}
{
wxCommandEvent event(wxEVT_COMBOBOX_CLOSEUP, GetId());
event.SetEventObject(this);
ProcessCommand(event);
}
break;
case CBN_SELENDOK:
// we need to reset this to prevent the selection from being undone
// by wxChoice, see wxChoice::MSWCommand() and comments there
m_lastAcceptedSelection = wxID_NONE;
// set these variables so that they could be also fixed in
// CBN_EDITCHANGE below
sel = GetSelection();
value = GetStringSelection();
// this string is going to become the new combobox value soon but
// we need it to be done right now, otherwise the event handler
// could get a wrong value when it calls our GetValue()
::SetWindowText(GetHwnd(), value.t_str());
SendSelectionChangedEvent(wxEVT_COMBOBOX);
// fall through: for compatibility with wxGTK, also send the text
// update event when the selection changes (this also seems more
// logical as the text does change)
wxFALLTHROUGH;
case CBN_EDITCHANGE:
if ( m_allowTextEvents )
{
wxCommandEvent event(wxEVT_TEXT, GetId());
// if sel != -1, value was already initialized above
if ( sel == -1 )
{
value = wxGetWindowText(GetHwnd());
}
event.SetString(value);
InitCommandEventWithItems(event, sel);
ProcessCommand(event);
}
break;
default:
return wxChoice::MSWCommand(param, id);
}
// skip wxChoice version as it would generate its own events for
// CBN_SELENDOK and also interfere with our handling of CBN_DROPDOWN
return true;
}
bool wxComboBox::MSWShouldPreProcessMessage(WXMSG *pMsg)
{
// prevent command accelerators from stealing editing
// hotkeys when we have the focus
if (wxIsCtrlDown())
{
WPARAM vkey = pMsg->wParam;
switch (vkey)
{
case 'C':
case 'V':
case 'X':
case VK_INSERT:
case VK_DELETE:
case VK_HOME:
case VK_END:
return false;
}
}
return wxChoice::MSWShouldPreProcessMessage(pMsg);
}
#if wxUSE_OLE
void wxComboBox::MSWProcessSpecialKey(wxKeyEvent& event)
{
if ( !MSWProcessEditSpecialKey(event.GetRawKeyCode()) )
event.Skip();
}
#endif // wxUSE_OLE
WXHWND wxComboBox::GetEditHWNDIfAvailable() const
{
WinStruct<COMBOBOXINFO> info;
if ( ::GetComboBoxInfo(GetHwnd(), &info) )
return info.hwndItem;
if (HasFlag(wxCB_SIMPLE))
{
POINT pt;
pt.x = pt.y = 4;
return (WXHWND) ::ChildWindowFromPoint(GetHwnd(), pt);
}
// we assume that the only child of the combobox is the edit window
return (WXHWND)::FindWindowEx(GetHwnd(), NULL, wxT("EDIT"), NULL);
}
WXHWND wxComboBox::GetEditHWND() const
{
// this function should not be called for wxCB_READONLY controls, it is
// the callers responsibility to check this
wxASSERT_MSG( !HasFlag(wxCB_READONLY),
wxT("read-only combobox doesn't have any edit control") );
WXHWND hWndEdit = GetEditHWNDIfAvailable();
wxASSERT_MSG( hWndEdit, wxT("combobox without edit control?") );
return hWndEdit;
}
wxWindow *wxComboBox::GetEditableWindow()
{
wxASSERT_MSG( !HasFlag(wxCB_READONLY),
wxT("read-only combobox doesn't have any edit control") );
return this;
}
// ----------------------------------------------------------------------------
// wxComboBox creation
// ----------------------------------------------------------------------------
bool wxComboBox::Create(wxWindow *parent, wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
int n, const wxString choices[],
long style,
const wxValidator& validator,
const wxString& name)
{
// pretend that wxComboBox is hidden while it is positioned and resized and
// show it only right before leaving this method because otherwise there is
// some noticeable flicker while the control rearranges itself
m_isShown = false;
if ( !CreateAndInit(parent, id, pos, size, n, choices, style,
validator, name) )
return false;
// we shouldn't call SetValue() for an empty string because this would
// (correctly) result in an assert with a read only combobox and is useless
// for the other ones anyhow
if ( !value.empty() )
SetValue(value);
// a (not read only) combobox is, in fact, 2 controls: the combobox itself
// and an edit control inside it and if we want to catch events from this
// edit control, we must subclass it as well
if ( !(style & wxCB_READONLY) )
{
gs_wndprocEdit = wxSetWindowProc((HWND)GetEditHWND(), wxComboEditWndProc);
}
// and finally, show the control
Show(true);
return true;
}
bool wxComboBox::Create(wxWindow *parent, wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
const wxArrayString& choices,
long style,
const wxValidator& validator,
const wxString& name)
{
wxCArrayString chs(choices);
return Create(parent, id, value, pos, size, chs.GetCount(),
chs.GetStrings(), style, validator, name);
}
WXDWORD wxComboBox::MSWGetStyle(long style, WXDWORD *exstyle) const
{
// we never have an external border
WXDWORD msStyle = wxChoice::MSWGetStyle
(
(style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
);
// usually WS_TABSTOP is added by wxControl::MSWGetStyle() but as we're
// created hidden (see Create() above), it is not done for us but we still
// want to have this style
msStyle |= WS_TABSTOP;
// remove the style always added by wxChoice
msStyle &= ~CBS_DROPDOWNLIST;
if ( style & wxCB_READONLY )
msStyle |= CBS_DROPDOWNLIST;
else if ( style & wxCB_SIMPLE )
msStyle |= CBS_SIMPLE; // A list (shown always) and edit control
else
msStyle |= CBS_DROPDOWN;
// there is no reason to not always use CBS_AUTOHSCROLL, so do use it
msStyle |= CBS_AUTOHSCROLL;
// NB: we used to also add CBS_NOINTEGRALHEIGHT here but why?
return msStyle;
}
// ----------------------------------------------------------------------------
// wxComboBox text control-like methods
// ----------------------------------------------------------------------------
wxString wxComboBox::GetValue() const
{
return HasFlag(wxCB_READONLY) ? GetStringSelection()
: wxTextEntry::GetValue();
}
void wxComboBox::SetValue(const wxString& value)
{
if ( HasFlag(wxCB_READONLY) )
SetStringSelection(value);
else
wxTextEntry::SetValue(value);
}
void wxComboBox::Clear()
{
wxChoice::Clear();
if ( !HasFlag(wxCB_READONLY) )
wxTextEntry::Clear();
}
bool wxComboBox::ContainsHWND(WXHWND hWnd) const
{
return hWnd == GetEditHWNDIfAvailable();
}
void wxComboBox::GetSelection(long *from, long *to) const
{
if ( !HasFlag(wxCB_READONLY) )
{
wxTextEntry::GetSelection(from, to);
}
else // text selection doesn't make sense for read only comboboxes
{
if ( from )
*from = -1;
if ( to )
*to = -1;
}
}
bool wxComboBox::IsEditable() const
{
return !HasFlag(wxCB_READONLY) && wxTextEntry::IsEditable();
}
// ----------------------------------------------------------------------------
// standard event handling
// ----------------------------------------------------------------------------
void wxComboBox::OnCut(wxCommandEvent& WXUNUSED(event))
{
Cut();
}
void wxComboBox::OnCopy(wxCommandEvent& WXUNUSED(event))
{
Copy();
}
void wxComboBox::OnPaste(wxCommandEvent& WXUNUSED(event))
{
Paste();
}
void wxComboBox::OnUndo(wxCommandEvent& WXUNUSED(event))
{
Undo();
}
void wxComboBox::OnRedo(wxCommandEvent& WXUNUSED(event))
{
Redo();
}
void wxComboBox::OnDelete(wxCommandEvent& WXUNUSED(event))
{
RemoveSelection();
}
void wxComboBox::OnSelectAll(wxCommandEvent& WXUNUSED(event))
{
SelectAll();
}
void wxComboBox::OnUpdateCut(wxUpdateUIEvent& event)
{
event.Enable( CanCut() );
}
void wxComboBox::OnUpdateCopy(wxUpdateUIEvent& event)
{
event.Enable( CanCopy() );
}
void wxComboBox::OnUpdatePaste(wxUpdateUIEvent& event)
{
event.Enable( CanPaste() );
}
void wxComboBox::OnUpdateUndo(wxUpdateUIEvent& event)
{
event.Enable( IsEditable() && CanUndo() );
}
void wxComboBox::OnUpdateRedo(wxUpdateUIEvent& event)
{
event.Enable( IsEditable() && CanRedo() );
}
void wxComboBox::OnUpdateDelete(wxUpdateUIEvent& event)
{
event.Enable(IsEditable() && HasSelection());
}
void wxComboBox::OnUpdateSelectAll(wxUpdateUIEvent& event)
{
event.Enable(IsEditable() && !wxTextEntry::IsEmpty());
}
#if wxUSE_TOOLTIPS
void wxComboBox::DoSetToolTip(wxToolTip *tip)
{
wxChoice::DoSetToolTip(tip);
if ( tip && !HasFlag(wxCB_READONLY) )
tip->AddOtherWindow(GetEditHWND());
}
#endif // wxUSE_TOOLTIPS
#if wxUSE_UXTHEME
bool wxComboBox::SetHint(const wxString& hintOrig)
{
wxString hint(hintOrig);
if ( wxUxThemeIsActive() )
{
// under XP (but not Vista) there is a bug in cue banners
// implementation for combobox edit control: the first character is
// partially chopped off, so prepend a space to make it fully visible
hint.insert(0, " ");
}
return wxTextEntry::SetHint(hint);
}
#endif // wxUSE_UXTHEME
wxSize wxComboBox::DoGetSizeFromTextSize(int xlen, int ylen) const
{
wxSize tsize( wxChoice::DoGetSizeFromTextSize(xlen, ylen) );
if ( !HasFlag(wxCB_READONLY) )
{
// Add the margins we have previously set
wxPoint marg( GetMargins() );
marg.x = wxMax(0, marg.x);
marg.y = wxMax(0, marg.y);
tsize.IncBy( marg );
}
return tsize;
}
wxWindow *wxComboBox::MSWFindItem(long id, WXHWND hWnd) const
{
// The base class version considers that any window with the same ID as
// this one must be this window itself, but this is not the case for the
// comboboxes where the native control seems to always use the ID of 1000
// for the popup listbox that it creates -- and this ID may be the same as
// our own one. So we must explicitly check the HWND value too here and
// avoid eating the events from the listbox as otherwise it is rendered
// inoperative, see #15647.
if ( id == GetId() && hWnd && hWnd != GetHWND() )
{
// Must be the case described above.
return NULL;
}
return wxChoice::MSWFindItem(id, hWnd);
}
void wxComboBox::SetLayoutDirection(wxLayoutDirection dir)
{
// Edit field and drop-down list must be handled explicitly.
// Edit field is a special EDIT control (e.g. it always returns null
// extended style flags), so its layout direction should be set using the
// same extended flag as for ordinary window but reset simply with
// alignment flags.
if ( !HasFlag(wxCB_READONLY) )
{
if ( dir == wxLayout_RightToLeft )
{
wxUpdateLayoutDirection(GetEditHWND(), dir);
}
else
{
wxMSWWinStyleUpdater styleUpdater(GetEditHWND());
if ( !styleUpdater.IsOn(ES_CENTER) )
{
styleUpdater.TurnOff(ES_RIGHT);
}
}
}
// Layout for the drop-down list also must be set explicitly.
WinStruct<COMBOBOXINFO> info;
if ( ::GetComboBoxInfo(GetHwnd(), &info) )
{
wxUpdateLayoutDirection(info.hwndList, dir);
}
wxChoice::SetLayoutDirection(dir);
}
#endif // wxUSE_COMBOBOX