]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/combobox.cpp
Enable variadic macros for VC9 and later.
[wxWidgets.git] / src / msw / combobox.cpp
index 8ced0946381fcc14b0a3a5e15a6477582dcb3196..9e3f75edd7a57dc7b86d78c8e14da745f403129d 100644 (file)
 #endif
 
 #include "wx/clipbrd.h"
+#include "wx/dynlib.h"
+#include "wx/wupdlock.h"
 #include "wx/msw/private.h"
 
+#if wxUSE_UXTHEME
+    #include "wx/msw/uxtheme.h"
+#endif
+
 #if wxUSE_TOOLTIPS
     #include "wx/tooltip.h"
 #endif // wxUSE_TOOLTIPS
 // wxWin macros
 // ----------------------------------------------------------------------------
 
-#if wxUSE_EXTENDED_RTTI
-WX_DEFINE_FLAGS( wxComboBoxStyle )
-
-wxBEGIN_FLAGS( wxComboBoxStyle )
-    // new style border flags, we put them first to
-    // use them for streaming out
-    wxFLAGS_MEMBER(wxBORDER_SIMPLE)
-    wxFLAGS_MEMBER(wxBORDER_SUNKEN)
-    wxFLAGS_MEMBER(wxBORDER_DOUBLE)
-    wxFLAGS_MEMBER(wxBORDER_RAISED)
-    wxFLAGS_MEMBER(wxBORDER_STATIC)
-    wxFLAGS_MEMBER(wxBORDER_NONE)
-
-    // old style border flags
-    wxFLAGS_MEMBER(wxSIMPLE_BORDER)
-    wxFLAGS_MEMBER(wxSUNKEN_BORDER)
-    wxFLAGS_MEMBER(wxDOUBLE_BORDER)
-    wxFLAGS_MEMBER(wxRAISED_BORDER)
-    wxFLAGS_MEMBER(wxSTATIC_BORDER)
-    wxFLAGS_MEMBER(wxBORDER)
-
-    // standard window styles
-    wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
-    wxFLAGS_MEMBER(wxCLIP_CHILDREN)
-    wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
-    wxFLAGS_MEMBER(wxWANTS_CHARS)
-    wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
-    wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
-    wxFLAGS_MEMBER(wxVSCROLL)
-    wxFLAGS_MEMBER(wxHSCROLL)
-
-    wxFLAGS_MEMBER(wxCB_SIMPLE)
-    wxFLAGS_MEMBER(wxCB_SORT)
-    wxFLAGS_MEMBER(wxCB_READONLY)
-    wxFLAGS_MEMBER(wxCB_DROPDOWN)
-
-wxEND_FLAGS( wxComboBoxStyle )
-
-IMPLEMENT_DYNAMIC_CLASS_XTI(wxComboBox, wxChoice,"wx/combobox.h")
-
-wxBEGIN_PROPERTIES_TABLE(wxComboBox)
-    wxEVENT_PROPERTY( Select , wxEVT_COMMAND_COMBOBOX_SELECTED , wxCommandEvent )
-    wxEVENT_PROPERTY( TextEnter , wxEVT_COMMAND_TEXT_ENTER , wxCommandEvent )
-
-    // TODO DELEGATES
-    wxPROPERTY( Font , wxFont , SetFont , GetFont  , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
-    wxPROPERTY_COLLECTION( Choices , wxArrayString , wxString , AppendString , GetStrings , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
-    wxPROPERTY( Value ,wxString, SetValue, GetValue, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
-    wxPROPERTY( Selection ,int, SetSelection, GetSelection, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
-    wxPROPERTY_FLAGS( WindowStyle , wxComboBoxStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
-wxEND_PROPERTIES_TABLE()
-
-wxBEGIN_HANDLERS_TABLE(wxComboBox)
-wxEND_HANDLERS_TABLE()
-
-wxCONSTRUCTOR_5( wxComboBox , wxWindow* , Parent , wxWindowID , Id , wxString , Value , wxPoint , Position , wxSize , Size )
-
-#else
-
-IMPLEMENT_DYNAMIC_CLASS(wxComboBox, wxChoice)
-
-#endif
-
 BEGIN_EVENT_TABLE(wxComboBox, wxControl)
     EVT_MENU(wxID_CUT, wxComboBox::OnCut)
     EVT_MENU(wxID_COPY, wxComboBox::OnCopy)
@@ -183,7 +126,7 @@ LRESULT APIENTRY _EXPORT wxComboEditWndProc(HWND hWnd,
                     // longer, check for it to avoid bogus assert failures
                     if ( !win->IsBeingDeleted() )
                     {
-                        wxFAIL_MSG( _T("should have combo as parent") );
+                        wxFAIL_MSG( wxT("should have combo as parent") );
                     }
                 }
                 else if ( combo->MSWProcessEditMsg(message, wParam, lParam) )
@@ -196,7 +139,7 @@ LRESULT APIENTRY _EXPORT wxComboEditWndProc(HWND hWnd,
 
         case WM_GETDLGCODE:
             {
-                wxCHECK_MSG( win, 0, _T("should have a parent") );
+                wxCHECK_MSG( win, 0, wxT("should have a parent") );
 
                 if ( win->GetWindowStyle() & wxTE_PROCESS_ENTER )
                 {
@@ -205,13 +148,6 @@ LRESULT APIENTRY _EXPORT wxComboEditWndProc(HWND hWnd,
                 }
             }
             break;
-
-        case WM_CUT:
-        case WM_COPY:
-        case WM_PASTE:
-            if( win->HandleClipboardEvent( message ) )
-                return 0;
-            break;
     }
 
     return ::CallWindowProc(CASTWNDPROC gs_wndprocEdit, hWnd, message, wParam, lParam);
@@ -235,10 +171,28 @@ WXLRESULT wxComboBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
             {
                 // 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);
 
@@ -284,7 +238,7 @@ bool wxComboBox::MSWProcessEditMsg(WXUINT msg, WXWPARAM wParam, WXLPARAM lParam)
             // fall through
 
         case WM_SYSCHAR:
-            return HandleChar(wParam, lParam, true /* isASCII */);
+            return HandleChar(wParam, lParam);
 
         case WM_SYSKEYDOWN:
         case WM_KEYDOWN:
@@ -299,6 +253,11 @@ bool wxComboBox::MSWProcessEditMsg(WXUINT msg, WXWPARAM wParam, WXLPARAM lParam)
 
         case WM_KILLFOCUS:
             return HandleKillFocus((WXHWND)wParam);
+
+        case WM_CUT:
+        case WM_COPY:
+        case WM_PASTE:
+            return HandleClipboardEvent(msg);
     }
 
     return false;
@@ -311,6 +270,29 @@ bool wxComboBox::MSWCommand(WXUINT param, WXWORD id)
 
     switch ( param )
     {
+        case CBN_DROPDOWN:
+            // remember the last selection, just as wxChoice does
+            m_lastAcceptedSelection = GetCurrentSelection();
+            if ( m_lastAcceptedSelection == -1 )
+            {
+                // but unlike with wxChoice we may have no selection but still
+                // have some text and we should avoid erasing it if the drop
+                // down is cancelled (see #8474)
+                m_lastAcceptedSelection = wxID_NONE;
+            }
+            {
+                wxCommandEvent event(wxEVT_COMMAND_COMBOBOX_DROPDOWN, GetId());
+                event.SetEventObject(this);
+                ProcessCommand(event);
+            }
+            break;
+        case CBN_CLOSEUP:
+            {
+                wxCommandEvent event(wxEVT_COMMAND_COMBOBOX_CLOSEUP, GetId());
+                event.SetEventObject(this);
+                ProcessCommand(event);
+            }
+            break;
         case CBN_SELENDOK:
 #ifndef __SMARTPHONE__
             // we need to reset this to prevent the selection from being undone
@@ -342,6 +324,7 @@ bool wxComboBox::MSWCommand(WXUINT param, WXWORD id)
             // logical as the text does change)
 
         case CBN_EDITCHANGE:
+            if ( m_allowTextEvents )
             {
                 wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
 
@@ -363,7 +346,7 @@ bool wxComboBox::MSWCommand(WXUINT param, WXWORD id)
     }
 
     // skip wxChoice version as it would generate its own events for
-    // CBN_SELENDOK
+    // CBN_SELENDOK and also interfere with our handling of CBN_DROPDOWN
     return true;
 }
 
@@ -391,22 +374,64 @@ bool wxComboBox::MSWShouldPreProcessMessage(WXMSG *pMsg)
     return wxChoice::MSWShouldPreProcessMessage(pMsg);
 }
 
-WXHWND wxComboBox::GetEditHWND() const
+WXHWND wxComboBox::GetEditHWNDIfAvailable() const
 {
-    // this function should not be called for wxCB_READONLY controls, it is
-    // the callers responsability to check this
-    wxASSERT_MSG( !(GetWindowStyle() & wxCB_READONLY),
-                  _T("read-only combobox doesn't have any edit control") );
-
-    POINT pt;
-    pt.x = pt.y = 4;
-    HWND hwndEdit = ::ChildWindowFromPoint(GetHwnd(), pt);
-    if ( !hwndEdit || hwndEdit == GetHwnd() )
+#if wxUSE_DYNLIB_CLASS
+#if defined(WINVER) && WINVER >= 0x0500
+    typedef BOOL (WINAPI *GetComboBoxInfo_t)(HWND, COMBOBOXINFO*);
+    static GetComboBoxInfo_t s_pfnGetComboBoxInfo = NULL;
+    static bool s_triedToLoad = false;
+    if ( !s_triedToLoad )
+    {
+        s_triedToLoad = true;
+        wxLoadedDLL dllUser32("user32.dll");
+        wxDL_INIT_FUNC(s_pfn, GetComboBoxInfo, dllUser32);
+    }
+
+    if ( s_pfnGetComboBoxInfo )
+    {
+        WinStruct<COMBOBOXINFO> info;
+        (*s_pfnGetComboBoxInfo)(GetHwnd(), &info);
+        return info.hwndItem;
+    }
+#endif // WINVER >= 0x0500
+#endif // wxUSE_DYNLIB_CLASS
+
+    if (HasFlag(wxCB_SIMPLE))
     {
-        wxFAIL_MSG(_T("not read only combobox without edit control?"));
+        POINT pt;
+        pt.x = pt.y = 4;
+        return (WXHWND) ::ChildWindowFromPoint(GetHwnd(), pt);
     }
 
-    return (WXHWND)hwndEdit;
+    // notice that a slightly safer alternative could be to use FindWindowEx()
+    // but it's not available under WinCE so just take the first child for now
+    // to keep one version of the code for all platforms and fix it later if
+    // problems are discovered
+
+    // we assume that the only child of the combobox is the edit window
+    return (WXHWND)::GetWindow(GetHwnd(), GW_CHILD);
+}
+
+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;
 }
 
 // ----------------------------------------------------------------------------
@@ -504,7 +529,8 @@ WXDWORD wxComboBox::MSWGetStyle(long style, WXDWORD *exstyle) const
 
 wxString wxComboBox::GetValue() const
 {
-    return wxGetWindowText(m_hWnd);
+    return HasFlag(wxCB_READONLY) ? GetStringSelection()
+                                  : wxTextEntry::GetValue();
 }
 
 void wxComboBox::SetValue(const wxString& value)
@@ -512,222 +538,34 @@ void wxComboBox::SetValue(const wxString& value)
     if ( HasFlag(wxCB_READONLY) )
         SetStringSelection(value);
     else
-        SetWindowText(GetHwnd(), value.c_str());
-}
-
-// Clipboard operations
-void wxComboBox::Copy()
-{
-    SendMessage(GetHwnd(), WM_COPY, 0, 0L);
-}
-
-void wxComboBox::Cut()
-{
-    SendMessage(GetHwnd(), WM_CUT, 0, 0L);
+        wxTextEntry::SetValue(value);
 }
 
-void wxComboBox::Paste()
+void wxComboBox::Clear()
 {
-    SendMessage(GetHwnd(), WM_PASTE, 0, 0L);
+    wxChoice::Clear();
+    if ( !HasFlag(wxCB_READONLY) )
+        wxTextEntry::Clear();
 }
 
-void wxComboBox::Undo()
+void wxComboBox::GetSelection(long *from, long *to) const
 {
-    if (CanUndo())
+    if ( !HasFlag(wxCB_READONLY) )
     {
-        HWND hEditWnd = (HWND) GetEditHWND();
-        if ( hEditWnd )
-            ::SendMessage(hEditWnd, EM_UNDO, 0, 0);
+        wxTextEntry::GetSelection(from, to);
     }
-}
-
-void wxComboBox::Redo()
-{
-    if (CanUndo())
+    else // text selection doesn't make sense for read only comboboxes
     {
-        // Same as Undo, since Undo undoes the undo, i.e. a redo.
-        HWND hEditWnd = (HWND) GetEditHWND();
-        if ( hEditWnd )
-            ::SendMessage(hEditWnd, EM_UNDO, 0, 0);
+        if ( from )
+            *from = -1;
+        if ( to )
+            *to = -1;
     }
 }
 
-void wxComboBox::SelectAll()
-{
-    SetSelection(0, GetLastPosition());
-}
-
-bool wxComboBox::CanUndo() const
-{
-    if (!IsEditable())
-        return false;
-
-    HWND hEditWnd = (HWND) GetEditHWND();
-    if ( hEditWnd )
-        return ::SendMessage(hEditWnd, EM_CANUNDO, 0, 0) != 0;
-    else
-        return false;
-}
-
-bool wxComboBox::CanRedo() const
-{
-    if (!IsEditable())
-        return false;
-
-    HWND hEditWnd = (HWND) GetEditHWND();
-    if ( hEditWnd )
-        return ::SendMessage(hEditWnd, EM_CANUNDO, 0, 0) != 0;
-    else
-        return false;
-}
-
-bool wxComboBox::HasSelection() const
-{
-    long from, to;
-    GetSelection(&from, &to);
-    return from != to;
-}
-
-bool wxComboBox::CanCopy() const
-{
-    // Can copy if there's a selection
-    return HasSelection();
-}
-
-bool wxComboBox::CanCut() const
-{
-    return IsEditable() && CanCopy();
-}
-
-bool wxComboBox::CanPaste() const
-{
-    if ( !IsEditable() )
-        return false;
-
-    // Standard edit control: check for straight text on clipboard
-    if ( !::OpenClipboard(GetHwndOf(wxTheApp->GetTopWindow())) )
-        return false;
-
-    bool isTextAvailable = ::IsClipboardFormatAvailable(CF_TEXT) != 0;
-    ::CloseClipboard();
-
-    return isTextAvailable;
-}
-
 bool wxComboBox::IsEditable() const
 {
-    return !HasFlag(wxCB_READONLY);
-}
-
-void wxComboBox::SetEditable(bool editable)
-{
-    HWND hWnd = (HWND)GetEditHWND();
-    if ( !::SendMessage(hWnd, EM_SETREADONLY, !editable, 0) )
-    {
-        wxLogLastError(_T("SendMessage(EM_SETREADONLY)"));
-    }
-}
-
-void wxComboBox::SetInsertionPoint(long pos)
-{
-    if ( GetWindowStyle() & wxCB_READONLY )
-        return;
-
-    HWND hWnd = GetHwnd();
-    ::SendMessage(hWnd, CB_SETEDITSEL, 0, MAKELPARAM(pos, pos));
-    HWND hEditWnd = (HWND) GetEditHWND();
-    if ( hEditWnd )
-    {
-        // Scroll insertion point into view
-        SendMessage(hEditWnd, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
-        // Why is this necessary? (Copied from wxTextCtrl::SetInsertionPoint)
-        SendMessage(hEditWnd, EM_REPLACESEL, 0, (LPARAM) wxEmptyString);
-    }
-}
-
-void wxComboBox::SetInsertionPointEnd()
-{
-    // setting insertion point doesn't make sense for read only comboboxes
-    if ( !(GetWindowStyle() & wxCB_READONLY) )
-    {
-        wxTextPos pos = GetLastPosition();
-        SetInsertionPoint(pos);
-    }
-}
-
-long wxComboBox::GetInsertionPoint() const
-{
-    // CB_GETEDITSEL returns the index of the first character of the selection in
-    // its low-order word
-    DWORD pos= (DWORD)::SendMessage(GetHwnd(), CB_GETEDITSEL, 0, 0L);
-    return LOWORD(pos);
-}
-
-wxTextPos wxComboBox::GetLastPosition() const
-{
-    HWND hEditWnd = (HWND) GetEditHWND();
-
-    // Get number of characters in the last (only) line. We'll add this to the character
-    // index for the last line, 1st position.
-    wxTextPos lineLength = (wxTextPos)SendMessage(hEditWnd, EM_LINELENGTH, (WPARAM) 0, (LPARAM)0L);
-
-    return lineLength;
-}
-
-void wxComboBox::Replace(long from, long to, const wxString& value)
-{
-#if wxUSE_CLIPBOARD
-    Remove(from, to);
-
-    // Now replace with 'value', by pasting.
-    wxSetClipboardData(wxDF_TEXT, (wxObject *)value.wx_str(), 0, 0);
-
-    // Paste into edit control
-    SendMessage(GetHwnd(), WM_PASTE, (WPARAM)0, (LPARAM)0L);
-#else
-    wxUnusedVar(from);
-    wxUnusedVar(to);
-    wxUnusedVar(value);
-#endif
-}
-
-void wxComboBox::Remove(long from, long to)
-{
-    // Set selection and remove it
-    SetSelection(from, to);
-    SendMessage(GetHwnd(), WM_CUT, (WPARAM)0, (LPARAM)0);
-}
-
-void wxComboBox::SetSelection(long from, long to)
-{
-    // if from and to are both -1, it means (in wxWidgets) that all text should
-    // be selected, translate this into Windows convention
-    if ( (from == -1) && (to == -1) )
-    {
-        from = 0;
-    }
-
-    if ( SendMessage(GetHwnd(), CB_SETEDITSEL,
-                     0, (LPARAM)MAKELONG(from, to)) == CB_ERR )
-    {
-        wxLogDebug(_T("CB_SETEDITSEL failed"));
-    }
-}
-
-void wxComboBox::GetSelection(long* from, long* to) const
-{
-    DWORD dwStart, dwEnd;
-    if ( ::SendMessage(GetHwnd(), CB_GETEDITSEL,
-                       (WPARAM)&dwStart, (LPARAM)&dwEnd) == CB_ERR )
-    {
-        *from =
-        *to = 0;
-    }
-    else
-    {
-        *from = dwStart;
-        *to = dwEnd;
-    }
+    return !HasFlag(wxCB_READONLY) && wxTextEntry::IsEditable();
 }
 
 // ----------------------------------------------------------------------------
@@ -761,15 +599,12 @@ void wxComboBox::OnRedo(wxCommandEvent& WXUNUSED(event))
 
 void wxComboBox::OnDelete(wxCommandEvent& WXUNUSED(event))
 {
-    long from, to;
-    GetSelection(& from, & to);
-    if (from != -1 && to != -1)
-        Remove(from, to);
+    RemoveSelection();
 }
 
 void wxComboBox::OnSelectAll(wxCommandEvent& WXUNUSED(event))
 {
-    SetSelection(-1, -1);
+    SelectAll();
 }
 
 void wxComboBox::OnUpdateCut(wxUpdateUIEvent& event)
@@ -789,12 +624,12 @@ void wxComboBox::OnUpdatePaste(wxUpdateUIEvent& event)
 
 void wxComboBox::OnUpdateUndo(wxUpdateUIEvent& event)
 {
-    event.Enable( CanUndo() );
+    event.Enable( IsEditable() && CanUndo() );
 }
 
 void wxComboBox::OnUpdateRedo(wxUpdateUIEvent& event)
 {
-    event.Enable( CanRedo() );
+    event.Enable( IsEditable() && CanRedo() );
 }
 
 void wxComboBox::OnUpdateDelete(wxUpdateUIEvent& event)
@@ -804,7 +639,37 @@ void wxComboBox::OnUpdateDelete(wxUpdateUIEvent& event)
 
 void wxComboBox::OnUpdateSelectAll(wxUpdateUIEvent& event)
 {
-    event.Enable(IsEditable() && GetLastPosition() > 0);
+    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 ( wxUxThemeEngine::GetIfActive() )
+    {
+        // 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
+
 #endif // wxUSE_COMBOBOX