1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/srchctrl.cpp
3 // Purpose:     wxSearchCtrl implementation - native
4 // Author:      Kettab Ali
5 // Created:     2019-12-23
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 
13 #if wxUSE_SEARCHCTRL
14 
15 #include "wx/srchctrl.h"
16 
17 #ifndef WX_PRECOMP
18     #include "wx/menu.h"
19 #endif //WX_PRECOMP
20 
21 #include "wx/utils.h"
22 #include "wx/gtk/private.h"
23 #include "wx/gtk/private/gtk3-compat.h"
24 
25 
26 #if GTK_CHECK_VERSION(3,6,0)
27     // GtkSearchEntry is available only for GTK+ >= 3.6
28     #define wxHAS_GTK_SEARCH_ENTRY
29 #endif // GTK >= 3.6
30 
31 namespace // anonymous
32 {
33 
34 // A more readable way to check for GtkSearchEntry availability.
HasGtkSearchEntry()35 inline bool HasGtkSearchEntry()
36 {
37 #ifdef wxHAS_GTK_SEARCH_ENTRY
38     return wx_is_at_least_gtk3(6);
39 #else
40     return false;
41 #endif
42 }
43 
CreateGtkSearchEntryIfAvailable()44 inline GtkWidget* CreateGtkSearchEntryIfAvailable()
45 {
46 #ifdef wxHAS_GTK_SEARCH_ENTRY
47     if ( wx_is_at_least_gtk3(6) )
48     {
49         return gtk_search_entry_new();
50     }
51 #endif // wxHAS_GTK_SEARCH_ENTRY
52 
53     // No GtkSearchEntry! fallback to the plain GtkEntry.
54     return gtk_entry_new();
55 }
56 
57 }
58 
59 // ============================================================================
60 // signal handlers implementation
61 // ============================================================================
62 
63 extern "C" {
64 
65 static void
wx_gtk_icon_press(GtkEntry * WXUNUSED (entry),gint position,GdkEventButton * WXUNUSED (event),wxSearchCtrl * ctrl)66 wx_gtk_icon_press(GtkEntry* WXUNUSED(entry),
67                   gint position,
68                   GdkEventButton* WXUNUSED(event),
69                   wxSearchCtrl* ctrl)
70 {
71     if ( position == GTK_ENTRY_ICON_PRIMARY )
72     {
73         // 1- Notice that contrary to the generic version, we don't generate the
74         //    wxEVT_SEARCH event here, which is the native behaviour of the
75         //    GtkSearchEntry (the search icon is inactive for a GtkSearchEntry).
76         //
77         // 2- If the wxSearchCtrl has a menu associated with it, we explicitly
78         //    make the search icon clickable as a way to display the menu.
79 
80         ctrl->PopupSearchMenu();
81     }
82     else // position == GTK_ENTRY_ICON_SECONDARY
83     {
84         if ( !HasGtkSearchEntry() )
85         {
86             // No need to call this for a GtkSearchEntry.
87             ctrl->Clear();
88         }
89 
90         wxCommandEvent event(wxEVT_SEARCH_CANCEL, ctrl->GetId());
91         event.SetEventObject(ctrl);
92         ctrl->HandleWindowEvent(event);
93     }
94 }
95 
96 }
97 
98 // ============================================================================
99 // wxSearchCtrl implementation
100 // ============================================================================
101 wxBEGIN_EVENT_TABLE(wxSearchCtrl, wxControl)
102     EVT_CHAR(wxSearchCtrl::OnChar)
103     EVT_TEXT(wxID_ANY, wxSearchCtrl::OnText)
104     EVT_TEXT_ENTER(wxID_ANY, wxSearchCtrl::OnTextEnter)
105 wxEND_EVENT_TABLE()
106 
107 wxIMPLEMENT_DYNAMIC_CLASS(wxSearchCtrl, wxControl);
108 
109 // ----------------------------------------------------------------------------
110 //  creation/destruction
111 // ----------------------------------------------------------------------------
112 
113 // destruction
114 // -----------
~wxSearchCtrl()115 wxSearchCtrl::~wxSearchCtrl()
116 {
117 #if wxUSE_MENUS
118     delete m_menu;
119 #endif // wxUSE_MENUS
120 }
121 
122 // creation
123 // --------
124 
Init()125 void wxSearchCtrl::Init()
126 {
127     m_entry = NULL;
128 
129 #if wxUSE_MENUS
130     m_menu = NULL;
131 #endif // wxUSE_MENUS
132 
133     m_cancelButtonVisible = false;
134 }
135 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)136 bool wxSearchCtrl::Create(wxWindow *parent, wxWindowID id,
137             const wxString& value,
138             const wxPoint& pos,
139             const wxSize& size,
140             long style,
141             const wxValidator& validator,
142             const wxString& name)
143 {
144     if ( !PreCreation(parent, pos, size) ||
145          !CreateBase(parent, id, pos, size, style | wxTE_PROCESS_ENTER,
146                      validator, name) )
147     {
148         wxFAIL_MSG( "wxSearchCtrl creation failed" );
149         return false;
150     }
151 
152     GTKCreateSearchEntryWidget();
153 
154     if ( HasFlag(wxBORDER_NONE) )
155     {
156         g_object_set (m_widget, "has-frame", FALSE, NULL);
157     }
158 
159     GtkEntry * const entry = GetEntry();
160 
161     // Theoretically m_entry cannot be null, and the test here
162     // is just for safety reasons.
163     if ( !entry )
164         return false;
165 
166     // Set it up to trigger default item on enter key press
167     gtk_entry_set_activates_default(entry, !HasFlag(wxTE_PROCESS_ENTER));
168 
169     gtk_editable_set_editable(GTK_EDITABLE(entry), true);
170 #ifdef __WXGTK3__
171     gtk_entry_set_width_chars(entry, 1);
172 #endif
173 
174     m_parent->DoAddChild(this);
175 
176     m_focusWidget = GTK_WIDGET(entry);
177 
178     PostCreation(size);
179 
180     gtk_entry_set_text(entry, wxGTK_CONV(value));
181 
182     SetHint(_("Search"));
183 
184     GTKConnectChangedSignal();
185     GTKConnectInsertTextSignal(entry);
186     GTKConnectClipboardSignals(GTK_WIDGET(entry));
187 
188     return true;
189 }
190 
GTKCreateSearchEntryWidget()191 void wxSearchCtrl::GTKCreateSearchEntryWidget()
192 {
193     m_widget = CreateGtkSearchEntryIfAvailable();
194 
195     g_object_ref(m_widget);
196 
197     m_entry = GTK_ENTRY(m_widget);
198 
199     if ( !HasGtkSearchEntry() )
200     {
201         // Add the search icon and make it looks as native as one would expect
202         // (i.e. GtkSearchEntry).
203         gtk_entry_set_icon_from_icon_name(m_entry,
204                                           GTK_ENTRY_ICON_PRIMARY,
205                                           "edit-find-symbolic");
206 
207         gtk_entry_set_icon_sensitive(m_entry, GTK_ENTRY_ICON_PRIMARY, FALSE);
208         gtk_entry_set_icon_activatable(m_entry, GTK_ENTRY_ICON_PRIMARY, FALSE);
209     }
210 
211     g_signal_connect(m_entry, "icon-press", G_CALLBACK(wx_gtk_icon_press), this);
212 }
213 
GetEditable() const214 GtkEditable *wxSearchCtrl::GetEditable() const
215 {
216     return GTK_EDITABLE(m_entry);
217 }
218 
Clear()219 void wxSearchCtrl::Clear()
220 {
221     wxTextEntry::Clear();
222     ShowCancelButton(false);
223 }
224 
225 // search control specific interfaces
226 // ----------------------------------
227 #if wxUSE_MENUS
228 
SetMenu(wxMenu * menu)229 void wxSearchCtrl::SetMenu( wxMenu* menu )
230 {
231     if ( menu == m_menu )
232     {
233         // no change
234         return;
235     }
236 
237     delete m_menu;
238     m_menu = menu;
239 
240     const bool hasMenu = m_menu != NULL;
241 
242     gtk_entry_set_icon_sensitive(m_entry, GTK_ENTRY_ICON_PRIMARY, hasMenu);
243     gtk_entry_set_icon_activatable(m_entry, GTK_ENTRY_ICON_PRIMARY, hasMenu);
244 }
245 
GetMenu()246 wxMenu* wxSearchCtrl::GetMenu()
247 {
248     return m_menu;
249 }
250 
251 #endif // wxUSE_MENUS
252 
ShowSearchButton(bool WXUNUSED (show))253 void wxSearchCtrl::ShowSearchButton(bool WXUNUSED(show))
254 {
255     // Search button is always shown in the native control.
256 }
257 
IsSearchButtonVisible() const258 bool wxSearchCtrl::IsSearchButtonVisible() const
259 {
260     // Search button is always shown in the native control.
261     return true;
262 }
263 
ShowCancelButton(bool show)264 void wxSearchCtrl::ShowCancelButton(bool show)
265 {
266     // The cancel button is shown/hidden automatically by the GtkSearchEntry.
267     if ( HasGtkSearchEntry() )
268         return;
269 
270     if ( show == IsCancelButtonVisible() )
271     {
272         // no change
273         return;
274     }
275 
276     gtk_entry_set_icon_from_icon_name(m_entry,
277                                       GTK_ENTRY_ICON_SECONDARY,
278                                       show ? "edit-clear-symbolic" : NULL);
279 
280     m_cancelButtonVisible = show;
281 }
282 
IsCancelButtonVisible() const283 bool wxSearchCtrl::IsCancelButtonVisible() const
284 {
285     if ( HasGtkSearchEntry() )
286     {
287         return !IsEmpty();
288     }
289 
290     return m_cancelButtonVisible;
291 }
292 
SetDescriptiveText(const wxString & text)293 void wxSearchCtrl::SetDescriptiveText(const wxString& text)
294 {
295     wxTextEntry::SetHint(text);
296 }
297 
GetDescriptiveText() const298 wxString wxSearchCtrl::GetDescriptiveText() const
299 {
300     return wxTextEntry::GetHint();
301 }
302 
303 // Events
304 // ----------
305 
OnChar(wxKeyEvent & key_event)306 void wxSearchCtrl::OnChar(wxKeyEvent& key_event)
307 {
308     wxCHECK_RET( m_entry != NULL, "invalid search ctrl" );
309 
310     if ( key_event.GetKeyCode() == WXK_RETURN )
311     {
312         if ( HasFlag(wxTE_PROCESS_ENTER) )
313         {
314             wxCommandEvent event(wxEVT_TEXT_ENTER, m_windowId);
315             event.SetEventObject(this);
316             event.SetString(GetValue());
317             if ( HandleWindowEvent(event) )
318                 return;
319 
320             // We disable built-in default button activation when
321             // wxTE_PROCESS_ENTER is used, but we still should activate it
322             // if the event wasn't handled, so do it from here.
323             if ( ClickDefaultButtonIfPossible() )
324                 return;
325         }
326     }
327 
328     key_event.Skip();
329 }
330 
OnText(wxCommandEvent & event)331 void wxSearchCtrl::OnText(wxCommandEvent& event)
332 {
333     ShowCancelButton(!IsEmpty());
334     event.Skip();
335 }
336 
OnTextEnter(wxCommandEvent & WXUNUSED (event))337 void wxSearchCtrl::OnTextEnter(wxCommandEvent& WXUNUSED(event))
338 {
339     if ( !IsEmpty() )
340     {
341         wxCommandEvent evt(wxEVT_SEARCH, GetId());
342         evt.SetEventObject(this);
343         evt.SetString(GetValue());
344 
345         ProcessWindowEvent(evt);
346     }
347 }
348 
349 #if wxUSE_MENUS
350 
PopupSearchMenu()351 void wxSearchCtrl::PopupSearchMenu()
352 {
353     if ( m_menu )
354     {
355         const wxSize size = GetSize();
356         PopupMenu(m_menu, 0, size.y);
357     }
358 }
359 
360 #endif // wxUSE_MENUS
361 
362 #endif // wxUSE_SEARCHCTRL
363