From 63f7d5022e786be61c0226314dac98739f49a426 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 2 Mar 2009 12:25:01 +0000 Subject: [PATCH] added wxTextEntry::SetHint() (a.k.a. cue banner or placeholder string) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@59263 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 1 + include/wx/cocoa/combobox.h | 4 + include/wx/gtk/combobox.h | 2 +- include/wx/gtk/textctrl.h | 1 - include/wx/gtk/textentry.h | 4 - include/wx/gtk1/combobox.h | 3 + include/wx/motif/combobox.h | 2 + include/wx/msw/combobox.h | 10 ++- include/wx/msw/textentry.h | 5 ++ include/wx/os2/combobox.h | 4 +- include/wx/osx/combobox.h | 3 + include/wx/richtext/richtextctrl.h | 9 +-- include/wx/srchctrl.h | 4 + include/wx/textctrl.h | 3 + include/wx/textentry.h | 24 +++++- include/wx/univ/combobox.h | 3 + interface/wx/textentry.h | 30 ++++++++ samples/widgets/widgets.cpp | 28 +++++++ src/common/textentrycmn.cpp | 115 +++++++++++++++++++++++++++++ src/msw/combobox.cpp | 30 ++++++++ src/msw/textentry.cpp | 44 +++++++++++ 21 files changed, 311 insertions(+), 18 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index cf53c69a10..f2572d699d 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -406,6 +406,7 @@ All (GUI): - Added support for labels for toolbar controls (Vince Harron). - Added wxMessageDialog::SetMessage() and SetExtendedMessage(). - Added wxListCtrl::Set/GetColumnsOrder() (Yury Voronov). +- Added wxTextEntry::SetHint(). - Made wxLogWindow thread-safe (Barbara Maren Winkler). - Added wxWindow::AlwaysShowScrollbars() (Julian Scheid). - Added wxMouseEvent::GetClickCount() (Julian Scheid). diff --git a/include/wx/cocoa/combobox.h b/include/wx/cocoa/combobox.h index 2b6e0b4882..d0f1ad8eda 100644 --- a/include/wx/cocoa/combobox.h +++ b/include/wx/cocoa/combobox.h @@ -149,6 +149,10 @@ public: virtual void GetSelection(long *from, long *to) const; virtual bool IsEditable() const; virtual void SetEditable(bool editable); + +private: + // implement wxTextEntry pure virtual method + virtual wxWindow *GetEditableWindow() { return this; } }; #endif // __WX_COCOA_COMBOBOX_H__ diff --git a/include/wx/gtk/combobox.h b/include/wx/gtk/combobox.h index 438dc7c3ad..d715d1dae8 100644 --- a/include/wx/gtk/combobox.h +++ b/include/wx/gtk/combobox.h @@ -142,7 +142,7 @@ protected: private: // From wxTextEntry: - virtual const wxWindow *GetEditableWindow() const { return this; } + virtual wxWindow *GetEditableWindow() { return this; } virtual GtkEditable *GetEditable() const; virtual void EnableTextChangedEvents(bool enable) { diff --git a/include/wx/gtk/textctrl.h b/include/wx/gtk/textctrl.h index a06a8a6345..a4371c7c98 100644 --- a/include/wx/gtk/textctrl.h +++ b/include/wx/gtk/textctrl.h @@ -177,7 +177,6 @@ protected: private: // overridden wxTextEntry virtual methods - virtual const wxWindow *GetEditableWindow() const { return this; } virtual GtkEditable *GetEditable() const; virtual void EnableTextChangedEvents(bool enable); diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index eb329722cd..91db71ae11 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -54,10 +54,6 @@ public: void SendMaxLenEvent(); private: - // implement this to return the associated window, it will be used for - // event generation - virtual const wxWindow *GetEditableWindow() const = 0; - // implement this to return the associated GtkEntry or another widget // implementing GtkEditable virtual GtkEditable *GetEditable() const = 0; diff --git a/include/wx/gtk1/combobox.h b/include/wx/gtk1/combobox.h index d064f57a33..079d0f56d0 100644 --- a/include/wx/gtk1/combobox.h +++ b/include/wx/gtk1/combobox.h @@ -169,6 +169,9 @@ protected: virtual wxSize DoGetBestSize() const; + // implement wxTextEntry pure virtual method + virtual wxWindow *GetEditableWindow() { return this; } + // Widgets that use the style->base colour for the BG colour should // override this and return true. virtual bool UseGTKStyleBase() const { return true; } diff --git a/include/wx/motif/combobox.h b/include/wx/motif/combobox.h index 63eb9018e4..e7e3598ed6 100644 --- a/include/wx/motif/combobox.h +++ b/include/wx/motif/combobox.h @@ -107,6 +107,8 @@ protected: int width, int height, int sizeFlags = wxSIZE_AUTO); + // implement wxTextEntry pure virtual methods + virtual wxWindow *GetEditableWindow() { return this; } virtual WXWidget GetTextWidget() const; private: diff --git a/include/wx/msw/combobox.h b/include/wx/msw/combobox.h index e96d4d95c4..8339b4cb0c 100644 --- a/include/wx/msw/combobox.h +++ b/include/wx/msw/combobox.h @@ -116,6 +116,11 @@ public: virtual WXDWORD MSWGetStyle(long style, WXDWORD *exstyle) const; +#if wxUSE_UXTHEME + // override wxTextEntry method to work around Windows bug + virtual bool SetHint(const wxString& hint); +#endif // wxUSE_UXTHEME + protected: #if wxUSE_TOOLTIPS virtual void DoSetToolTip(wxToolTip *tip); @@ -136,8 +141,9 @@ protected: } private: - // this is the overridden wxTextEntry method which should only be called - // when we do have an edit control so it asserts if this is not the case + // there are the overridden wxTextEntry methods which should only be called + // when we do have an edit control so they assert if this is not the case + virtual wxWindow *GetEditableWindow(); virtual WXHWND GetEditHWND() const; // common part of all ctors diff --git a/include/wx/msw/textentry.h b/include/wx/msw/textentry.h index e3a523b825..25ee6c62cd 100644 --- a/include/wx/msw/textentry.h +++ b/include/wx/msw/textentry.h @@ -54,6 +54,11 @@ public: virtual void SetMaxLength(unsigned long len); +#if wxUSE_UXTHEME + virtual bool SetHint(const wxString& hint); + virtual wxString GetHint() const; +#endif // wxUSE_UXTHEME + protected: // this is really a hook for multiline text controls as the single line // ones don't need to ever scroll to show the selection but having it here diff --git a/include/wx/os2/combobox.h b/include/wx/os2/combobox.h index f95d442a2f..b5e7d293cb 100644 --- a/include/wx/os2/combobox.h +++ b/include/wx/os2/combobox.h @@ -122,8 +122,8 @@ class WXDLLIMPEXP_CORE wxComboBox : public wxChoice, ); private: - // implement wxTextEntry pure virtual: it implements all the operations for - // the simple EDIT controls + // implement wxTextEntry pure virtual methods + virtual wxWindow *GetEditableWindow() { return this; } virtual WXHWND GetEditHWND() const { return m_hWnd; } DECLARE_DYNAMIC_CLASS(wxComboBox) diff --git a/include/wx/osx/combobox.h b/include/wx/osx/combobox.h index 69c96872f9..e115a4c847 100644 --- a/include/wx/osx/combobox.h +++ b/include/wx/osx/combobox.h @@ -154,6 +154,9 @@ protected: virtual void SetClientDataType(wxClientDataType clientDataItemsType); + // implement wxTextEntry pure virtual method + virtual wxWindow *GetEditableWindow() { return this; } + // the subcontrols wxComboBoxText* m_text; wxComboBoxChoice* m_choice; diff --git a/include/wx/richtext/richtextctrl.h b/include/wx/richtext/richtextctrl.h index 5d60b8e042..b308b57d87 100644 --- a/include/wx/richtext/richtextctrl.h +++ b/include/wx/richtext/richtextctrl.h @@ -771,16 +771,13 @@ public: static const wxArrayString& GetAvailableFontNames(); static void ClearAvailableFontNames(); + // FIXME: this does not work, it allows this code to compile but will fail + // during run-time #ifdef __WXMSW__ virtual WXHWND GetEditHWND() const { return GetHWND(); } #endif #ifdef __WXGTK__ - // implement this to return the associated window, it will be used for - // event generation - virtual const wxWindow *GetEditableWindow() const { return NULL; } - - // implement this to return the associated GtkEntry or another widget - // implementing GtkEditable + virtual const wxWindow *GetEditableWindow() { return this; } virtual GtkEditable *GetEditable() const { return NULL; } #endif diff --git a/include/wx/srchctrl.h b/include/wx/srchctrl.h index ccd27a29be..948aa2c0ce 100644 --- a/include/wx/srchctrl.h +++ b/include/wx/srchctrl.h @@ -64,6 +64,10 @@ public: virtual void ShowCancelButton( bool show ) = 0; virtual bool IsCancelButtonVisible() const = 0; + +private: + // implement wxTextEntry pure virtual method + virtual wxWindow *GetEditableWindow() { return this; } }; diff --git a/include/wx/textctrl.h b/include/wx/textctrl.h index c1fb770632..318af3e53f 100644 --- a/include/wx/textctrl.h +++ b/include/wx/textctrl.h @@ -692,6 +692,9 @@ protected: virtual bool DoLoadFile(const wxString& file, int fileType); virtual bool DoSaveFile(const wxString& file, int fileType); +private: + // implement the wxTextEntry pure virtual method + virtual wxWindow *GetEditableWindow() { return this; } wxDECLARE_NO_COPY_CLASS(wxTextCtrlBase); DECLARE_ABSTRACT_CLASS(wxTextCtrlBase) diff --git a/include/wx/textentry.h b/include/wx/textentry.h index 28bffe9913..1194b61f90 100644 --- a/include/wx/textentry.h +++ b/include/wx/textentry.h @@ -16,6 +16,7 @@ typedef long wxTextPos; class WXDLLIMPEXP_FWD_BASE wxArrayString; +class WXDLLIMPEXP_FWD_CORE wxTextEntryHintData; // ---------------------------------------------------------------------------- // wxTextEntryBase @@ -24,8 +25,8 @@ class WXDLLIMPEXP_FWD_BASE wxArrayString; class WXDLLIMPEXP_CORE wxTextEntryBase { public: - wxTextEntryBase() { m_eventsBlock = 0; } - virtual ~wxTextEntryBase() { } + wxTextEntryBase() { m_eventsBlock = 0; m_hintData = NULL; } + virtual ~wxTextEntryBase(); // accessing the value @@ -129,6 +130,17 @@ public: virtual void SetMaxLength(unsigned long WXUNUSED(len)) { } + // hints + // ----- + + // hint is the (usually greyed out) text shown in the control as long as + // it's empty and doesn't have focus, it is typically used in controls used + // for searching to let the user know what is supposed to be entered there + + virtual bool SetHint(const wxString& hint); + virtual wxString GetHint() const; + + protected: // flags for DoSetValue(): common part of SetValue() and ChangeValue() and // also used to implement WriteText() in wxMSW @@ -172,6 +184,11 @@ protected: bool EventsAllowed() const { return m_eventsBlock == 0; } private: + // override this to return the associated window, it will be used for event + // generation and also by generic hints implementation + virtual wxWindow *GetEditableWindow() = 0; + + // suppress or resume the text changed events generation: don't use these // functions directly, use EventsSuppressor class above instead void SuppressTextChangedEvents() @@ -196,6 +213,9 @@ private: // if this counter is non-null, events are blocked unsigned m_eventsBlock; + + // hint-related stuff, only allocated if/when SetHint() is used + wxTextEntryHintData *m_hintData; }; #ifdef __WXUNIVERSAL__ diff --git a/include/wx/univ/combobox.h b/include/wx/univ/combobox.h index 2b26eb5180..30a46691f6 100644 --- a/include/wx/univ/combobox.h +++ b/include/wx/univ/combobox.h @@ -166,6 +166,9 @@ protected: wxListBox *GetLBox() const { return m_lbox; } private: + // implement wxTextEntry pure virtual method + virtual wxWindow *GetEditableWindow() { return this; } + // the popup listbox wxListBox *m_lbox; diff --git a/interface/wx/textentry.h b/interface/wx/textentry.h index df1ddc9100..bec4b6a928 100644 --- a/interface/wx/textentry.h +++ b/interface/wx/textentry.h @@ -351,6 +351,36 @@ public: */ virtual void SelectAll(); + /** + Sets a hint shown in an empty unfocused text control. + + The hints are usually used to indicate to the user what is supposed to + be entered into the given entry field, e.g. a common use of them is to + show an explanation of what can be entered in a wxSearchCtrl. + + The hint is shown (usually greyed out) for an empty control until it + gets focus and is shown again if the control loses it and remains + empty. It won't be shown once the control has a non-empty value, + although it will be shown again if the control contents is cleared. + Because of this, it generally only makes sense to use hints with the + controls which are initially empty. + + Notice that hints are known as cue banners under MSW or + placeholder strings under OS X. + + @since 2.9.0 + */ + virtual void SetHint(const wxString& hint); + + /** + Returns the current hint string. + + See SetHint() for more information about hints. + + @since 2.9.0 + */ + virtual wxString GetHint() const; + /** Sets the new text control value. diff --git a/samples/widgets/widgets.cpp b/samples/widgets/widgets.cpp index 19d86f02e3..b3da0a0d2c 100644 --- a/samples/widgets/widgets.cpp +++ b/samples/widgets/widgets.cpp @@ -97,6 +97,8 @@ enum TextEntry_DisableAutoComplete = TextEntry_Begin, TextEntry_AutoCompleteFixed, TextEntry_AutoCompleteFilenames, + + TextEntry_SetHint, TextEntry_End }; @@ -164,10 +166,13 @@ protected: void OnToggleGlobalBusyCursor(wxCommandEvent& event); void OnToggleBusyCursor(wxCommandEvent& event); + // wxTextEntry-specific tests void OnDisableAutoComplete(wxCommandEvent& event); void OnAutoCompleteFixed(wxCommandEvent& event); void OnAutoCompleteFilenames(wxCommandEvent& event); + void OnSetHint(wxCommandEvent& event); + void OnUpdateTextUI(wxUpdateUIEvent& event) { event.Enable( CurrentPage()->GetTextEntry() != NULL ); @@ -308,6 +313,8 @@ BEGIN_EVENT_TABLE(WidgetsFrame, wxFrame) EVT_MENU(TextEntry_AutoCompleteFixed, WidgetsFrame::OnAutoCompleteFixed) EVT_MENU(TextEntry_AutoCompleteFilenames, WidgetsFrame::OnAutoCompleteFilenames) + EVT_MENU(TextEntry_SetHint, WidgetsFrame::OnSetHint) + EVT_UPDATE_UI_RANGE(TextEntry_Begin, TextEntry_End - 1, WidgetsFrame::OnUpdateTextUI) @@ -419,6 +426,8 @@ WidgetsFrame::WidgetsFrame(const wxString& title) _T("Fixed-&list auto-completion")); menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteFilenames, _T("&Files names auto-completion")); + menuTextEntry->AppendSeparator(); + menuTextEntry->Append(TextEntry_SetHint, "Set help &hint"); mbar->Append(menuTextEntry, _T("&Text")); @@ -948,6 +957,25 @@ void WidgetsFrame::OnAutoCompleteFilenames(wxCommandEvent& WXUNUSED(event)) wxLogMessage("AutoCompleteFileNames() failed."); } +void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event)) +{ + wxTextEntryBase *entry = CurrentPage()->GetTextEntry(); + wxCHECK_RET( entry, "menu item should be disabled" ); + + static wxString s_hint("Type here"); + wxString + hint = wxGetTextFromUser("Text hint:", "Widgets sample", s_hint, this); + if ( hint.empty() ) + return; + + s_hint = hint; + + if ( entry->SetHint(hint) ) + wxLogMessage("Set hint to \"%s\".", hint); + else + wxLogMessage("Text hints not supported."); +} + #endif // wxUSE_MENUS // ---------------------------------------------------------------------------- diff --git a/src/common/textentrycmn.cpp b/src/common/textentrycmn.cpp index b179499381..a0e31da051 100644 --- a/src/common/textentrycmn.cpp +++ b/src/common/textentrycmn.cpp @@ -33,10 +33,98 @@ #include "wx/textentry.h" #include "wx/clipbrd.h" +// ---------------------------------------------------------------------------- +// wxTextEntryHintData +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_CORE wxTextEntryHintData wxBIND_OR_CONNECT_HACK_ONLY_BASE_CLASS +{ +public: + wxTextEntryHintData(wxTextEntryBase *entry, wxWindow *win) + : m_entry(entry), + m_win(win) + { + wxBIND_OR_CONNECT_HACK(win, wxEVT_SET_FOCUS, wxFocusEventHandler, + wxTextEntryHintData::OnSetFocus, this); + wxBIND_OR_CONNECT_HACK(win, wxEVT_KILL_FOCUS, wxFocusEventHandler, + wxTextEntryHintData::OnKillFocus, this); + } + + // default dtor is ok + + void SetHintString(const wxString& hint) + { + m_hint = hint; + + if ( ShowsHint() ) + { + // update it immediately + m_entry->ChangeValue(hint); + } + //else: the new hint will be shown later + } + + const wxString& GetHintString() const { return m_hint; } + +private: + // are we showing the hint right now? + bool ShowsHint() const + { + return m_entry->GetValue() == m_hint; + } + + void OnSetFocus(wxFocusEvent& event) + { + // hide the hint if we were showing it + if ( ShowsHint() ) + { + // Clear() would send an event which we don't want, so do it like + // this + m_entry->ChangeValue(wxString()); + m_win->SetForegroundColour(m_colFg); + } + + event.Skip(); + } + + void OnKillFocus(wxFocusEvent& event) + { + // restore the hint if the user didn't do anything in the control + if ( m_entry->IsEmpty() ) + { + m_entry->ChangeValue(m_hint); + + m_colFg = m_win->GetForegroundColour(); + m_win->SetForegroundColour(*wxLIGHT_GREY); + } + + event.Skip(); + } + + + wxTextEntryBase * const m_entry; + wxWindow * const m_win; + + wxColour m_colFg; + + wxString m_hint; + + wxDECLARE_NO_COPY_CLASS(wxTextEntryHintData); +}; + // ============================================================================ // wxTextEntryBase implementation // ============================================================================ +wxTextEntryBase::~wxTextEntryBase() +{ + delete m_hintData; +} + +// ---------------------------------------------------------------------------- +// text operations +// ---------------------------------------------------------------------------- + wxString wxTextEntryBase::GetRange(long from, long to) const { wxString sel; @@ -77,6 +165,10 @@ void wxTextEntryBase::Replace(long from, long to, const wxString& value) WriteText(value); } +// ---------------------------------------------------------------------------- +// selection +// ---------------------------------------------------------------------------- + bool wxTextEntryBase::HasSelection() const { long from, to; @@ -101,6 +193,10 @@ wxString wxTextEntryBase::GetStringSelection() const return GetRange(from, to); } +// ---------------------------------------------------------------------------- +// clipboard +// ---------------------------------------------------------------------------- + bool wxTextEntryBase::CanCopy() const { return HasSelection(); @@ -131,4 +227,23 @@ bool wxTextEntryBase::CanPaste() const return false; } +// ---------------------------------------------------------------------------- +// hints support +// ---------------------------------------------------------------------------- + +bool wxTextEntryBase::SetHint(const wxString& hint) +{ + if ( !m_hintData ) + m_hintData = new wxTextEntryHintData(this, GetEditableWindow()); + + m_hintData->SetHintString(hint); + + return true; +} + +wxString wxTextEntryBase::GetHint() const +{ + return m_hintData ? m_hintData->GetHintString() : wxString(); +} + #endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX diff --git a/src/msw/combobox.cpp b/src/msw/combobox.cpp index ca40d744fc..7ae8dfc956 100644 --- a/src/msw/combobox.cpp +++ b/src/msw/combobox.cpp @@ -42,6 +42,10 @@ #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 @@ -445,6 +449,14 @@ WXHWND wxComboBox::GetEditHWND() const return hWndEdit; } +wxWindow *wxComboBox::GetEditableWindow() +{ + wxASSERT_MSG( !HasFlag(wxCB_READONLY), + _T("read-only combobox doesn't have any edit control") ); + + return this; +} + // ---------------------------------------------------------------------------- // wxComboBox creation // ---------------------------------------------------------------------------- @@ -665,4 +677,22 @@ void wxComboBox::DoSetToolTip(wxToolTip *tip) #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 diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index 7a30e5daf8..968f913fd1 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -35,6 +35,10 @@ #include "wx/msw/private.h" +#if wxUSE_UXTHEME + #include "wx/msw/uxtheme.h" +#endif + #define GetEditHwnd() ((HWND)(GetEditHWND())) // ---------------------------------------------------------------------------- @@ -416,4 +420,44 @@ 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 + if ( ::SendMessage(GetEditHwnd(), EM_SETCUEBANNER, + 0, (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 buf; + } + + return wxTextEntryBase::GetHint(); +} + + +#endif // wxUSE_UXTHEME + #endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX -- 2.45.2