X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e2fc2bd51c2351f3cb7e289bb397efbb60b8cbc5..12bb29f5432174ecbd65549bda832d70d34a98ae:/src/msw/textentry.cpp diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index b763b5de8e..a8a12a5c7f 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -31,14 +31,21 @@ #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX #include "wx/textentry.h" +#include "wx/textcompleter.h" #include "wx/dynlib.h" +#include + #include "wx/msw/private.h" +#if wxUSE_UXTHEME + #include "wx/msw/uxtheme.h" +#endif + #define GetEditHwnd() ((HWND)(GetEditHWND())) // ---------------------------------------------------------------------------- -// wxIEnumString implements IEnumString interface +// Classes used by auto-completion implementation. // ---------------------------------------------------------------------------- // standard VC6 SDK (WINVER == 0x0400) does not know about IAutoComplete @@ -51,9 +58,13 @@ #include "wx/msw/ole/oleutils.h" #include -#if defined(__MINGW32__) || defined (__WATCOMC__) +#if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__) // needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST #include + + #ifndef ACO_AUTOAPPEND + #define ACO_AUTOAPPEND 0x02 + #endif #endif #ifndef ACO_UPDOWNKEYDROPSLIST @@ -64,18 +75,95 @@ #define SHACF_FILESYS_ONLY 0x00000010 #endif -DEFINE_GUID(CLSID_AutoComplete, +#ifndef SHACF_FILESYS_DIRS + #define SHACF_FILESYS_DIRS 0x00000020 +#endif + +namespace +{ + +// Normally this interface and its IID are defined in shobjidl.h header file +// included in the platform SDK but MinGW and Cygwin don't have it so redefine +// the interface ourselves and, as long as we do it all, do it for all +// compilers to ensure we have the same behaviour for all of them and to avoid +// the need to check for concrete compilers and maybe even their versions. +class IAutoCompleteDropDown : public IUnknown +{ +public: + virtual HRESULT wxSTDCALL GetDropDownStatus(DWORD *, LPWSTR *) = 0; + virtual HRESULT wxSTDCALL ResetEnumerator() = 0; +}; + +DEFINE_GUID(wxIID_IAutoCompleteDropDown, + 0x3cd141f4, 0x3c6a, 0x11d2, 0xbc, 0xaa, 0x00, 0xc0, 0x4f, 0xd9, 0x29, 0xdb); + +DEFINE_GUID(wxCLSID_AutoComplete, 0x00bb2763, 0x6a77, 0x11d0, 0xa5, 0x35, 0x00, 0xc0, 0x4f, 0xd7, 0xd0, 0x62); +// Small helper class which can be used to ensure thread safety even when +// wxUSE_THREADS==0 (and hence wxCriticalSection does nothing). +class CSLock +{ +public: + CSLock(CRITICAL_SECTION& cs) : m_cs(&cs) + { + ::EnterCriticalSection(m_cs); + } + + ~CSLock() + { + ::LeaveCriticalSection(m_cs); + } + +private: + CRITICAL_SECTION * const m_cs; + + wxDECLARE_NO_COPY_CLASS(CSLock); +}; + +} // anonymity namespace + +// Implementation of string enumerator used by wxTextAutoCompleteData. This +// class simply forwards to wxTextCompleter associated with it. +// +// Notice that Next() method of this class is called by IAutoComplete +// background thread and so we must care about thread safety here. class wxIEnumString : public IEnumString { public: - wxIEnumString(const wxArrayString& strings) : m_strings(strings) + wxIEnumString() { - m_index = 0; + Init(); } - DECLARE_IUNKNOWN_METHODS; + void ChangeCompleter(wxTextCompleter *completer) + { + // Indicate to Next() that it should bail out as soon as possible. + { + CSLock lock(m_csRestart); + + m_restart = TRUE; + } + + // Now try to enter this critical section to ensure that Next() doesn't + // use the old pointer any more before changing it (this is vital as + // the old pointer will be destroyed after we return). + CSLock lock(m_csCompleter); + + m_completer = completer; + } + + void UpdatePrefix(const wxString& prefix) + { + CSLock lock(m_csRestart); + + // We simply store the prefix here and will really update during the + // next call to our Next() method as we want to call Start() from the + // worker thread to prevent the main UI thread from blocking while the + // completions are generated. + m_prefix = prefix; + m_restart = TRUE; + } virtual HRESULT STDMETHODCALLTYPE Next(ULONG celt, LPOLESTR *rgelt, @@ -90,12 +178,22 @@ public: *pceltFetched = 0; - for ( const unsigned count = m_strings.size(); celt--; ++m_index ) + CSLock lock(m_csCompleter); + + if ( !RestartIfNeeded() ) + return S_FALSE; + + while ( celt-- ) { - if ( m_index == count ) + // Stop iterating if we need to update completions anyhow. + if ( m_restart ) return S_FALSE; - const wxWX2WCbuf wcbuf = m_strings[m_index].wc_str(); + const wxString s = m_completer->GetNext(); + if ( s.empty() ) + return S_FALSE; + + const wxWX2WCbuf wcbuf = s.wc_str(); const size_t size = (wcslen(wcbuf) + 1)*sizeof(wchar_t); void *olestr = CoTaskMemAlloc(size); if ( !olestr ) @@ -103,7 +201,7 @@ public: memcpy(olestr, wcbuf, size); - *rgelt++ = wx_static_cast(LPOLESTR, olestr); + *rgelt++ = static_cast(olestr); ++(*pceltFetched); } @@ -113,11 +211,21 @@ public: virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt) { - m_index += celt; - if ( m_index > m_strings.size() ) - { - m_index = m_strings.size(); + if ( !celt ) + return E_INVALIDARG; + + CSLock lock(m_csCompleter); + + if ( !RestartIfNeeded() ) return S_FALSE; + + while ( celt-- ) + { + if ( m_restart ) + return S_FALSE; + + if ( m_completer->GetNext().empty() ) + return S_FALSE; } return S_OK; @@ -125,7 +233,9 @@ public: virtual HRESULT STDMETHODCALLTYPE Reset() { - m_index = 0; + CSLock lock(m_csRestart); + + m_restart = TRUE; return S_OK; } @@ -135,27 +245,100 @@ public: if ( !ppEnum ) return E_POINTER; - wxIEnumString *e = new wxIEnumString(m_strings); - e->m_index = m_index; + CSLock lock(m_csCompleter); + wxIEnumString * const e = new wxIEnumString; e->AddRef(); + + e->ChangeCompleter(m_completer); + *ppEnum = e; return S_OK; } + DECLARE_IUNKNOWN_METHODS; + private: // dtor doesn't have to be virtual as we're only ever deleted from our own // Release() and are not meant to be derived form anyhow, but making it // virtual silences gcc warnings; making it private makes it impossible to // (mistakenly) delete us directly instead of calling Release() - virtual ~wxIEnumString() { } + virtual ~wxIEnumString() + { + ::DeleteCriticalSection(&m_csRestart); + ::DeleteCriticalSection(&m_csCompleter); + } + + // Common part of all ctors. + void Init() + { + ::InitializeCriticalSection(&m_csCompleter); + ::InitializeCriticalSection(&m_csRestart); + m_completer = NULL; + m_restart = FALSE; + } + + // Restart completions generation if needed. Should be only called from + // inside m_csCompleter. + // + // If false is returned, it means that there are no completions and that + // wxTextCompleter::GetNext() shouldn't be called at all. + bool RestartIfNeeded() + { + bool rc = true; + for ( ;; ) + { + wxString prefix; + LONG restart; + { + CSLock lock(m_csRestart); - const wxArrayString m_strings; - unsigned m_index; + prefix = m_prefix; + restart = m_restart; - DECLARE_NO_COPY_CLASS(wxIEnumString) + m_restart = FALSE; + } // Release m_csRestart before calling Start() to avoid blocking + // the main thread in UpdatePrefix() during its execution. + + if ( !restart ) + break; + + rc = m_completer->Start(prefix); + } + + return rc; + } + + + // Critical section protecting m_completer itself. It must be entered when + // using the pointer to ensure that we don't continue using a dangling one + // after it is destroyed. + CRITICAL_SECTION m_csCompleter; + + // The completer we delegate to for the completions generation. It is never + // NULL after the initial ChangeCompleter() call. + wxTextCompleter *m_completer; + + + // Critical section m_prefix and m_restart. It should be only entered for + // short periods of time, i.e. we shouldn't call any wxTextCompleter + // methods from inside, to prevent the main thread from blocking on it in + // UpdatePrefix(). + CRITICAL_SECTION m_csRestart; + + // If m_restart is true, we need to call wxTextCompleter::Start() with the + // given prefix to restart generating the completions. + wxString m_prefix; + + // Notice that we use LONG and not bool here to ensure that reading this + // value is atomic (32 bit reads are atomic operations under all Windows + // versions but reading bool isn't necessarily). + LONG m_restart; + + + wxDECLARE_NO_COPY_CLASS(wxIEnumString); }; BEGIN_IID_TABLE(wxIEnumString) @@ -165,22 +348,287 @@ END_IID_TABLE; IMPLEMENT_IUNKNOWN_METHODS(wxIEnumString) + +// This class gathers the all auto-complete-related stuff we use. It is +// allocated on demand by wxTextEntry when AutoComplete() is called. +class wxTextAutoCompleteData wxBIND_OR_CONNECT_HACK_ONLY_BASE_CLASS +{ +public: + // The constructor associates us with the given text entry. + wxTextAutoCompleteData(wxTextEntry *entry) + : m_entry(entry), + m_win(entry->GetEditableWindow()) + { + m_autoComplete = NULL; + m_autoCompleteDropDown = NULL; + m_enumStrings = NULL; + + m_fixedCompleter = NULL; + m_customCompleter = NULL; + + m_connectedCharEvent = false; + + // Create an object exposing IAutoComplete interface which we'll later + // use to get IAutoComplete2 as the latter can't be created directly, + // apparently. + HRESULT hr = CoCreateInstance + ( + wxCLSID_AutoComplete, + NULL, + CLSCTX_INPROC_SERVER, + IID_IAutoComplete, + reinterpret_cast(&m_autoComplete) + ); + if ( FAILED(hr) ) + { + wxLogApiError(wxT("CoCreateInstance(CLSID_AutoComplete)"), hr); + return; + } + + // Create a string enumerator and initialize the completer with it. + m_enumStrings = new wxIEnumString; + m_enumStrings->AddRef(); + hr = m_autoComplete->Init(m_entry->GetEditHWND(), m_enumStrings, + NULL, NULL); + if ( FAILED(hr) ) + { + wxLogApiError(wxT("IAutoComplete::Init"), hr); + + m_enumStrings->Release(); + m_enumStrings = NULL; + + return; + } + + // As explained in DoRefresh(), we need to call IAutoCompleteDropDown:: + // ResetEnumerator() if we want to be able to change the completions on + // the fly. In principle we could live without it, i.e. return true + // from IsOk() even if this QueryInterface() fails, but it doesn't look + // like this is ever going to have in practice anyhow as the shell- + // provided IAutoComplete always implements IAutoCompleteDropDown too. + hr = m_autoComplete->QueryInterface + ( + wxIID_IAutoCompleteDropDown, + reinterpret_cast(&m_autoCompleteDropDown) + ); + if ( FAILED(hr) ) + { + wxLogApiError(wxT("IAutoComplete::QI(IAutoCompleteDropDown)"), hr); + return; + } + + // Finally set the completion options using IAutoComplete2. + IAutoComplete2 *pAutoComplete2 = NULL; + hr = m_autoComplete->QueryInterface + ( + IID_IAutoComplete2, + reinterpret_cast(&pAutoComplete2) + ); + if ( SUCCEEDED(hr) ) + { + pAutoComplete2->SetOptions(ACO_AUTOSUGGEST | + ACO_AUTOAPPEND | + ACO_UPDOWNKEYDROPSLIST); + pAutoComplete2->Release(); + } + } + + ~wxTextAutoCompleteData() + { + delete m_customCompleter; + delete m_fixedCompleter; + + if ( m_enumStrings ) + m_enumStrings->Release(); + if ( m_autoCompleteDropDown ) + m_autoCompleteDropDown->Release(); + if ( m_autoComplete ) + m_autoComplete->Release(); + } + + // Must be called after creating this object to verify if initializing it + // succeeded. + bool IsOk() const + { + return m_autoComplete && m_autoCompleteDropDown && m_enumStrings; + } + + void ChangeStrings(const wxArrayString& strings) + { + if ( !m_fixedCompleter ) + m_fixedCompleter = new wxTextCompleterFixed; + + m_fixedCompleter->SetCompletions(strings); + + m_enumStrings->ChangeCompleter(m_fixedCompleter); + + DoRefresh(); + } + + // Takes ownership of the pointer if it is non-NULL. + bool ChangeCustomCompleter(wxTextCompleter *completer) + { + // Ensure that the old completer is not used any more before deleting + // it. + m_enumStrings->ChangeCompleter(completer); + + delete m_customCompleter; + m_customCompleter = completer; + + if ( m_customCompleter ) + { + // We postpone connecting to this event until we really need to do + // it (however we don't disconnect from it when we don't need it + // any more because we don't have wxUNBIND_OR_DISCONNECT_HACK...). + if ( !m_connectedCharEvent ) + { + m_connectedCharEvent = true; + + // Use the special wxEVT_AFTER_CHAR and not the usual + // wxEVT_CHAR here because we need to have the updated value of + // the text control in this handler in order to provide + // completions for the correct prefix and unfortunately we + // don't have any way to let DefWindowProc() run from our + // wxEVT_CHAR handler (as we must also let the other handlers + // defined at wx level run first). + // + // Notice that we can't use wxEVT_TEXT here + // neither as, due to our use of ACO_AUTOAPPEND, we get + // EN_CHANGE notifications from the control every time + // IAutoComplete auto-appends something to it. + wxBIND_OR_CONNECT_HACK(m_win, wxEVT_AFTER_CHAR, + wxKeyEventHandler, + wxTextAutoCompleteData::OnAfterChar, + this); + } + + UpdateStringsFromCustomCompleter(); + } + + return true; + } + + void DisableCompletion() + { + // We currently simply reset the list of possible strings as this seems + // to effectively disable auto-completion just fine. We could (and + // probably should) use IAutoComplete::Enable(FALSE) for this too but + // then we'd need to call Enable(TRUE) to turn it on back again later. + ChangeStrings(wxArrayString()); + } + +private: + // Must be called after changing the values to be returned by wxIEnumString + // to really make the changes stick. + void DoRefresh() + { + m_enumStrings->Reset(); + + // This is completely and utterly not documented and in fact the + // current MSDN seems to try to discourage us from using it by saying + // that "there is no reason to use this method unless the drop-down + // list is currently visible" but actually we absolutely must call it + // to force the auto-completer (and not just its drop-down!) to refresh + // the list of completions which could have changed now. Without this + // call the new choices returned by GetCompletions() that hadn't been + // returned by it before are simply silently ignored. + m_autoCompleteDropDown->ResetEnumerator(); + } + + // Update the strings returned by our string enumerator to correspond to + // the currently valid choices according to the custom completer. + void UpdateStringsFromCustomCompleter() + { + // As we use ACO_AUTOAPPEND, the selected part of the text is usually + // the one appended by us so don't consider it as part of the + // user-entered prefix. + long from, to; + m_entry->GetSelection(&from, &to); + + if ( to == from ) + from = m_entry->GetLastPosition(); // Take all if no selection. + + const wxString prefix = m_entry->GetRange(0, from); + + m_enumStrings->UpdatePrefix(prefix); + + DoRefresh(); + } + + void OnAfterChar(wxKeyEvent& event) + { + // Notice that we must not refresh the completions when the user + // presses Backspace as this would result in adding back the just + // erased character(s) because of ACO_AUTOAPPEND option we use. + if ( m_customCompleter && event.GetKeyCode() != WXK_BACK ) + UpdateStringsFromCustomCompleter(); + + event.Skip(); + } + + + // The text entry we're associated with. + wxTextEntry * const m_entry; + + // The window of this text entry. + wxWindow * const m_win; + + // The auto-completer object itself. + IAutoComplete *m_autoComplete; + + // Its IAutoCompleteDropDown interface needed for ResetEnumerator() call. + IAutoCompleteDropDown *m_autoCompleteDropDown; + + // Enumerator for strings currently used for auto-completion. + wxIEnumString *m_enumStrings; + + // Fixed string completer or NULL if none. + wxTextCompleterFixed *m_fixedCompleter; + + // Custom completer or NULL if none. + wxTextCompleter *m_customCompleter; + + // Initially false, set to true after connecting OnTextChanged() handler. + bool m_connectedCharEvent; + + + wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); +}; + #endif // HAS_AUTOCOMPLETE // ============================================================================ // wxTextEntry implementation // ============================================================================ +// ---------------------------------------------------------------------------- +// initialization and destruction +// ---------------------------------------------------------------------------- + +wxTextEntry::wxTextEntry() +{ +#ifdef HAS_AUTOCOMPLETE + m_autoCompleteData = NULL; +#endif // HAS_AUTOCOMPLETE +} + +wxTextEntry::~wxTextEntry() +{ +#ifdef HAS_AUTOCOMPLETE + delete m_autoCompleteData; +#endif // HAS_AUTOCOMPLETE +} + // ---------------------------------------------------------------------------- // operations on text // ---------------------------------------------------------------------------- void wxTextEntry::WriteText(const wxString& text) { - ::SendMessage(GetEditHwnd(), EM_REPLACESEL, 0, (LPARAM)text.wx_str()); + ::SendMessage(GetEditHwnd(), EM_REPLACESEL, 0, wxMSW_CONV_LPARAM(text)); } -wxString wxTextEntry::GetValue() const +wxString wxTextEntry::DoGetValue() const { return wxGetWindowText(GetEditHWND()); } @@ -243,6 +691,11 @@ bool wxTextEntry::CanRedo() const void wxTextEntry::SetInsertionPoint(long pos) { + // calling DoSetSelection(-1, -1) would select everything which is not what + // we want here + if ( pos == -1 ) + pos = GetLastPosition(); + // be careful to call DoSetSelection() which is overridden in wxTextCtrl // and not just SetSelection() here DoSetSelection(pos, pos); @@ -287,15 +740,20 @@ void wxTextEntry::GetSelection(long *from, long *to) const // auto-completion // ---------------------------------------------------------------------------- -bool wxTextEntry::AutoCompleteFileNames() -{ +#if wxUSE_OLE + #ifdef HAS_AUTOCOMPLETE + +#if wxUSE_DYNLIB_CLASS + +bool wxTextEntry::DoAutoCompleteFileNames(int flags) +{ typedef HRESULT (WINAPI *SHAutoComplete_t)(HWND, DWORD); static SHAutoComplete_t s_pfnSHAutoComplete = (SHAutoComplete_t)-1; static wxDynamicLibrary s_dllShlwapi; if ( s_pfnSHAutoComplete == (SHAutoComplete_t)-1 ) { - if ( !s_dllShlwapi.Load(_T("shlwapi.dll"), wxDL_VERBATIM | wxDL_QUIET) ) + if ( !s_dllShlwapi.Load(wxT("shlwapi.dll"), wxDL_VERBATIM | wxDL_QUIET) ) { s_pfnSHAutoComplete = NULL; } @@ -308,77 +766,111 @@ bool wxTextEntry::AutoCompleteFileNames() if ( !s_pfnSHAutoComplete ) return false; - HRESULT hr = (*s_pfnSHAutoComplete)(GetEditHwnd(), SHACF_FILESYS_ONLY); + DWORD dwFlags = 0; + if ( flags & wxFILE ) + dwFlags |= SHACF_FILESYS_ONLY; + else if ( flags & wxDIR ) + dwFlags |= SHACF_FILESYS_DIRS; + else + { + wxFAIL_MSG(wxS("No flags for file name auto completion?")); + return false; + } + + HRESULT hr = (*s_pfnSHAutoComplete)(GetEditHwnd(), dwFlags); if ( FAILED(hr) ) { - wxLogApiError(_T("SHAutoComplete()"), hr); + wxLogApiError(wxT("SHAutoComplete()"), hr); return false; } + + // Disable the other kinds of completion now that we use the built-in file + // names completion. + if ( m_autoCompleteData ) + m_autoCompleteData->DisableCompletion(); + return true; -#else // !HAS_AUTOCOMPLETE - return false; -#endif // HAS_AUTOCOMPLETE/!HAS_AUTOCOMPLETE } -bool wxTextEntry::AutoComplete(const wxArrayString& choices) +#endif // wxUSE_DYNLIB_CLASS + +wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() { -#ifdef HAS_AUTOCOMPLETE - // create an object exposing IAutoComplete interface (don't go for - // IAutoComplete2 immediately as, presumably, it might be not available on - // older systems as otherwise why do we have both -- although in practice I - // don't know when can this happen) - IAutoComplete *pAutoComplete = NULL; - HRESULT hr = CoCreateInstance - ( - CLSID_AutoComplete, - NULL, - CLSCTX_INPROC_SERVER, - IID_IAutoComplete, - wx_reinterpret_cast(void **, &pAutoComplete) - ); - if ( FAILED(hr) ) + if ( !m_autoCompleteData ) { - wxLogApiError(_T("CoCreateInstance(CLSID_AutoComplete)"), hr); - return false; + wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this); + if ( ac->IsOk() ) + m_autoCompleteData = ac; + else + delete ac; } - // associate it with our strings - wxIEnumString *pEnumString = new wxIEnumString(choices); - pEnumString->AddRef(); - hr = pAutoComplete->Init(GetEditHwnd(), pEnumString, NULL, NULL); - pEnumString->Release(); - if ( FAILED(hr) ) - { - wxLogApiError(_T("IAutoComplete::Init"), hr); + return m_autoCompleteData; +} + +bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +{ + wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); + if ( !ac ) return false; - } - // if IAutoComplete2 is available, set more user-friendly options - IAutoComplete2 *pAutoComplete2 = NULL; - hr = pAutoComplete->QueryInterface - ( - IID_IAutoComplete2, - wx_reinterpret_cast(void **, &pAutoComplete2) - ); - if ( SUCCEEDED(hr) ) + ac->ChangeStrings(choices); + + return true; +} + +bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) +{ + // First deal with the case when we just want to disable auto-completion. + if ( !completer ) + { + if ( m_autoCompleteData ) + m_autoCompleteData->DisableCompletion(); + //else: Nothing to do, we hadn't used auto-completion even before. + } + else // Have a valid completer. { - pAutoComplete2->SetOptions(ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST); - pAutoComplete2->Release(); + wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); + if ( !ac ) + { + // Delete the custom completer for consistency with the case when + // we succeed to avoid memory leaks in user code. + delete completer; + return false; + } + + // This gives ownership of the custom completer to m_autoCompleteData. + if ( !ac->ChangeCustomCompleter(completer) ) + return false; } - // the docs are unclear about when can we release it but it seems safe to - // do it immediately, presumably the edit control itself keeps a reference - // to the auto completer object - pAutoComplete->Release(); return true; +} + #else // !HAS_AUTOCOMPLETE - wxUnusedVar(choices); - return false; -#endif // HAS_AUTOCOMPLETE/!HAS_AUTOCOMPLETE +// We still need to define stubs as we declared these overrides in the header. + +bool wxTextEntry::DoAutoCompleteFileNames(int flags) +{ + return wxTextEntryBase::DoAutoCompleteFileNames(flags); } +bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +{ + return wxTextEntryBase::DoAutoCompleteStrings(choices); +} + +bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) +{ + return wxTextEntryBase::DoAutoCompleteCustom(completer); +} + +#endif // HAS_AUTOCOMPLETE/!HAS_AUTOCOMPLETE + +#endif // wxUSE_OLE + // ---------------------------------------------------------------------------- // editable state // ---------------------------------------------------------------------------- @@ -409,4 +901,91 @@ void wxTextEntry::SetMaxLength(unsigned long len) ::SendMessage(GetEditHwnd(), EM_LIMITTEXT, len, 0); } +// ---------------------------------------------------------------------------- +// hints +// ---------------------------------------------------------------------------- + +#if wxUSE_UXTHEME + +#ifndef EM_SETCUEBANNER + #define EM_SETCUEBANNER 0x1501 + #define EM_GETCUEBANNER 0x1502 +#endif + +bool wxTextEntry::SetHint(const wxString& hint) +{ + if ( wxUxThemeEngine::GetIfActive() ) + { + // notice that this message always works with Unicode strings + // + // we always use TRUE for wParam to show the hint even when the window + // has focus, otherwise there would be no way to show the hint for the + // initially focused window + if ( ::SendMessage(GetEditHwnd(), EM_SETCUEBANNER, + TRUE, (LPARAM)(const wchar_t *)hint.wc_str()) ) + return true; + } + + return wxTextEntryBase::SetHint(hint); +} + +wxString wxTextEntry::GetHint() const +{ + if ( wxUxThemeEngine::GetIfActive() ) + { + wchar_t buf[256]; + if ( ::SendMessage(GetEditHwnd(), EM_GETCUEBANNER, + (WPARAM)buf, WXSIZEOF(buf)) ) + return wxString(buf); + } + + return wxTextEntryBase::GetHint(); +} + + +#endif // wxUSE_UXTHEME + +// ---------------------------------------------------------------------------- +// margins support +// ---------------------------------------------------------------------------- + +bool wxTextEntry::DoSetMargins(const wxPoint& margins) +{ +#if !defined(__WXWINCE__) + bool res = true; + + if ( margins.x != -1 ) + { + // Set both horizontal margins to the given value, we don't distinguish + // between left and right margin at wx API level and it seems to be + // better to change both of them than only left one. + ::SendMessage(GetEditHwnd(), EM_SETMARGINS, + EC_LEFTMARGIN | EC_RIGHTMARGIN, + MAKELONG(margins.x, margins.x)); + } + + if ( margins.y != -1 ) + { + res = false; + } + + return res; +#else + return false; +#endif +} + +wxPoint wxTextEntry::DoGetMargins() const +{ +#if !defined(__WXWINCE__) + LRESULT lResult = ::SendMessage(GetEditHwnd(), EM_GETMARGINS, + 0, 0); + int left = LOWORD(lResult); + int top = -1; + return wxPoint(left, top); +#else + return wxPoint(-1, -1); +#endif +} + #endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX