#include "wx/textcompleter.h"
#include "wx/dynlib.h"
+#include <initguid.h>
+
#include "wx/msw/private.h"
#if wxUSE_UXTHEME
#include "wx/msw/ole/oleutils.h"
#include <shldisp.h>
-#include <shobjidl.h>
#if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__)
// needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST
#include <shlguid.h>
+
+ #ifndef ACO_AUTOAPPEND
+ #define ACO_AUTOAPPEND 0x02
+ #endif
#endif
#ifndef ACO_UPDOWNKEYDROPSLIST
#define SHACF_FILESYS_ONLY 0x00000010
#endif
-DEFINE_GUID(CLSID_AutoComplete,
- 0x00bb2763, 0x6a77, 0x11d0, 0xa5, 0x35, 0x00, 0xc0, 0x4f, 0xd7, 0xd0, 0x62);
+#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
} // anonymity namespace
// Implementation of string enumerator used by wxTextAutoCompleteData. This
-// class simply iterates over its strings except that it also can be told to
-// update them from the custom completer if one is used.
+// 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.
wxIEnumString()
{
Init();
-
- m_index = 0;
}
- wxIEnumString(const wxIEnumString& other)
- : m_strings(other.m_strings),
- m_index(other.m_index)
+ void ChangeCompleter(wxTextCompleter *completer)
{
- Init();
- }
+ // Indicate to Next() that it should bail out as soon as possible.
+ {
+ CSLock lock(m_csRestart);
- void ChangeStrings(const wxArrayString& strings)
- {
- CSLock lock(m_csStrings);
+ 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_strings = strings;
- m_index = 0;
+ m_completer = completer;
}
- void UpdateStringsFromCompleter(wxTextCompleter *completer,
- const wxString& prefix)
+ void UpdatePrefix(const wxString& prefix)
{
- CSLock lock(m_csUpdate);
+ CSLock lock(m_csRestart);
- // We simply store the pointer here and will really update during the
- // next call to our Next() method as we want to call GetCompletions()
- // from the worker thread to prevent the main UI thread from blocking
- // while the completions are generated.
- m_completer = completer;
+ // 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,
*pceltFetched = 0;
- CSLock lock(m_csStrings);
+ CSLock lock(m_csCompleter);
- DoUpdateIfNeeded();
+ if ( !RestartIfNeeded() )
+ return S_FALSE;
- for ( const unsigned count = m_strings.size(); celt--; ++m_index )
+ 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 )
virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt)
{
- CSLock lock(m_csStrings);
+ if ( !celt )
+ return E_INVALIDARG;
- m_index += celt;
- if ( m_index > m_strings.size() )
- {
- m_index = m_strings.size();
+ 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;
virtual HRESULT STDMETHODCALLTYPE Reset()
{
- {
- CSLock lock(m_csUpdate);
+ CSLock lock(m_csRestart);
- if ( m_completer )
- {
- // We will update the string from completer soon (or maybe are
- // already in process of doing it) so don't do anything now, it
- // is useless at best and could also result in a deadlock if
- // m_csStrings is already locked by Next().
- return S_OK;
- }
- }
-
- CSLock lock(m_csStrings);
-
- m_index = 0;
+ m_restart = TRUE;
return S_OK;
}
if ( !ppEnum )
return E_POINTER;
- CSLock lock(m_csStrings);
-
- wxIEnumString *e = new wxIEnumString(*this);
+ CSLock lock(m_csCompleter);
+ wxIEnumString * const e = new wxIEnumString;
e->AddRef();
+
+ e->ChangeCompleter(m_completer);
+
*ppEnum = e;
return S_OK;
// (mistakenly) delete us directly instead of calling Release()
virtual ~wxIEnumString()
{
- ::DeleteCriticalSection(&m_csStrings);
- ::DeleteCriticalSection(&m_csUpdate);
+ ::DeleteCriticalSection(&m_csRestart);
+ ::DeleteCriticalSection(&m_csCompleter);
}
// Common part of all ctors.
void Init()
{
- ::InitializeCriticalSection(&m_csUpdate);
- ::InitializeCriticalSection(&m_csStrings);
+ ::InitializeCriticalSection(&m_csCompleter);
+ ::InitializeCriticalSection(&m_csRestart);
m_completer = NULL;
+ m_restart = FALSE;
}
- void DoUpdateIfNeeded()
+ // 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()
{
- wxTextCompleter *completer;
- wxString prefix;
+ bool rc = true;
+ for ( ;; )
{
- CSLock lock(m_csUpdate);
-
- completer = m_completer;
- if ( !completer )
- return;
+ wxString prefix;
+ LONG restart;
+ {
+ CSLock lock(m_csRestart);
- prefix = m_prefix;
- } // Unlock m_csUpdate to allow the main thread to call our
- // UpdateStringsFromCompleter() without blocking while we are
- // generating completions.
+ prefix = m_prefix;
+ restart = m_restart;
- // Notice that m_csStrings is locked by our caller already so no need
- // to enter it again.
- m_index = 0;
- m_strings.clear();
- completer->GetCompletions(prefix, m_strings);
+ m_restart = FALSE;
+ } // Release m_csRestart before calling Start() to avoid blocking
+ // the main thread in UpdatePrefix() during its execution.
- {
- CSLock lock(m_csUpdate);
+ if ( !restart )
+ break;
- if ( m_completer == completer && m_prefix == prefix )
- {
- // There were no calls to UpdateStringsFromCompleter() while we
- // generated the completions, so our completions are still
- // pertinent.
- m_completer = NULL;
- return;
- }
- //else: Our completions are already out of date, regenerate them
- // once again.
+ rc = m_completer->Start(prefix);
}
+
+ return rc;
}
- // Critical section protecting m_strings and m_index.
- CRITICAL_SECTION m_csStrings;
+ // 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;
- // The strings we iterate over and the current iteration index.
- wxArrayString m_strings;
- unsigned m_index;
- // This one protects just m_completer, it is different from m_csStrings
- // because the main thread should be able to update our completer prefix
- // without blocking (as this would freeze the UI) even while we're inside a
- // possibly long process of updating m_strings.
- CRITICAL_SECTION m_csUpdate;
+ // 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 completer pointer is non-NULL, we must use it by calling its
- // GetCompletions() method with the specified prefix to update the list of
- // strings we iterate over when our Next() is called the next time.
- wxTextCompleter *m_completer;
+ // 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_ASSIGN_CLASS(wxIEnumString);
+
+ wxDECLARE_NO_COPY_CLASS(wxIEnumString);
};
BEGIN_IID_TABLE(wxIEnumString)
IMPLEMENT_IUNKNOWN_METHODS(wxIEnumString)
-// This class gathers the auto-complete-related we use. It is allocated on
-// demand by wxTextEntry when AutoComplete() is called.
+// 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:
m_autoComplete = NULL;
m_autoCompleteDropDown = NULL;
m_enumStrings = NULL;
+
+ m_fixedCompleter = NULL;
m_customCompleter = NULL;
m_connectedCharEvent = false;
// apparently.
HRESULT hr = CoCreateInstance
(
- CLSID_AutoComplete,
+ wxCLSID_AutoComplete,
NULL,
CLSCTX_INPROC_SERVER,
IID_IAutoComplete,
// provided IAutoComplete always implements IAutoCompleteDropDown too.
hr = m_autoComplete->QueryInterface
(
- IID_IAutoCompleteDropDown,
+ wxIID_IAutoCompleteDropDown,
reinterpret_cast<void **>(&m_autoCompleteDropDown)
);
if ( FAILED(hr) )
~wxTextAutoCompleteData()
{
delete m_customCompleter;
+ delete m_fixedCompleter;
if ( m_enumStrings )
m_enumStrings->Release();
void ChangeStrings(const wxArrayString& strings)
{
- m_enumStrings->ChangeStrings(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;
// wxEVT_CHAR handler (as we must also let the other handlers
// defined at wx level run first).
//
- // Notice that we can't use wxEVT_COMMAND_TEXT_UPDATED here
+ // 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.
// 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.
-
- m_enumStrings->ChangeStrings(wxArrayString());
-
- ChangeCustomCompleter(NULL);
+ ChangeStrings(wxArrayString());
}
private:
- // Must be called after changing wxIEnumString::m_strings to really make
- // the changes stick.
+ // Must be called after changing the values to be returned by wxIEnumString
+ // to really make the changes stick.
void DoRefresh()
{
m_enumStrings->Reset();
const wxString prefix = m_entry->GetRange(0, from);
- m_enumStrings->UpdateStringsFromCompleter(m_customCompleter, prefix);
+ m_enumStrings->UpdatePrefix(prefix);
DoRefresh();
}
// 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;
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::DoGetValue() const
#ifdef HAS_AUTOCOMPLETE
-bool wxTextEntry::DoAutoCompleteFileNames()
+#if wxUSE_DYNLIB_CLASS
+
+bool wxTextEntry::DoAutoCompleteFileNames(int flags)
{
typedef HRESULT (WINAPI *SHAutoComplete_t)(HWND, DWORD);
static SHAutoComplete_t s_pfnSHAutoComplete = (SHAutoComplete_t)-1;
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(wxT("SHAutoComplete()"), hr);
return true;
}
+#endif // wxUSE_DYNLIB_CLASS
+
wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter()
{
if ( !m_autoCompleteData )
// We still need to define stubs as we declared these overrides in the header.
-bool wxTextEntry::DoAutoCompleteFileNames()
+bool wxTextEntry::DoAutoCompleteFileNames(int flags)
{
- return wxTextEntryBase::DoAutoCompleteFileNames();
+ 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
if ( margins.x != -1 )
{
- // left margin
+ // 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, MAKELONG(margins.x, 0));
+ EC_LEFTMARGIN | EC_RIGHTMARGIN,
+ MAKELONG(margins.x, margins.x));
}
if ( margins.y != -1 )