1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/textentry.cpp
3 // Purpose:     wxTextEntry implementation for wxGTK
4 // Author:      Vadim Zeitlin
5 // Created:     2007-09-24
6 // Copyright:   (c) 2007 Vadim Zeitlin <vadim@wxwindows.org>
7 // Licence:     wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
9 
10 // ============================================================================
11 // declarations
12 // ============================================================================
13 
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17 
18 // for compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20 
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24 
25 #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX
26 
27 #ifndef WX_PRECOMP
28     #include "wx/textentry.h"
29     #include "wx/window.h"
30     #include "wx/textctrl.h"
31 #endif //WX_PRECOMP
32 
33 #include <gtk/gtk.h>
34 #include "wx/gtk/private.h"
35 #include "wx/gtk/private/gtk2-compat.h"
36 
37 // ============================================================================
38 // signal handlers implementation
39 // ============================================================================
40 
41 // "insert_text" handler for GtkEntry
42 extern "C"
43 void
wx_gtk_insert_text_callback(GtkEditable * editable,const gchar * new_text,gint WXUNUSED (new_text_length),gint * WXUNUSED (position),wxTextEntry * text)44 wx_gtk_insert_text_callback(GtkEditable *editable,
45                             const gchar * new_text,
46                             gint WXUNUSED(new_text_length),
47                             gint * WXUNUSED(position),
48                             wxTextEntry *text)
49 {
50     GtkEntry *entry = GTK_ENTRY (editable);
51 
52 #if GTK_CHECK_VERSION(3,0,0) || defined(GSEAL_ENABLE)
53     const int text_max_length = gtk_entry_buffer_get_max_length(gtk_entry_get_buffer(entry));
54 #else
55     const int text_max_length = entry->text_max_length;
56 #endif
57 
58     bool handled = false;
59 
60     // check that we don't overflow the max length limit if we have it
61     if ( text_max_length )
62     {
63         const int text_length = gtk_entry_get_text_length(entry);
64 
65         // We can't use new_text_length as it is in bytes while we want to count
66         // characters (in first approximation, anyhow...).
67         if ( text_length + g_utf8_strlen(new_text, -1) > text_max_length )
68         {
69             // Prevent the new text from being inserted.
70             handled = true;
71 
72             // Currently we don't insert anything at all, but it would be better to
73             // insert as many characters as would fit into the text control and
74             // only discard the rest.
75 
76             // Notify the user code about overflow.
77             text->SendMaxLenEvent();
78         }
79     }
80 
81     if ( !handled && text->GTKEntryOnInsertText(new_text) )
82     {
83         // If we already handled the new text insertion, don't do it again.
84         handled = true;
85     }
86 
87     if ( handled )
88         g_signal_stop_emission_by_name (editable, "insert_text");
89 }
90 
91 //-----------------------------------------------------------------------------
92 //  clipboard events: "copy-clipboard", "cut-clipboard", "paste-clipboard"
93 //-----------------------------------------------------------------------------
94 
95 // common part of the event handlers below
96 static void
DoHandleClipboardCallback(GtkWidget * widget,wxWindow * win,wxEventType eventType,const gchar * signal_name)97 DoHandleClipboardCallback( GtkWidget *widget,
98                            wxWindow *win,
99                            wxEventType eventType,
100                            const gchar* signal_name)
101 {
102     wxClipboardTextEvent event( eventType, win->GetId() );
103     event.SetEventObject( win );
104     if ( win->HandleWindowEvent( event ) )
105     {
106         // don't let the default processing to take place if we did something
107         // ourselves in the event handler
108         g_signal_stop_emission_by_name (widget, signal_name);
109     }
110 }
111 
112 extern "C"
113 {
114 
115 static void
wx_gtk_copy_clipboard_callback(GtkWidget * widget,wxWindow * win)116 wx_gtk_copy_clipboard_callback( GtkWidget *widget, wxWindow *win )
117 {
118     DoHandleClipboardCallback(
119         widget, win, wxEVT_TEXT_COPY, "copy-clipboard" );
120 }
121 
122 static void
wx_gtk_cut_clipboard_callback(GtkWidget * widget,wxWindow * win)123 wx_gtk_cut_clipboard_callback( GtkWidget *widget, wxWindow *win )
124 {
125     DoHandleClipboardCallback(
126         widget, win, wxEVT_TEXT_CUT, "cut-clipboard" );
127 }
128 
129 static void
wx_gtk_paste_clipboard_callback(GtkWidget * widget,wxWindow * win)130 wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win )
131 {
132     DoHandleClipboardCallback(
133         widget, win, wxEVT_TEXT_PASTE, "paste-clipboard" );
134 }
135 
136 } // extern "C"
137 
138 // ============================================================================
139 // wxTextEntry implementation
140 // ============================================================================
141 
142 // ----------------------------------------------------------------------------
143 // text operations
144 // ----------------------------------------------------------------------------
145 
WriteText(const wxString & value)146 void wxTextEntry::WriteText(const wxString& value)
147 {
148     GtkEditable * const edit = GetEditable();
149 
150     // remove the selection if there is one and suppress the text change event
151     // generated by this: we only want to generate one event for this change,
152     // not two
153     {
154         EventsSuppressor noevents(this);
155         gtk_editable_delete_selection(edit);
156     }
157 
158     // insert new text at the cursor position
159     gint len = gtk_editable_get_position(edit);
160     gtk_editable_insert_text
161     (
162         edit,
163         wxGTK_CONV_FONT(value, GetEditableWindow()->GetFont()),
164         -1,     // text: length: compute it using strlen()
165         &len    // will be updated to position after the text end
166     );
167 
168     // and move cursor to the end of new text
169     gtk_editable_set_position(edit, len);
170 }
171 
DoSetValue(const wxString & value,int flags)172 void wxTextEntry::DoSetValue(const wxString& value, int flags)
173 {
174     if (value != DoGetValue())
175     {
176         // use Remove() rather than SelectAll() to avoid unnecessary clipboard
177         // operations, and prevent triggering an apparent bug in GTK which
178         // causes the the subsequent WriteText() to append rather than overwrite
179         {
180             EventsSuppressor noevents(this);
181             Remove(0, -1);
182         }
183         EventsSuppressor noeventsIf(this, !(flags & SetValue_SendEvent));
184         WriteText(value);
185     }
186     else if (flags & SetValue_SendEvent)
187         SendTextUpdatedEvent(GetEditableWindow());
188 
189     SetInsertionPoint(0);
190 }
191 
DoGetValue() const192 wxString wxTextEntry::DoGetValue() const
193 {
194     const wxGtkString value(gtk_editable_get_chars(GetEditable(), 0, -1));
195 
196     return wxGTK_CONV_BACK_FONT(value,
197             const_cast<wxTextEntry *>(this)->GetEditableWindow()->GetFont());
198 }
199 
Remove(long from,long to)200 void wxTextEntry::Remove(long from, long to)
201 {
202     gtk_editable_delete_text(GetEditable(), from, to);
203 }
204 
205 // ----------------------------------------------------------------------------
206 // clipboard operations
207 // ----------------------------------------------------------------------------
208 
GTKConnectClipboardSignals(GtkWidget * entry)209 void wxTextEntry::GTKConnectClipboardSignals(GtkWidget* entry)
210 {
211     g_signal_connect(entry, "copy-clipboard",
212                      G_CALLBACK (wx_gtk_copy_clipboard_callback),
213                      GetEditableWindow());
214     g_signal_connect(entry, "cut-clipboard",
215                      G_CALLBACK (wx_gtk_cut_clipboard_callback),
216                      GetEditableWindow());
217     g_signal_connect(entry, "paste-clipboard",
218                      G_CALLBACK (wx_gtk_paste_clipboard_callback),
219                      GetEditableWindow());
220 }
221 
Copy()222 void wxTextEntry::Copy()
223 {
224     gtk_editable_copy_clipboard(GetEditable());
225 }
226 
Cut()227 void wxTextEntry::Cut()
228 {
229     gtk_editable_cut_clipboard(GetEditable());
230 }
231 
Paste()232 void wxTextEntry::Paste()
233 {
234     gtk_editable_paste_clipboard(GetEditable());
235 }
236 
237 // ----------------------------------------------------------------------------
238 // undo/redo
239 // ----------------------------------------------------------------------------
240 
Undo()241 void wxTextEntry::Undo()
242 {
243     // TODO: not implemented
244 }
245 
Redo()246 void wxTextEntry::Redo()
247 {
248     // TODO: not implemented
249 }
250 
CanUndo() const251 bool wxTextEntry::CanUndo() const
252 {
253     return false;
254 }
255 
CanRedo() const256 bool wxTextEntry::CanRedo() const
257 {
258     return false;
259 }
260 
261 // ----------------------------------------------------------------------------
262 // insertion point
263 // ----------------------------------------------------------------------------
264 
SetInsertionPoint(long pos)265 void wxTextEntry::SetInsertionPoint(long pos)
266 {
267     gtk_editable_set_position(GetEditable(), pos);
268 }
269 
GetInsertionPoint() const270 long wxTextEntry::GetInsertionPoint() const
271 {
272     return gtk_editable_get_position(GetEditable());
273 }
274 
GetLastPosition() const275 long wxTextEntry::GetLastPosition() const
276 {
277     // this can't be implemented for arbitrary GtkEditable so only do it for
278     // GtkEntries
279     long pos = -1;
280     GtkEntry* entry = (GtkEntry*)GetEditable();
281     if (GTK_IS_ENTRY(entry))
282         pos = gtk_entry_get_text_length(entry);
283 
284     return pos;
285 }
286 
287 // ----------------------------------------------------------------------------
288 // selection
289 // ----------------------------------------------------------------------------
290 
SetSelection(long from,long to)291 void wxTextEntry::SetSelection(long from, long to)
292 {
293     // in wx convention, (-1, -1) means the entire range but GTK+ translates -1
294     // (or any negative number for that matter) into last position so we need
295     // to translate manually
296     if ( from == -1 && to == -1 )
297         from = 0;
298 
299     // for compatibility with MSW, exchange from and to parameters so that the
300     // insertion point is set to the start of the selection and not its end as
301     // GTK+ does by default
302     gtk_editable_select_region(GetEditable(), to, from);
303 
304 #ifndef __WXGTK3__
305     // avoid reported problem with RHEL 5 GTK+ 2.10 where selection is reset by
306     // a clipboard callback, see #13277
307     if (gtk_check_version(2,12,0))
308     {
309         GtkEntry* entry = GTK_ENTRY(GetEditable());
310         if (to < 0)
311             to = entry->text_length;
312         entry->selection_bound = to;
313     }
314 #endif
315 }
316 
GetSelection(long * from,long * to) const317 void wxTextEntry::GetSelection(long *from, long *to) const
318 {
319     gint start, end;
320     if ( gtk_editable_get_selection_bounds(GetEditable(), &start, &end) )
321     {
322         // the output must always be in order, although in GTK+ it isn't
323         if ( start > end )
324         {
325             gint tmp = start;
326             start = end;
327             end = tmp;
328         }
329     }
330     else // no selection
331     {
332         // for compatibility with MSW return the empty selection at cursor
333         start =
334         end = GetInsertionPoint();
335     }
336 
337     if ( from )
338         *from = start;
339 
340     if ( to )
341         *to = end;
342 }
343 
344 // ----------------------------------------------------------------------------
345 // auto completion
346 // ----------------------------------------------------------------------------
347 
DoAutoCompleteStrings(const wxArrayString & choices)348 bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
349 {
350     GtkEntry* const entry = (GtkEntry*)GetEditable();
351     wxCHECK_MSG(GTK_IS_ENTRY(entry), false, "auto completion doesn't work with this control");
352 
353     GtkListStore * const store = gtk_list_store_new(1, G_TYPE_STRING);
354     GtkTreeIter iter;
355 
356     for ( wxArrayString::const_iterator i = choices.begin();
357           i != choices.end();
358           ++i )
359     {
360         gtk_list_store_append(store, &iter);
361         gtk_list_store_set(store, &iter,
362                            0, (const gchar *)i->utf8_str(),
363                            -1);
364     }
365 
366     GtkEntryCompletion * const completion = gtk_entry_completion_new();
367     gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
368     gtk_entry_completion_set_text_column(completion, 0);
369     gtk_entry_set_completion(entry, completion);
370     g_object_unref(completion);
371     return true;
372 }
373 
374 // ----------------------------------------------------------------------------
375 // editable status
376 // ----------------------------------------------------------------------------
377 
IsEditable() const378 bool wxTextEntry::IsEditable() const
379 {
380     return gtk_editable_get_editable(GetEditable()) != 0;
381 }
382 
SetEditable(bool editable)383 void wxTextEntry::SetEditable(bool editable)
384 {
385     gtk_editable_set_editable(GetEditable(), editable);
386 }
387 
388 // ----------------------------------------------------------------------------
389 // max text length
390 // ----------------------------------------------------------------------------
391 
SetMaxLength(unsigned long len)392 void wxTextEntry::SetMaxLength(unsigned long len)
393 {
394     GtkEntry* const entry = (GtkEntry*)GetEditable();
395     if (!GTK_IS_ENTRY(entry))
396         return;
397 
398     gtk_entry_set_max_length(entry, len);
399 }
400 
SendMaxLenEvent()401 void wxTextEntry::SendMaxLenEvent()
402 {
403     // remember that the next changed signal is to be ignored to avoid
404     // generating a dummy wxEVT_TEXT event
405     //IgnoreNextTextUpdate();
406 
407     wxWindow * const win = GetEditableWindow();
408     wxCommandEvent event(wxEVT_TEXT_MAXLEN, win->GetId());
409     event.SetEventObject(win);
410     event.SetString(GetValue());
411     win->HandleWindowEvent(event);
412 }
413 
414 // ----------------------------------------------------------------------------
415 // IM handling
416 // ----------------------------------------------------------------------------
417 
GTKIMFilterKeypress(GdkEventKey * event) const418 int wxTextEntry::GTKIMFilterKeypress(GdkEventKey* event) const
419 {
420     int result;
421 #if GTK_CHECK_VERSION(2, 22, 0)
422 #ifndef __WXGTK3__
423     result = false;
424     if (gtk_check_version(2,22,0) == NULL)
425 #endif
426     {
427         result = gtk_entry_im_context_filter_keypress(GetEntry(), event);
428     }
429 #else // GTK+ < 2.22
430     wxUnusedVar(event);
431     result = false;
432 #endif // GTK+ 2.22+
433 
434     return result;
435 }
436 
GTKConnectInsertTextSignal(GtkEntry * entry)437 void wxTextEntry::GTKConnectInsertTextSignal(GtkEntry* entry)
438 {
439     g_signal_connect(entry, "insert_text",
440                      G_CALLBACK(wx_gtk_insert_text_callback), this);
441 }
442 
GTKEntryOnInsertText(const char * text)443 bool wxTextEntry::GTKEntryOnInsertText(const char* text)
444 {
445     return GetEditableWindow()->GTKOnInsertText(text);
446 }
447 
448 // ----------------------------------------------------------------------------
449 // margins support
450 // ----------------------------------------------------------------------------
451 
DoSetMargins(const wxPoint & margins)452 bool wxTextEntry::DoSetMargins(const wxPoint& margins)
453 {
454 #if GTK_CHECK_VERSION(2,10,0)
455     GtkEntry* entry = GetEntry();
456 
457     if ( !entry )
458         return false;
459 #ifndef __WXGTK3__
460     if (gtk_check_version(2,10,0))
461         return false;
462 #endif
463 
464     const GtkBorder* oldBorder = gtk_entry_get_inner_border(entry);
465     GtkBorder newBorder;
466 
467     if ( oldBorder )
468         newBorder = *oldBorder;
469     else
470     {
471         // Use some reasonable defaults for initial margins
472         newBorder.left = 2;
473         newBorder.right = 2;
474 
475         // These numbers seem to let the text remain vertically centered
476         // in common use scenarios when margins.y == -1.
477         newBorder.top = 3;
478         newBorder.bottom = 3;
479     }
480 
481     if ( margins.x != -1 )
482         newBorder.left = margins.x;
483 
484     if ( margins.y != -1 )
485         newBorder.top = margins.y;
486 
487     gtk_entry_set_inner_border(entry, &newBorder);
488 
489     return true;
490 #else
491     wxUnusedVar(margins);
492     return false;
493 #endif
494 }
495 
DoGetMargins() const496 wxPoint wxTextEntry::DoGetMargins() const
497 {
498     wxPoint point(-1, -1);
499 #if GTK_CHECK_VERSION(2,10,0)
500     GtkEntry* entry = GetEntry();
501     if (entry)
502     {
503 #ifndef __WXGTK3__
504         if (gtk_check_version(2,10,0) == NULL)
505 #endif
506         {
507             const GtkBorder* border = gtk_entry_get_inner_border(entry);
508             if (border)
509             {
510                 point.x = border->left;
511                 point.y = border->top;
512             }
513         }
514     }
515 #endif
516     return point;
517 }
518 
519 #endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX
520