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