1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/textctrl.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Copyright:   (c) 1998 Robert Roebling, Vadim Zeitlin, 2005 Mart Raudsepp
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 #if wxUSE_TEXTCTRL
13 
14 #include "wx/textctrl.h"
15 
16 #ifndef WX_PRECOMP
17     #include "wx/intl.h"
18     #include "wx/log.h"
19     #include "wx/utils.h"
20     #include "wx/settings.h"
21     #include "wx/math.h"
22 #endif
23 
24 #include "wx/scopeguard.h"
25 #include "wx/strconv.h"
26 #include "wx/fontutil.h"        // for wxNativeFontInfo (GetNativeFontInfo())
27 
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <ctype.h>
31 
32 #include <gtk/gtk.h>
33 #include "wx/gtk/private.h"
34 #include "wx/gtk/private/gtk2-compat.h"
35 
36 // ----------------------------------------------------------------------------
37 // helpers
38 // ----------------------------------------------------------------------------
39 
40 extern "C" {
wxGtkOnRemoveTag(GtkTextBuffer * buffer,GtkTextTag * tag,GtkTextIter * WXUNUSED (start),GtkTextIter * WXUNUSED (end),char * prefix)41 static void wxGtkOnRemoveTag(GtkTextBuffer *buffer,
42                              GtkTextTag *tag,
43                              GtkTextIter * WXUNUSED(start),
44                              GtkTextIter * WXUNUSED(end),
45                              char *prefix)
46 {
47     gchar *name;
48     g_object_get (tag, "name", &name, NULL);
49 
50     if (!name || strncmp(name, prefix, strlen(prefix)))
51         // anonymous tag or not starting with prefix - don't remove
52         g_signal_stop_emission_by_name (buffer, "remove_tag");
53 
54     g_free(name);
55 }
56 }
57 
58 // remove all tags starting with the given prefix from the start..end range
59 static void
wxGtkTextRemoveTagsWithPrefix(GtkTextBuffer * text_buffer,const char * prefix,GtkTextIter * start,GtkTextIter * end)60 wxGtkTextRemoveTagsWithPrefix(GtkTextBuffer *text_buffer,
61                               const char *prefix,
62                               GtkTextIter *start,
63                               GtkTextIter *end)
64 {
65     gulong remove_handler_id = g_signal_connect
66                                (
67                                 text_buffer,
68                                 "remove_tag",
69                                 G_CALLBACK(wxGtkOnRemoveTag),
70                                 gpointer(prefix)
71                                );
72     gtk_text_buffer_remove_all_tags(text_buffer, start, end);
73     g_signal_handler_disconnect(text_buffer, remove_handler_id);
74 }
75 
wxGtkTextApplyTagsFromAttr(GtkWidget * text,GtkTextBuffer * text_buffer,const wxTextAttr & attr,GtkTextIter * start,GtkTextIter * end)76 static void wxGtkTextApplyTagsFromAttr(GtkWidget *text,
77                                        GtkTextBuffer *text_buffer,
78                                        const wxTextAttr& attr,
79                                        GtkTextIter *start,
80                                        GtkTextIter *end)
81 {
82     static gchar buf[1024];
83     GtkTextTag *tag;
84 
85     if (attr.HasFont())
86     {
87         wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXFONT", start, end);
88 
89         wxFont font(attr.GetFont());
90 
91         PangoFontDescription *font_description = font.GetNativeFontInfo()->description;
92         wxGtkString font_string(pango_font_description_to_string(font_description));
93         g_snprintf(buf, sizeof(buf), "WXFONT %s", font_string.c_str());
94         tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
95                                          buf );
96         if (!tag)
97             tag = gtk_text_buffer_create_tag( text_buffer, buf,
98                                               "font-desc", font_description,
99                                               NULL );
100         gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
101 
102         if (font.GetUnderlined())
103         {
104             g_snprintf(buf, sizeof(buf), "WXFONTUNDERLINE");
105             tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
106                                              buf );
107             if (!tag)
108                 tag = gtk_text_buffer_create_tag( text_buffer, buf,
109                                                   "underline-set", TRUE,
110                                                   "underline", PANGO_UNDERLINE_SINGLE,
111                                                   NULL );
112             gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
113         }
114         if ( font.GetStrikethrough() )
115         {
116             g_snprintf(buf, sizeof(buf), "WXFONTSTRIKETHROUGH");
117             tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
118                                              buf );
119             if (!tag)
120                 tag = gtk_text_buffer_create_tag( text_buffer, buf,
121                                                   "strikethrough-set", TRUE,
122                                                   "strikethrough", TRUE,
123                                                   NULL );
124             gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
125         }
126     }
127 
128     if (attr.HasTextColour())
129     {
130         wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXFORECOLOR", start, end);
131 
132         const GdkColor *colFg = attr.GetTextColour().GetColor();
133         g_snprintf(buf, sizeof(buf), "WXFORECOLOR %d %d %d",
134                    colFg->red, colFg->green, colFg->blue);
135         tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
136                                          buf );
137         if (!tag)
138             tag = gtk_text_buffer_create_tag( text_buffer, buf,
139                                               "foreground-gdk", colFg, NULL );
140         gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
141     }
142 
143     if (attr.HasBackgroundColour())
144     {
145         wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXBACKCOLOR", start, end);
146 
147         const GdkColor *colBg = attr.GetBackgroundColour().GetColor();
148         g_snprintf(buf, sizeof(buf), "WXBACKCOLOR %d %d %d",
149                    colBg->red, colBg->green, colBg->blue);
150         tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
151                                          buf );
152         if (!tag)
153             tag = gtk_text_buffer_create_tag( text_buffer, buf,
154                                               "background-gdk", colBg, NULL );
155         gtk_text_buffer_apply_tag (text_buffer, tag, start, end);
156     }
157 
158     if (attr.HasAlignment())
159     {
160         GtkTextIter para_start, para_end = *end;
161         gtk_text_buffer_get_iter_at_line( text_buffer,
162                                           &para_start,
163                                           gtk_text_iter_get_line(start) );
164         gtk_text_iter_forward_line(&para_end);
165 
166         wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXALIGNMENT", &para_start, &para_end);
167 
168         GtkJustification align;
169         switch (attr.GetAlignment())
170         {
171             case wxTEXT_ALIGNMENT_RIGHT:
172                 align = GTK_JUSTIFY_RIGHT;
173                 break;
174             case wxTEXT_ALIGNMENT_CENTER:
175                 align = GTK_JUSTIFY_CENTER;
176                 break;
177             case wxTEXT_ALIGNMENT_JUSTIFIED:
178 #ifdef __WXGTK3__
179                 align = GTK_JUSTIFY_FILL;
180                 break;
181 #elif GTK_CHECK_VERSION(2,11,0)
182 // gtk+ doesn't support justify before gtk+-2.11.0 with pango-1.17 being available
183 // (but if new enough pango isn't available it's a mere gtk warning)
184                 if (!gtk_check_version(2,11,0))
185                 {
186                     align = GTK_JUSTIFY_FILL;
187                     break;
188                 }
189                 // fallthrough
190 #endif
191             default:
192                 align = GTK_JUSTIFY_LEFT;
193                 break;
194         }
195 
196         g_snprintf(buf, sizeof(buf), "WXALIGNMENT %d", align);
197         tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
198                                          buf );
199         if (!tag)
200             tag = gtk_text_buffer_create_tag( text_buffer, buf,
201                                               "justification", align, NULL );
202         gtk_text_buffer_apply_tag( text_buffer, tag, &para_start, &para_end );
203     }
204 
205     if (attr.HasLeftIndent())
206     {
207         // Indentation attribute
208 
209         // Clear old indentation tags
210         GtkTextIter para_start, para_end = *end;
211         gtk_text_buffer_get_iter_at_line( text_buffer,
212                                           &para_start,
213                                           gtk_text_iter_get_line(start) );
214         gtk_text_iter_forward_line(&para_end);
215 
216         wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXINDENT", &para_start, &para_end);
217 
218         // Convert indent from 1/10th of a mm into pixels
219         float factor =
220             (float)gdk_screen_get_width(gtk_widget_get_screen(text)) /
221                       gdk_screen_get_width_mm(gtk_widget_get_screen(text)) / 10;
222 
223         const int indent = (int)(factor * attr.GetLeftIndent());
224         const int subIndent = (int)(factor * attr.GetLeftSubIndent());
225 
226         gint gindent;
227         gint gsubindent;
228 
229         if (subIndent >= 0)
230         {
231             gindent = indent;
232             gsubindent = -subIndent;
233         }
234         else
235         {
236             gindent = -subIndent;
237             gsubindent = indent;
238         }
239 
240         g_snprintf(buf, sizeof(buf), "WXINDENT %d %d", gindent, gsubindent);
241         tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
242                                         buf );
243         if (!tag)
244             tag = gtk_text_buffer_create_tag( text_buffer, buf,
245                                               "left-margin", gindent, "indent", gsubindent, NULL );
246         gtk_text_buffer_apply_tag (text_buffer, tag, &para_start, &para_end);
247     }
248 
249     if (attr.HasTabs())
250     {
251         // Set tab stops
252 
253         // Clear old tabs
254         GtkTextIter para_start, para_end = *end;
255         gtk_text_buffer_get_iter_at_line( text_buffer,
256                                           &para_start,
257                                           gtk_text_iter_get_line(start) );
258         gtk_text_iter_forward_line(&para_end);
259 
260         wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXTABS", &para_start, &para_end);
261 
262         const wxArrayInt& tabs = attr.GetTabs();
263 
264         wxString tagname = wxT("WXTABS");
265         g_snprintf(buf, sizeof(buf), "WXTABS");
266         for (size_t i = 0; i < tabs.GetCount(); i++)
267             tagname += wxString::Format(wxT(" %d"), tabs[i]);
268 
269         const wxWX2MBbuf buftag = tagname.utf8_str();
270 
271         tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ),
272                                         buftag );
273         if (!tag)
274         {
275             // Factor to convert from 1/10th of a mm into pixels
276             float factor =
277                 (float)gdk_screen_get_width(gtk_widget_get_screen(text)) /
278                           gdk_screen_get_width_mm(gtk_widget_get_screen(text)) / 10;
279 
280             PangoTabArray* tabArray = pango_tab_array_new(tabs.GetCount(), TRUE);
281             for (size_t i = 0; i < tabs.GetCount(); i++)
282                 pango_tab_array_set_tab(tabArray, i, PANGO_TAB_LEFT, (gint)(tabs[i] * factor));
283             tag = gtk_text_buffer_create_tag( text_buffer, buftag,
284                                               "tabs", tabArray, NULL );
285             pango_tab_array_free(tabArray);
286         }
287         gtk_text_buffer_apply_tag (text_buffer, tag, &para_start, &para_end);
288     }
289 }
290 
wxGtkTextInsert(GtkWidget * text,GtkTextBuffer * text_buffer,const wxTextAttr & attr,const wxCharBuffer & buffer)291 static void wxGtkTextInsert(GtkWidget *text,
292                             GtkTextBuffer *text_buffer,
293                             const wxTextAttr& attr,
294                             const wxCharBuffer& buffer)
295 
296 {
297     gint start_offset;
298     GtkTextIter iter, start;
299 
300     gtk_text_buffer_get_iter_at_mark( text_buffer, &iter,
301                                      gtk_text_buffer_get_insert (text_buffer) );
302     start_offset = gtk_text_iter_get_offset (&iter);
303     gtk_text_buffer_insert( text_buffer, &iter, buffer, strlen(buffer) );
304 
305     gtk_text_buffer_get_iter_at_offset (text_buffer, &start, start_offset);
306 
307     wxGtkTextApplyTagsFromAttr(text, text_buffer, attr, &start, &iter);
308 }
309 
310 // Implementation of wxTE_AUTO_URL for wxGTK2 by Mart Raudsepp,
311 
312 extern "C" {
313 static void
au_apply_tag_callback(GtkTextBuffer * buffer,GtkTextTag * tag,GtkTextIter * WXUNUSED (start),GtkTextIter * WXUNUSED (end),gpointer WXUNUSED (textctrl))314 au_apply_tag_callback(GtkTextBuffer *buffer,
315                       GtkTextTag *tag,
316                       GtkTextIter * WXUNUSED(start),
317                       GtkTextIter * WXUNUSED(end),
318                       gpointer WXUNUSED(textctrl))
319 {
320     if(tag == gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl"))
321         g_signal_stop_emission_by_name (buffer, "apply_tag");
322 }
323 }
324 
325 //-----------------------------------------------------------------------------
326 //  GtkTextCharPredicates for gtk_text_iter_*_find_char
327 //-----------------------------------------------------------------------------
328 
329 extern "C" {
330 static gboolean
pred_whitespace(gunichar ch,gpointer WXUNUSED (user_data))331 pred_whitespace(gunichar ch, gpointer WXUNUSED(user_data))
332 {
333     return g_unichar_isspace(ch);
334 }
335 }
336 
337 extern "C" {
338 static gboolean
pred_non_whitespace(gunichar ch,gpointer WXUNUSED (user_data))339 pred_non_whitespace (gunichar ch, gpointer WXUNUSED(user_data))
340 {
341     return !g_unichar_isspace(ch);
342 }
343 }
344 
345 extern "C" {
346 static gboolean
pred_nonpunct(gunichar ch,gpointer WXUNUSED (user_data))347 pred_nonpunct (gunichar ch, gpointer WXUNUSED(user_data))
348 {
349     return !g_unichar_ispunct(ch);
350 }
351 }
352 
353 extern "C" {
354 static gboolean
pred_nonpunct_or_slash(gunichar ch,gpointer WXUNUSED (user_data))355 pred_nonpunct_or_slash (gunichar ch, gpointer WXUNUSED(user_data))
356 {
357     return !g_unichar_ispunct(ch) || ch == '/';
358 }
359 }
360 
361 //-----------------------------------------------------------------------------
362 //  Check for links between s and e and correct tags as necessary
363 //-----------------------------------------------------------------------------
364 
365 // This function should be made match better while being efficient at one point.
366 // Most probably with a row of regular expressions.
367 extern "C" {
368 static void
au_check_word(GtkTextIter * s,GtkTextIter * e)369 au_check_word( GtkTextIter *s, GtkTextIter *e )
370 {
371     static const char *const URIPrefixes[] =
372     {
373         "http://",
374         "ftp://",
375         "www.",
376         "ftp.",
377         "mailto://",
378         "https://",
379         "file://",
380         "nntp://",
381         "news://",
382         "telnet://",
383         "mms://",
384         "gopher://",
385         "prospero://",
386         "wais://",
387     };
388 
389     GtkTextIter start = *s, end = *e;
390     GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
391 
392     // Get our special link tag
393     GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
394 
395     // Get rid of punctuation from beginning and end.
396     // Might want to move this to au_check_range if an improved link checking doesn't
397     // use some intelligent punctuation checking itself (beware of undesired iter modifications).
398     if(g_unichar_ispunct( gtk_text_iter_get_char( &start ) ) )
399         gtk_text_iter_forward_find_char( &start, pred_nonpunct, NULL, e );
400 
401     gtk_text_iter_backward_find_char( &end, pred_nonpunct_or_slash, NULL, &start );
402     gtk_text_iter_forward_char(&end);
403 
404     wxGtkString text(gtk_text_iter_get_text( &start, &end ));
405     size_t len = strlen(text), prefix_len;
406     size_t n;
407 
408     for( n = 0; n < WXSIZEOF(URIPrefixes); ++n )
409     {
410         prefix_len = strlen(URIPrefixes[n]);
411         if((len > prefix_len) && !wxStrnicmp(text, URIPrefixes[n], prefix_len))
412             break;
413     }
414 
415     if(n < WXSIZEOF(URIPrefixes))
416     {
417         gulong signal_id = g_signal_handler_find (buffer,
418                                                   (GSignalMatchType) (G_SIGNAL_MATCH_FUNC),
419                                                   0, 0, NULL,
420                                                   (gpointer)au_apply_tag_callback, NULL);
421 
422         g_signal_handler_block (buffer, signal_id);
423         gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
424         g_signal_handler_unblock (buffer, signal_id);
425     }
426 }
427 }
428 
429 extern "C" {
430 static void
au_check_range(GtkTextIter * s,GtkTextIter * range_end)431 au_check_range(GtkTextIter *s,
432                GtkTextIter *range_end)
433 {
434     GtkTextIter range_start = *s;
435     GtkTextIter word_end;
436     GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
437     GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
438 
439     gtk_text_buffer_remove_tag(buffer, tag, s, range_end);
440 
441     if(g_unichar_isspace(gtk_text_iter_get_char(&range_start)))
442         gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
443 
444     while(!gtk_text_iter_equal(&range_start, range_end))
445     {
446         word_end = range_start;
447         gtk_text_iter_forward_find_char(&word_end, pred_whitespace, NULL, range_end);
448 
449         // Now we should have a word delimited by range_start and word_end, correct link tags
450         au_check_word(&range_start, &word_end);
451 
452         range_start = word_end;
453         gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
454     }
455 }
456 }
457 
458 //-----------------------------------------------------------------------------
459 //  "insert-text" for GtkTextBuffer
460 //-----------------------------------------------------------------------------
461 
462 extern "C" {
463 
464 // Normal version used for detecting IME input and generating appropriate
465 // events for it.
466 void
wx_insert_text_callback(GtkTextBuffer * buffer,GtkTextIter * WXUNUSED (end),gchar * text,gint WXUNUSED (len),wxTextCtrl * win)467 wx_insert_text_callback(GtkTextBuffer* buffer,
468                         GtkTextIter* WXUNUSED(end),
469                         gchar *text,
470                         gint WXUNUSED(len),
471                         wxTextCtrl *win)
472 {
473     if ( win->GTKOnInsertText(text) )
474     {
475         // If we already handled the new text insertion, don't do it again.
476         g_signal_stop_emission_by_name (buffer, "insert_text");
477     }
478 }
479 
480 
481 // And an "after" version used for detecting URLs in the text.
482 static void
au_insert_text_callback(GtkTextBuffer * WXUNUSED (buffer),GtkTextIter * end,gchar * text,gint len,wxTextCtrl * win)483 au_insert_text_callback(GtkTextBuffer * WXUNUSED(buffer),
484                         GtkTextIter *end,
485                         gchar *text,
486                         gint len,
487                         wxTextCtrl *win)
488 {
489     if (!len || !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
490         return;
491 
492     GtkTextIter start = *end;
493     gtk_text_iter_backward_chars(&start, g_utf8_strlen(text, len));
494 
495     GtkTextIter line_start = start;
496     GtkTextIter line_end = *end;
497     GtkTextIter words_start = start;
498     GtkTextIter words_end = *end;
499 
500     gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(&start));
501     gtk_text_iter_forward_to_line_end(&line_end);
502     gtk_text_iter_backward_find_char(&words_start, pred_whitespace, NULL, &line_start);
503     gtk_text_iter_forward_find_char(&words_end, pred_whitespace, NULL, &line_end);
504 
505     au_check_range(&words_start, &words_end);
506 }
507 }
508 
509 //-----------------------------------------------------------------------------
510 //  "delete-range" for GtkTextBuffer
511 //-----------------------------------------------------------------------------
512 
513 extern "C" {
514 static void
au_delete_range_callback(GtkTextBuffer * WXUNUSED (buffer),GtkTextIter * start,GtkTextIter * end,wxTextCtrl * win)515 au_delete_range_callback(GtkTextBuffer * WXUNUSED(buffer),
516                          GtkTextIter *start,
517                          GtkTextIter *end,
518                          wxTextCtrl *win)
519 {
520     if( !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
521         return;
522 
523     GtkTextIter line_start = *start, line_end = *end;
524 
525     gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(start));
526     gtk_text_iter_forward_to_line_end(&line_end);
527     gtk_text_iter_backward_find_char(start, pred_whitespace, NULL, &line_start);
528     gtk_text_iter_forward_find_char(end, pred_whitespace, NULL, &line_end);
529 
530     au_check_range(start, end);
531 }
532 }
533 
534 //-----------------------------------------------------------------------------
535 //  "populate_popup" from text control and "unmap" from its poup menu
536 //-----------------------------------------------------------------------------
537 
538 extern "C" {
539 static void
gtk_textctrl_popup_unmap(GtkMenu * WXUNUSED (menu),wxTextCtrl * win)540 gtk_textctrl_popup_unmap( GtkMenu *WXUNUSED(menu), wxTextCtrl* win )
541 {
542     win->GTKEnableFocusOutEvent();
543 }
544 }
545 
546 extern "C" {
547 static void
gtk_textctrl_populate_popup(GtkEntry * WXUNUSED (entry),GtkMenu * menu,wxTextCtrl * win)548 gtk_textctrl_populate_popup( GtkEntry *WXUNUSED(entry), GtkMenu *menu, wxTextCtrl *win )
549 {
550     win->GTKDisableFocusOutEvent();
551 
552     g_signal_connect (menu, "unmap", G_CALLBACK (gtk_textctrl_popup_unmap), win );
553 }
554 }
555 
556 //-----------------------------------------------------------------------------
557 //  "changed"
558 //-----------------------------------------------------------------------------
559 
560 extern "C" {
561 static void
gtk_text_changed_callback(GtkWidget * WXUNUSED (widget),wxTextCtrl * win)562 gtk_text_changed_callback( GtkWidget *WXUNUSED(widget), wxTextCtrl *win )
563 {
564     if ( win->IgnoreTextUpdate() )
565         return;
566 
567     if ( win->MarkDirtyOnChange() )
568         win->MarkDirty();
569 
570     win->SendTextUpdatedEvent();
571 }
572 }
573 
574 //-----------------------------------------------------------------------------
575 //  "mark_set"
576 //-----------------------------------------------------------------------------
577 
578 extern "C" {
mark_set(GtkTextBuffer *,GtkTextIter *,GtkTextMark * mark,GSList ** markList)579 static void mark_set(GtkTextBuffer*, GtkTextIter*, GtkTextMark* mark, GSList** markList)
580 {
581     if (gtk_text_mark_get_name(mark) == NULL)
582         *markList = g_slist_prepend(*markList, mark);
583 }
584 }
585 
586 #ifdef __WXGTK3__
587 //-----------------------------------------------------------------------------
588 //  "state_flags_changed"
589 //-----------------------------------------------------------------------------
590 extern "C" {
state_flags_changed(GtkWidget *,GtkStateFlags,wxTextCtrl * win)591 static void state_flags_changed(GtkWidget*, GtkStateFlags, wxTextCtrl* win)
592 {
593     // restore non-default cursor, if any
594     win->GTKUpdateCursor(false, true);
595 }
596 }
597 #endif // __WXGTK3__
598 
599 //-----------------------------------------------------------------------------
600 //  wxTextCtrl
601 //-----------------------------------------------------------------------------
602 
BEGIN_EVENT_TABLE(wxTextCtrl,wxTextCtrlBase)603 BEGIN_EVENT_TABLE(wxTextCtrl, wxTextCtrlBase)
604     EVT_CHAR(wxTextCtrl::OnChar)
605 
606     EVT_MENU(wxID_CUT, wxTextCtrl::OnCut)
607     EVT_MENU(wxID_COPY, wxTextCtrl::OnCopy)
608     EVT_MENU(wxID_PASTE, wxTextCtrl::OnPaste)
609     EVT_MENU(wxID_UNDO, wxTextCtrl::OnUndo)
610     EVT_MENU(wxID_REDO, wxTextCtrl::OnRedo)
611 
612     EVT_UPDATE_UI(wxID_CUT, wxTextCtrl::OnUpdateCut)
613     EVT_UPDATE_UI(wxID_COPY, wxTextCtrl::OnUpdateCopy)
614     EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
615     EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
616     EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
617 
618     // wxTE_AUTO_URL wxTextUrl support. Currently only creates
619     // wxTextUrlEvent in the same cases as wxMSW, more can be added here.
620     EVT_MOTION      (wxTextCtrl::OnUrlMouseEvent)
621     EVT_LEFT_DOWN   (wxTextCtrl::OnUrlMouseEvent)
622     EVT_LEFT_UP     (wxTextCtrl::OnUrlMouseEvent)
623     EVT_LEFT_DCLICK (wxTextCtrl::OnUrlMouseEvent)
624     EVT_RIGHT_DOWN  (wxTextCtrl::OnUrlMouseEvent)
625     EVT_RIGHT_UP    (wxTextCtrl::OnUrlMouseEvent)
626     EVT_RIGHT_DCLICK(wxTextCtrl::OnUrlMouseEvent)
627 END_EVENT_TABLE()
628 
629 void wxTextCtrl::Init()
630 {
631     m_dontMarkDirty =
632     m_modified = false;
633 
634     m_countUpdatesToIgnore = 0;
635 
636     SetUpdateFont(false);
637 
638     m_text = NULL;
639     m_buffer = NULL;
640     m_showPositionOnThaw = NULL;
641     m_anonymousMarkList = NULL;
642 }
643 
~wxTextCtrl()644 wxTextCtrl::~wxTextCtrl()
645 {
646     if (m_text)
647         GTKDisconnect(m_text);
648     if (m_buffer)
649         GTKDisconnect(m_buffer);
650 
651     // this is also done by wxWindowGTK dtor, but has to be done here so our
652     // DoThaw() override is called
653     while (IsFrozen())
654         Thaw();
655 
656     if (m_anonymousMarkList)
657         g_slist_free(m_anonymousMarkList);
658 }
659 
wxTextCtrl(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)660 wxTextCtrl::wxTextCtrl( wxWindow *parent,
661                         wxWindowID id,
662                         const wxString &value,
663                         const wxPoint &pos,
664                         const wxSize &size,
665                         long style,
666                         const wxValidator& validator,
667                         const wxString &name )
668 {
669     Init();
670 
671     Create( parent, id, value, pos, size, style, validator, name );
672 }
673 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)674 bool wxTextCtrl::Create( wxWindow *parent,
675                          wxWindowID id,
676                          const wxString &value,
677                          const wxPoint &pos,
678                          const wxSize &size,
679                          long style,
680                          const wxValidator& validator,
681                          const wxString &name )
682 {
683     if (!PreCreation( parent, pos, size ) ||
684         !CreateBase( parent, id, pos, size, style, validator, name ))
685     {
686         wxFAIL_MSG( wxT("wxTextCtrl creation failed") );
687         return false;
688     }
689 
690     bool multi_line = (style & wxTE_MULTILINE) != 0;
691 
692     if (multi_line)
693     {
694         m_buffer = gtk_text_buffer_new(NULL);
695         gulong sig_id = g_signal_connect(m_buffer, "mark_set", G_CALLBACK(mark_set), &m_anonymousMarkList);
696         // Create view
697         m_text = gtk_text_view_new_with_buffer(m_buffer);
698         GTKConnectFreezeWidget(m_text);
699         // gtk_text_view_set_buffer adds its own reference
700         g_object_unref(m_buffer);
701         g_signal_handler_disconnect(m_buffer, sig_id);
702 
703         // create "ShowPosition" marker
704         GtkTextIter iter;
705         gtk_text_buffer_get_start_iter(m_buffer, &iter);
706         gtk_text_buffer_create_mark(m_buffer, "ShowPosition", &iter, true);
707 
708         // create scrolled window
709         m_widget = gtk_scrolled_window_new( NULL, NULL );
710         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( m_widget ),
711                                         GTK_POLICY_AUTOMATIC,
712                                         style & wxTE_NO_VSCROLL
713                                             ? GTK_POLICY_NEVER
714                                             : GTK_POLICY_AUTOMATIC );
715         // for ScrollLines/Pages
716         m_scrollBar[1] = GTK_RANGE(gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(m_widget)));
717 
718         // Insert view into scrolled window
719         gtk_container_add( GTK_CONTAINER(m_widget), m_text );
720 
721         GTKSetWrapMode();
722 
723         GTKScrolledWindowSetBorder(m_widget, style);
724 
725         gtk_widget_add_events( GTK_WIDGET(m_text), GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
726 
727         gtk_widget_set_can_focus(m_widget, FALSE);
728     }
729     else
730     {
731         // a single-line text control: no need for scrollbars
732         m_widget =
733         m_text = gtk_entry_new();
734 
735         // Set a minimal width for preferred size to avoid GTK3 debug warnings
736         // about size allocations smaller than preferred size
737         gtk_entry_set_width_chars((GtkEntry*)m_text, 1);
738 
739         // work around probable bug in GTK+ 2.18 when calling WriteText on a
740         // new, empty control, see http://trac.wxwidgets.org/ticket/11409
741         gtk_entry_get_text((GtkEntry*)m_text);
742 
743         if (style & wxNO_BORDER)
744             g_object_set (m_text, "has-frame", FALSE, NULL);
745 
746     }
747     g_object_ref(m_widget);
748 
749     m_parent->DoAddChild( this );
750 
751     m_focusWidget = m_text;
752 
753     PostCreation(size);
754 
755     if (multi_line)
756     {
757         gtk_widget_show(m_text);
758     }
759 
760     // We want to be notified about text changes.
761     if (multi_line)
762     {
763         g_signal_connect (m_buffer, "changed",
764                           G_CALLBACK (gtk_text_changed_callback), this);
765     }
766     else
767     {
768         g_signal_connect (m_text, "changed",
769                           G_CALLBACK (gtk_text_changed_callback), this);
770     }
771 
772     // Catch to disable focus out handling
773     g_signal_connect (m_text, "populate_popup",
774                       G_CALLBACK (gtk_textctrl_populate_popup),
775                       this);
776 
777     if (!value.empty())
778     {
779         SetValue( value );
780     }
781 
782     if (style & wxTE_PASSWORD)
783         GTKSetVisibility();
784 
785     if (style & wxTE_READONLY)
786         GTKSetEditable();
787 
788     // left justification (alignment) is the default anyhow
789     if ( style & (wxTE_RIGHT | wxTE_CENTRE) )
790         GTKSetJustification();
791 
792     if (multi_line)
793     {
794         // Handle URLs on multi-line controls with wxTE_AUTO_URL style
795         if (style & wxTE_AUTO_URL)
796         {
797             GtkTextIter start, end;
798 
799             // We create our wxUrl tag here for slight efficiency gain - we
800             // don't have to check for the tag existence in callbacks,
801             // hereby it's guaranteed to exist.
802             gtk_text_buffer_create_tag(m_buffer, "wxUrl",
803                                        "foreground", "blue",
804                                        "underline", PANGO_UNDERLINE_SINGLE,
805                                        NULL);
806 
807             // Check for URLs after each text change
808             g_signal_connect_after (m_buffer, "insert_text",
809                                     G_CALLBACK (au_insert_text_callback), this);
810             g_signal_connect_after (m_buffer, "delete_range",
811                                     G_CALLBACK (au_delete_range_callback), this);
812 
813             // Block all wxUrl tag applying unless we do it ourselves, in which case we
814             // block this callback temporarily. This takes care of gtk+ internal
815             // gtk_text_buffer_insert_range* calls that would copy our URL tag otherwise,
816             // which is undesired because only a part of the URL might be copied.
817             // The insert-text signal emitted inside it will take care of newly formed
818             // or wholly copied URLs.
819             g_signal_connect (m_buffer, "apply_tag",
820                               G_CALLBACK (au_apply_tag_callback), NULL);
821 
822             // Check for URLs in the initial string passed to Create
823             gtk_text_buffer_get_start_iter(m_buffer, &start);
824             gtk_text_buffer_get_end_iter(m_buffer, &end);
825             au_check_range(&start, &end);
826         }
827 
828         // Also connect a normal (not "after") signal handler for checking for
829         // the IME-generated input.
830         g_signal_connect(m_buffer, "insert_text",
831                          G_CALLBACK(wx_insert_text_callback), this);
832     }
833     else // single line
834     {
835         // do the right thing with Enter presses depending on whether we have
836         // wxTE_PROCESS_ENTER or not
837         GTKSetActivatesDefault();
838 
839         GTKConnectInsertTextSignal(GTK_ENTRY(m_text));
840     }
841 
842     GTKConnectClipboardSignals(m_text);
843 
844 #ifdef __WXGTK3__
845     g_signal_connect(m_text, "state_flags_changed", G_CALLBACK(state_flags_changed), this);
846 #endif
847 
848     return true;
849 }
850 
GetEditable() const851 GtkEditable *wxTextCtrl::GetEditable() const
852 {
853     wxCHECK_MSG( IsSingleLine(), NULL, "shouldn't be called for multiline" );
854 
855     return GTK_EDITABLE(m_text);
856 }
857 
GetEntry() const858 GtkEntry *wxTextCtrl::GetEntry() const
859 {
860     return GTK_ENTRY(m_text);
861 }
862 
GTKIMFilterKeypress(GdkEventKey * event) const863 int wxTextCtrl::GTKIMFilterKeypress(GdkEventKey* event) const
864 {
865     if (IsSingleLine())
866         return wxTextEntry::GTKIMFilterKeypress(event);
867 
868     int result;
869 #if GTK_CHECK_VERSION(2, 22, 0)
870 #ifndef __WXGTK3__
871     result = false;
872     if (gtk_check_version(2,22,0) == NULL)
873 #endif
874     {
875         result = gtk_text_view_im_context_filter_keypress(GTK_TEXT_VIEW(m_text), event);
876     }
877 #else // GTK+ < 2.22
878     wxUnusedVar(event);
879     result = false;
880 #endif // GTK+ 2.22+
881 
882     return result;
883 }
884 
885 // ----------------------------------------------------------------------------
886 // flags handling
887 // ----------------------------------------------------------------------------
888 
GTKSetEditable()889 void wxTextCtrl::GTKSetEditable()
890 {
891     gboolean editable = !HasFlag(wxTE_READONLY);
892     if ( IsSingleLine() )
893         gtk_editable_set_editable(GTK_EDITABLE(m_text), editable);
894     else
895         gtk_text_view_set_editable(GTK_TEXT_VIEW(m_text), editable);
896 }
897 
GTKSetVisibility()898 void wxTextCtrl::GTKSetVisibility()
899 {
900     wxCHECK_RET( IsSingleLine(),
901                  "wxTE_PASSWORD is for single line text controls only" );
902 
903     gtk_entry_set_visibility(GTK_ENTRY(m_text), !HasFlag(wxTE_PASSWORD));
904 }
905 
GTKSetActivatesDefault()906 void wxTextCtrl::GTKSetActivatesDefault()
907 {
908     wxCHECK_RET( IsSingleLine(),
909                  "wxTE_PROCESS_ENTER is for single line text controls only" );
910 
911     gtk_entry_set_activates_default(GTK_ENTRY(m_text),
912                                     !HasFlag(wxTE_PROCESS_ENTER));
913 }
914 
GTKSetWrapMode()915 void wxTextCtrl::GTKSetWrapMode()
916 {
917     // no wrapping in single line controls
918     if ( !IsMultiLine() )
919         return;
920 
921     // translate wx wrapping style to GTK+
922     GtkWrapMode wrap;
923     if ( HasFlag( wxTE_DONTWRAP ) )
924         wrap = GTK_WRAP_NONE;
925     else if ( HasFlag( wxTE_CHARWRAP ) )
926         wrap = GTK_WRAP_CHAR;
927     else if ( HasFlag( wxTE_WORDWRAP ) )
928         wrap = GTK_WRAP_WORD;
929     else // HasFlag(wxTE_BESTWRAP) always true as wxTE_BESTWRAP == 0
930         wrap = GTK_WRAP_WORD_CHAR;
931 
932     gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( m_text ), wrap );
933 }
934 
GTKSetJustification()935 void wxTextCtrl::GTKSetJustification()
936 {
937     if ( IsMultiLine() )
938     {
939         GtkJustification just;
940         if ( HasFlag(wxTE_RIGHT) )
941             just = GTK_JUSTIFY_RIGHT;
942         else if ( HasFlag(wxTE_CENTRE) )
943             just = GTK_JUSTIFY_CENTER;
944         else // wxTE_LEFT == 0
945             just = GTK_JUSTIFY_LEFT;
946 
947         gtk_text_view_set_justification(GTK_TEXT_VIEW(m_text), just);
948     }
949     else // single line
950     {
951         gfloat align;
952         if ( HasFlag(wxTE_RIGHT) )
953             align = 1.0;
954         else if ( HasFlag(wxTE_CENTRE) )
955             align = 0.5;
956         else // single line
957             align = 0.0;
958 
959         gtk_entry_set_alignment(GTK_ENTRY(m_text), align);
960     }
961 }
962 
SetWindowStyleFlag(long style)963 void wxTextCtrl::SetWindowStyleFlag(long style)
964 {
965     long styleOld = GetWindowStyleFlag();
966 
967     wxTextCtrlBase::SetWindowStyleFlag(style);
968 
969     if ( (style & wxTE_READONLY) != (styleOld & wxTE_READONLY) )
970         GTKSetEditable();
971 
972     if ( (style & wxTE_PASSWORD) != (styleOld & wxTE_PASSWORD) )
973         GTKSetVisibility();
974 
975     if ( (style & wxTE_PROCESS_ENTER) != (styleOld & wxTE_PROCESS_ENTER) )
976         GTKSetActivatesDefault();
977 
978     static const long flagsWrap = wxTE_WORDWRAP | wxTE_CHARWRAP | wxTE_DONTWRAP;
979     if ( (style & flagsWrap) != (styleOld & flagsWrap) )
980         GTKSetWrapMode();
981 
982     static const long flagsAlign = wxTE_LEFT | wxTE_CENTRE | wxTE_RIGHT;
983     if ( (style & flagsAlign) != (styleOld & flagsAlign) )
984         GTKSetJustification();
985 }
986 
987 // ----------------------------------------------------------------------------
988 // control value
989 // ----------------------------------------------------------------------------
990 
GetValue() const991 wxString wxTextCtrl::GetValue() const
992 {
993     wxCHECK_MSG( m_text != NULL, wxEmptyString, wxT("invalid text ctrl") );
994 
995     if ( IsMultiLine() )
996     {
997         GtkTextIter start;
998         gtk_text_buffer_get_start_iter( m_buffer, &start );
999         GtkTextIter end;
1000         gtk_text_buffer_get_end_iter( m_buffer, &end );
1001         wxGtkString text(gtk_text_buffer_get_text(m_buffer, &start, &end, true));
1002 
1003         return wxGTK_CONV_BACK(text);
1004     }
1005     else // single line
1006     {
1007         return wxTextEntry::GetValue();
1008     }
1009 }
1010 
GetTextEncoding() const1011 wxFontEncoding wxTextCtrl::GetTextEncoding() const
1012 {
1013     // GTK+ uses UTF-8 internally, we need to convert to it but from which
1014     // encoding?
1015 
1016     // first check the default text style (we intentionally don't check the
1017     // style for the current position as it doesn't make sense for SetValue())
1018     const wxTextAttr& style = GetDefaultStyle();
1019     wxFontEncoding enc = style.HasFontEncoding() ? style.GetFontEncoding()
1020                                          : wxFONTENCODING_SYSTEM;
1021 
1022     // fall back to the controls font if no style
1023     if ( enc == wxFONTENCODING_SYSTEM && m_hasFont )
1024         enc = GetFont().GetEncoding();
1025 
1026     return enc;
1027 }
1028 
IsEmpty() const1029 bool wxTextCtrl::IsEmpty() const
1030 {
1031     if ( IsMultiLine() )
1032         return gtk_text_buffer_get_char_count(m_buffer) == 0;
1033 
1034     return wxTextEntry::IsEmpty();
1035 }
1036 
DoSetValue(const wxString & value,int flags)1037 void wxTextCtrl::DoSetValue( const wxString &value, int flags )
1038 {
1039     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1040 
1041     m_modified = false;
1042 
1043     if ( !IsMultiLine() )
1044     {
1045         wxTextEntry::DoSetValue(value, flags);
1046         return;
1047     }
1048 
1049     if (value.IsEmpty())
1050     {
1051         if ( !(flags & SetValue_SendEvent) )
1052             EnableTextChangedEvents(false);
1053 
1054         gtk_text_buffer_set_text( m_buffer, "", 0 );
1055 
1056         if ( !(flags & SetValue_SendEvent) )
1057             EnableTextChangedEvents(true);
1058 
1059         return;
1060     }
1061 
1062 #if wxUSE_UNICODE
1063     const wxCharBuffer buffer(value.utf8_str());
1064 #else
1065     wxFontEncoding enc = m_defaultStyle.HasFont()
1066                             ? m_defaultStyle.GetFont().GetEncoding()
1067                             : wxFONTENCODING_SYSTEM;
1068     if ( enc == wxFONTENCODING_SYSTEM )
1069         enc = GetTextEncoding();
1070 
1071     const wxCharBuffer buffer(wxGTK_CONV_ENC(value, enc));
1072     if ( !buffer )
1073     {
1074         // see comment in WriteText() as to why we must warn the user about
1075         // this
1076         wxLogWarning(_("Failed to set text in the text control."));
1077         return;
1078     }
1079 #endif
1080 
1081     if ( !(flags & SetValue_SendEvent) )
1082     {
1083         EnableTextChangedEvents(false);
1084     }
1085 
1086     gtk_text_buffer_set_text( m_buffer, buffer, strlen(buffer) );
1087 
1088     if ( !m_defaultStyle.IsDefault() )
1089     {
1090         GtkTextIter start, end;
1091         gtk_text_buffer_get_bounds( m_buffer, &start, &end );
1092         wxGtkTextApplyTagsFromAttr(m_widget, m_buffer, m_defaultStyle,
1093                                    &start, &end);
1094     }
1095 
1096     if ( !(flags & SetValue_SendEvent) )
1097     {
1098         EnableTextChangedEvents(true);
1099     }
1100 }
1101 
WriteText(const wxString & text)1102 void wxTextCtrl::WriteText( const wxString &text )
1103 {
1104     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1105 
1106     // we're changing the text programmatically
1107     DontMarkDirtyOnNextChange();
1108 
1109     // avoid generating wxEVT_CHAR when called from wxEVT_CHAR handler
1110     GdkEventKey* const imKeyEvent_save = m_imKeyEvent;
1111     m_imKeyEvent = NULL;
1112     wxON_BLOCK_EXIT_SET(m_imKeyEvent, imKeyEvent_save);
1113 
1114     if ( !IsMultiLine() )
1115     {
1116         wxTextEntry::WriteText(text);
1117         m_dontMarkDirty = false;
1118         return;
1119     }
1120 
1121 #if wxUSE_UNICODE
1122     const wxCharBuffer buffer(text.utf8_str());
1123 #else
1124     // check if we have a specific style for the current position
1125     wxFontEncoding enc = wxFONTENCODING_SYSTEM;
1126     wxTextAttr style;
1127     if ( GetStyle(GetInsertionPoint(), style) && style.HasFontEncoding() )
1128     {
1129         enc = style.GetFontEncoding();
1130     }
1131 
1132     if ( enc == wxFONTENCODING_SYSTEM )
1133         enc = GetTextEncoding();
1134 
1135     const wxCharBuffer buffer(wxGTK_CONV_ENC(text, enc));
1136     if ( !buffer )
1137     {
1138         // we must log an error here as losing the text like this can be a
1139         // serious problem (e.g. imagine the document edited by user being
1140         // empty instead of containing the correct text)
1141         wxLogWarning(_("Failed to insert text in the control."));
1142         m_dontMarkDirty = false;
1143         return;
1144     }
1145 #endif
1146 
1147     // First remove the selection if there is one
1148     gtk_text_buffer_delete_selection(m_buffer, false, true);
1149 
1150     // Insert the text
1151     wxGtkTextInsert( m_text, m_buffer, m_defaultStyle, buffer );
1152 
1153     // Scroll to cursor, but only if scrollbar thumb is at the very bottom
1154     // won't work when frozen, text view is not using m_buffer then
1155     if (!IsFrozen())
1156     {
1157         GtkAdjustment* adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(m_widget));
1158         const double value = gtk_adjustment_get_value(adj);
1159         const double upper = gtk_adjustment_get_upper(adj);
1160         const double page_size = gtk_adjustment_get_page_size(adj);
1161         if (wxIsSameDouble(value, upper - page_size))
1162         {
1163             gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(m_text),
1164                 gtk_text_buffer_get_insert(m_buffer), 0, false, 0, 1);
1165         }
1166     }
1167     m_dontMarkDirty = false;
1168 }
1169 
GetLineText(long lineNo) const1170 wxString wxTextCtrl::GetLineText( long lineNo ) const
1171 {
1172     wxString result;
1173     if ( IsMultiLine() )
1174     {
1175         GtkTextIter line;
1176         gtk_text_buffer_get_iter_at_line(m_buffer,&line,lineNo);
1177 
1178         GtkTextIter end = line;
1179         // avoid skipping to the next line end if this one is empty
1180         if ( !gtk_text_iter_ends_line(&line) )
1181             gtk_text_iter_forward_to_line_end(&end);
1182 
1183         wxGtkString text(gtk_text_buffer_get_text(m_buffer, &line, &end, true));
1184         result = wxGTK_CONV_BACK(text);
1185     }
1186     else
1187     {
1188         if (lineNo == 0)
1189             result = GetValue();
1190     }
1191     return result;
1192 }
1193 
OnDropFiles(wxDropFilesEvent & WXUNUSED (event))1194 void wxTextCtrl::OnDropFiles( wxDropFilesEvent &WXUNUSED(event) )
1195 {
1196   /* If you implement this, don't forget to update the documentation!
1197    * (file docs/latex/wx/text.tex) */
1198     wxFAIL_MSG( wxT("wxTextCtrl::OnDropFiles not implemented") );
1199 }
1200 
PositionToXY(long pos,long * x,long * y) const1201 bool wxTextCtrl::PositionToXY(long pos, long *x, long *y ) const
1202 {
1203     if ( IsMultiLine() )
1204     {
1205         GtkTextIter iter;
1206 
1207         if (pos > GetLastPosition())
1208             return false;
1209 
1210         gtk_text_buffer_get_iter_at_offset(m_buffer, &iter, pos);
1211 
1212         if ( y )
1213             *y = gtk_text_iter_get_line(&iter);
1214         if ( x )
1215             *x = gtk_text_iter_get_line_offset(&iter);
1216     }
1217     else // single line control
1218     {
1219         if (pos <= gtk_entry_get_text_length(GTK_ENTRY(m_text)))
1220         {
1221             if ( y )
1222                 *y = 0;
1223             if ( x )
1224                 *x = pos;
1225         }
1226         else
1227         {
1228             // index out of bounds
1229             return false;
1230         }
1231     }
1232 
1233     return true;
1234 }
1235 
XYToPosition(long x,long y) const1236 long wxTextCtrl::XYToPosition(long x, long y ) const
1237 {
1238     if ( IsSingleLine() )
1239         return 0;
1240 
1241     GtkTextIter iter;
1242     if (y >= gtk_text_buffer_get_line_count (m_buffer))
1243         return -1;
1244 
1245     gtk_text_buffer_get_iter_at_line(m_buffer, &iter, y);
1246     if (x >= gtk_text_iter_get_chars_in_line (&iter))
1247         return -1;
1248 
1249     return gtk_text_iter_get_offset(&iter) + x;
1250 }
1251 
GetLineLength(long lineNo) const1252 int wxTextCtrl::GetLineLength(long lineNo) const
1253 {
1254     if ( IsMultiLine() )
1255     {
1256         int last_line = gtk_text_buffer_get_line_count( m_buffer ) - 1;
1257         if (lineNo > last_line)
1258             return -1;
1259 
1260         GtkTextIter iter;
1261         gtk_text_buffer_get_iter_at_line(m_buffer, &iter, lineNo);
1262         // get_chars_in_line return includes paragraph delimiters, so need to subtract 1 IF it is not the last line
1263         return gtk_text_iter_get_chars_in_line(&iter) - ((lineNo == last_line) ? 0 : 1);
1264     }
1265     else
1266     {
1267         wxString str = GetLineText (lineNo);
1268         return (int) str.length();
1269     }
1270 }
1271 
DoPositionToCoords(long pos) const1272 wxPoint wxTextCtrl::DoPositionToCoords(long pos) const
1273 {
1274     if ( !IsMultiLine() )
1275     {
1276         // Single line text entry (GtkTextEntry) doesn't have support for
1277         // getting the coordinates for the given offset. Perhaps we could
1278         // find them ourselves by using GetTextExtent() but for now just leave
1279         // it unimplemented, this function is more useful for multiline
1280         // controls anyhow.
1281         return wxDefaultPosition;
1282     }
1283 
1284     // Window coordinates for the given position is calculated by getting
1285     // the buffer coordinates and converting them to window coordinates.
1286     GtkTextView *textview = GTK_TEXT_VIEW(m_text);
1287 
1288     GtkTextIter iter;
1289     gtk_text_buffer_get_iter_at_offset(m_buffer, &iter, pos);
1290 
1291     GdkRectangle bufferCoords;
1292     gtk_text_view_get_iter_location(textview, &iter, &bufferCoords);
1293 
1294     gint winCoordX = 0,
1295          winCoordY = 0;
1296     gtk_text_view_buffer_to_window_coords(textview, GTK_TEXT_WINDOW_WIDGET,
1297                                           bufferCoords.x, bufferCoords.y,
1298                                           &winCoordX, &winCoordY);
1299 
1300     return wxPoint(winCoordX, winCoordY);
1301 }
1302 
GetNumberOfLines() const1303 int wxTextCtrl::GetNumberOfLines() const
1304 {
1305     if ( IsMultiLine() )
1306     {
1307         return gtk_text_buffer_get_line_count( m_buffer );
1308     }
1309     else // single line
1310     {
1311         return 1;
1312     }
1313 }
1314 
SetInsertionPoint(long pos)1315 void wxTextCtrl::SetInsertionPoint( long pos )
1316 {
1317     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1318 
1319     if ( IsMultiLine() )
1320     {
1321         GtkTextIter iter;
1322         gtk_text_buffer_get_iter_at_offset( m_buffer, &iter, pos );
1323         gtk_text_buffer_place_cursor( m_buffer, &iter );
1324         GtkTextMark* mark = gtk_text_buffer_get_insert(m_buffer);
1325         if (IsFrozen())
1326             // defer until Thaw, text view is not using m_buffer now
1327             m_showPositionOnThaw = mark;
1328         else
1329             gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(m_text), mark);
1330     }
1331     else // single line
1332     {
1333         wxTextEntry::SetInsertionPoint(pos);
1334     }
1335 }
1336 
SetEditable(bool editable)1337 void wxTextCtrl::SetEditable( bool editable )
1338 {
1339     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1340 
1341     if ( IsMultiLine() )
1342     {
1343         gtk_text_view_set_editable( GTK_TEXT_VIEW(m_text), editable );
1344     }
1345     else // single line
1346     {
1347         wxTextEntry::SetEditable(editable);
1348     }
1349 }
1350 
Enable(bool enable)1351 bool wxTextCtrl::Enable( bool enable )
1352 {
1353     if (!wxWindowBase::Enable(enable))
1354     {
1355         // nothing to do
1356         return false;
1357     }
1358 
1359     gtk_widget_set_sensitive( m_text, enable );
1360 
1361     return true;
1362 }
1363 
MarkDirty()1364 void wxTextCtrl::MarkDirty()
1365 {
1366     m_modified = true;
1367 }
1368 
DiscardEdits()1369 void wxTextCtrl::DiscardEdits()
1370 {
1371     m_modified = false;
1372 }
1373 
1374 // ----------------------------------------------------------------------------
1375 // event handling
1376 // ----------------------------------------------------------------------------
1377 
EnableTextChangedEvents(bool enable)1378 void wxTextCtrl::EnableTextChangedEvents(bool enable)
1379 {
1380     if ( enable )
1381     {
1382         g_signal_handlers_unblock_by_func(GetTextObject(),
1383             (gpointer)gtk_text_changed_callback, this);
1384     }
1385     else // disable events
1386     {
1387         g_signal_handlers_block_by_func(GetTextObject(),
1388             (gpointer)gtk_text_changed_callback, this);
1389     }
1390 }
1391 
IgnoreTextUpdate()1392 bool wxTextCtrl::IgnoreTextUpdate()
1393 {
1394     if ( m_countUpdatesToIgnore > 0 )
1395     {
1396         m_countUpdatesToIgnore--;
1397 
1398         return true;
1399     }
1400 
1401     return false;
1402 }
1403 
MarkDirtyOnChange()1404 bool wxTextCtrl::MarkDirtyOnChange()
1405 {
1406     if ( m_dontMarkDirty )
1407     {
1408         m_dontMarkDirty = false;
1409 
1410         return false;
1411     }
1412 
1413     return true;
1414 }
1415 
SetSelection(long from,long to)1416 void wxTextCtrl::SetSelection( long from, long to )
1417 {
1418     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1419 
1420     if ( IsMultiLine() )
1421     {
1422         if (from == -1 && to == -1)
1423         {
1424             from = 0;
1425             to = GetValue().length();
1426         }
1427 
1428         GtkTextIter fromi, toi;
1429         gtk_text_buffer_get_iter_at_offset( m_buffer, &fromi, from );
1430         gtk_text_buffer_get_iter_at_offset( m_buffer, &toi, to );
1431 
1432         gtk_text_buffer_select_range( m_buffer, &fromi, &toi );
1433     }
1434     else // single line
1435     {
1436         wxTextEntry::SetSelection(from, to);
1437     }
1438 }
1439 
ShowPosition(long pos)1440 void wxTextCtrl::ShowPosition( long pos )
1441 {
1442     if (IsMultiLine())
1443     {
1444         GtkTextIter iter;
1445         gtk_text_buffer_get_iter_at_offset(m_buffer, &iter, int(pos));
1446         GtkTextMark* mark = gtk_text_buffer_get_mark(m_buffer, "ShowPosition");
1447         gtk_text_buffer_move_mark(m_buffer, mark, &iter);
1448         if (IsFrozen())
1449             // defer until Thaw, text view is not using m_buffer now
1450             m_showPositionOnThaw = mark;
1451         else
1452             gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(m_text), mark);
1453     }
1454 }
1455 
1456 wxTextCtrlHitTestResult
HitTest(const wxPoint & pt,long * pos) const1457 wxTextCtrl::HitTest(const wxPoint& pt, long *pos) const
1458 {
1459     if ( !IsMultiLine() )
1460     {
1461         // These variables will contain the position inside PangoLayout.
1462         int x = pt.x,
1463             y = pt.y;
1464 
1465         // Get the offsets of PangoLayout inside the control.
1466         //
1467         // Note that contrary to what GTK+ documentation implies, the
1468         // horizontal offset already accounts for scrolling, i.e. it will be
1469         // negative if text is scrolled.
1470         gint ofsX = 0,
1471              ofsY = 0;
1472         gtk_entry_get_layout_offsets(GTK_ENTRY(m_text), &ofsX, &ofsY);
1473 
1474         x -= ofsX;
1475         y -= ofsY;
1476 
1477         // And scale the coordinates for Pango.
1478         x *= PANGO_SCALE;
1479         y *= PANGO_SCALE;
1480 
1481         PangoLayout* const layout = gtk_entry_get_layout(GTK_ENTRY(m_text));
1482 
1483         int idx = -1,
1484             ofs = 0;
1485         if ( !pango_layout_xy_to_index(layout, x, y, &idx, &ofs) )
1486         {
1487             // Try to guess why did it fail.
1488             if ( x < 0 || y < 0 )
1489             {
1490                 if ( pos )
1491                     *pos = 0;
1492 
1493                 return wxTE_HT_BEFORE;
1494             }
1495             else
1496             {
1497                 if ( pos )
1498                     *pos = wxTextEntry::GetLastPosition();
1499 
1500                 return wxTE_HT_BEYOND;
1501             }
1502         }
1503 
1504         if ( pos )
1505             *pos = idx;
1506 
1507         return wxTE_HT_ON_TEXT;
1508     }
1509 
1510     int x, y;
1511     gtk_text_view_window_to_buffer_coords
1512     (
1513         GTK_TEXT_VIEW(m_text),
1514         GTK_TEXT_WINDOW_TEXT,
1515         pt.x, pt.y,
1516         &x, &y
1517     );
1518 
1519     GtkTextIter iter;
1520     gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(m_text), &iter, x, y);
1521     if ( pos )
1522         *pos = gtk_text_iter_get_offset(&iter);
1523 
1524     return wxTE_HT_ON_TEXT;
1525 }
1526 
GetInsertionPoint() const1527 long wxTextCtrl::GetInsertionPoint() const
1528 {
1529     wxCHECK_MSG( m_text != NULL, 0, wxT("invalid text ctrl") );
1530 
1531     if ( IsMultiLine() )
1532     {
1533         // There is no direct accessor for the cursor, but
1534         // internally, the cursor is the "mark" called
1535         // "insert" in the text view's btree structure.
1536 
1537         GtkTextMark *mark = gtk_text_buffer_get_insert( m_buffer );
1538         GtkTextIter cursor;
1539         gtk_text_buffer_get_iter_at_mark( m_buffer, &cursor, mark );
1540 
1541         return gtk_text_iter_get_offset( &cursor );
1542     }
1543     else
1544     {
1545         return wxTextEntry::GetInsertionPoint();
1546     }
1547 }
1548 
GetLastPosition() const1549 wxTextPos wxTextCtrl::GetLastPosition() const
1550 {
1551     wxCHECK_MSG( m_text != NULL, 0, wxT("invalid text ctrl") );
1552 
1553     int pos = 0;
1554 
1555     if ( IsMultiLine() )
1556     {
1557         GtkTextIter end;
1558         gtk_text_buffer_get_end_iter( m_buffer, &end );
1559 
1560         pos = gtk_text_iter_get_offset( &end );
1561     }
1562     else // single line
1563     {
1564         pos = wxTextEntry::GetLastPosition();
1565     }
1566 
1567     return (long)pos;
1568 }
1569 
Remove(long from,long to)1570 void wxTextCtrl::Remove( long from, long to )
1571 {
1572     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1573 
1574     if ( IsMultiLine() )
1575     {
1576         GtkTextIter fromi, toi;
1577         gtk_text_buffer_get_iter_at_offset( m_buffer, &fromi, from );
1578         gtk_text_buffer_get_iter_at_offset( m_buffer, &toi, to );
1579 
1580         gtk_text_buffer_delete( m_buffer, &fromi, &toi );
1581     }
1582     else // single line
1583     {
1584         wxTextEntry::Remove(from, to);
1585     }
1586 }
1587 
Cut()1588 void wxTextCtrl::Cut()
1589 {
1590     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1591 
1592     if ( IsMultiLine() )
1593         g_signal_emit_by_name (m_text, "cut-clipboard");
1594     else
1595         wxTextEntry::Cut();
1596 }
1597 
Copy()1598 void wxTextCtrl::Copy()
1599 {
1600     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1601 
1602     if ( IsMultiLine() )
1603         g_signal_emit_by_name (m_text, "copy-clipboard");
1604     else
1605         wxTextEntry::Copy();
1606 }
1607 
Paste()1608 void wxTextCtrl::Paste()
1609 {
1610     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1611 
1612     if ( IsMultiLine() )
1613         g_signal_emit_by_name (m_text, "paste-clipboard");
1614     else
1615         wxTextEntry::Paste();
1616 }
1617 
1618 // If the return values from and to are the same, there is no
1619 // selection.
GetSelection(long * fromOut,long * toOut) const1620 void wxTextCtrl::GetSelection(long* fromOut, long* toOut) const
1621 {
1622     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1623 
1624     if ( !IsMultiLine() )
1625     {
1626         wxTextEntry::GetSelection(fromOut, toOut);
1627         return;
1628     }
1629 
1630     gint from, to;
1631 
1632     GtkTextIter ifrom, ito;
1633     if ( gtk_text_buffer_get_selection_bounds(m_buffer, &ifrom, &ito) )
1634     {
1635         from = gtk_text_iter_get_offset(&ifrom);
1636         to = gtk_text_iter_get_offset(&ito);
1637 
1638         if ( from > to )
1639         {
1640             // exchange them to be compatible with wxMSW
1641             gint tmp = from;
1642             from = to;
1643             to = tmp;
1644         }
1645     }
1646     else // no selection
1647     {
1648         from =
1649         to = GetInsertionPoint();
1650     }
1651 
1652     if ( fromOut )
1653         *fromOut = from;
1654     if ( toOut )
1655         *toOut = to;
1656 }
1657 
1658 
IsEditable() const1659 bool wxTextCtrl::IsEditable() const
1660 {
1661     wxCHECK_MSG( m_text != NULL, false, wxT("invalid text ctrl") );
1662 
1663     if ( IsMultiLine() )
1664     {
1665         return gtk_text_view_get_editable(GTK_TEXT_VIEW(m_text)) != 0;
1666     }
1667     else
1668     {
1669         return wxTextEntry::IsEditable();
1670     }
1671 }
1672 
IsModified() const1673 bool wxTextCtrl::IsModified() const
1674 {
1675     return m_modified;
1676 }
1677 
OnChar(wxKeyEvent & key_event)1678 void wxTextCtrl::OnChar( wxKeyEvent &key_event )
1679 {
1680     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
1681 
1682     if ( key_event.GetKeyCode() == WXK_RETURN )
1683     {
1684         if ( HasFlag(wxTE_PROCESS_ENTER) )
1685         {
1686             wxCommandEvent event(wxEVT_TEXT_ENTER, m_windowId);
1687             event.SetEventObject(this);
1688             event.SetString(GetValue());
1689             if ( HandleWindowEvent(event) )
1690                 return;
1691         }
1692     }
1693 
1694     key_event.Skip();
1695 }
1696 
GetConnectWidget()1697 GtkWidget* wxTextCtrl::GetConnectWidget()
1698 {
1699     return GTK_WIDGET(m_text);
1700 }
1701 
GTKGetWindow(wxArrayGdkWindows & WXUNUSED (windows)) const1702 GdkWindow *wxTextCtrl::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
1703 {
1704     if ( IsMultiLine() )
1705     {
1706         return gtk_text_view_get_window(GTK_TEXT_VIEW(m_text),
1707                                         GTK_TEXT_WINDOW_TEXT );
1708     }
1709     else
1710     {
1711 #ifdef __WXGTK3__
1712         GdkWindow* wxGTKFindWindow(GtkWidget* widget);
1713         return wxGTKFindWindow(m_text);
1714 #else
1715         return gtk_entry_get_text_window(GTK_ENTRY(m_text));
1716 #endif
1717     }
1718 }
1719 
1720 // the font will change for subsequent text insertiongs
SetFont(const wxFont & font)1721 bool wxTextCtrl::SetFont( const wxFont &font )
1722 {
1723     wxCHECK_MSG( m_text != NULL, false, wxT("invalid text ctrl") );
1724 
1725     if ( !wxTextCtrlBase::SetFont(font) )
1726     {
1727         // font didn't change, nothing to do
1728         return false;
1729     }
1730 
1731     if ( IsMultiLine() )
1732     {
1733         SetUpdateFont(true);
1734 
1735         m_defaultStyle.SetFont(font);
1736 
1737         ChangeFontGlobally();
1738     }
1739 
1740     return true;
1741 }
1742 
ChangeFontGlobally()1743 void wxTextCtrl::ChangeFontGlobally()
1744 {
1745     // this method is very inefficient and hence should be called as rarely as
1746     // possible!
1747     //
1748     // TODO: it can be implemented much more efficiently for GTK2
1749     wxASSERT_MSG( IsMultiLine(),
1750                   wxT("shouldn't be called for single line controls") );
1751 
1752     wxString value = GetValue();
1753     if ( !value.empty() )
1754     {
1755         SetUpdateFont(false);
1756 
1757         Clear();
1758         AppendText(value);
1759     }
1760 }
1761 
SetForegroundColour(const wxColour & colour)1762 bool wxTextCtrl::SetForegroundColour(const wxColour& colour)
1763 {
1764     if ( !wxControl::SetForegroundColour(colour) )
1765         return false;
1766 
1767     // update default fg colour too
1768     m_defaultStyle.SetTextColour(colour);
1769 
1770     return true;
1771 }
1772 
SetBackgroundColour(const wxColour & colour)1773 bool wxTextCtrl::SetBackgroundColour( const wxColour &colour )
1774 {
1775     wxCHECK_MSG( m_text != NULL, false, wxT("invalid text ctrl") );
1776 
1777     if ( !wxControl::SetBackgroundColour( colour ) )
1778         return false;
1779 
1780     if (!m_backgroundColour.IsOk())
1781         return false;
1782 
1783     // change active background color too
1784     m_defaultStyle.SetBackgroundColour( colour );
1785 
1786     return true;
1787 }
1788 
SetStyle(long start,long end,const wxTextAttr & style)1789 bool wxTextCtrl::SetStyle( long start, long end, const wxTextAttr& style )
1790 {
1791     if ( IsMultiLine() )
1792     {
1793         if ( style.IsDefault() )
1794         {
1795             // nothing to do
1796             return true;
1797         }
1798 
1799         gint l = gtk_text_buffer_get_char_count( m_buffer );
1800 
1801         wxCHECK_MSG( start >= 0 && end <= l, false,
1802                      wxT("invalid range in wxTextCtrl::SetStyle") );
1803 
1804         GtkTextIter starti, endi;
1805         gtk_text_buffer_get_iter_at_offset( m_buffer, &starti, start );
1806         gtk_text_buffer_get_iter_at_offset( m_buffer, &endi, end );
1807 
1808         wxGtkTextApplyTagsFromAttr( m_widget, m_buffer, style, &starti, &endi );
1809 
1810         return true;
1811     }
1812     //else: single line text controls don't support styles
1813 
1814     return false;
1815 }
1816 
GetStyle(long position,wxTextAttr & style)1817 bool wxTextCtrl::GetStyle(long position, wxTextAttr& style)
1818 {
1819     if ( !IsMultiLine() )
1820     {
1821         // no styles for GtkEntry
1822         return false;
1823     }
1824 
1825     gint l = gtk_text_buffer_get_char_count( m_buffer );
1826 
1827     wxCHECK_MSG( position >= 0 && position <= l, false,
1828                  wxT("invalid range in wxTextCtrl::GetStyle") );
1829 
1830     GtkTextIter positioni;
1831     gtk_text_buffer_get_iter_at_offset(m_buffer, &positioni, position);
1832 
1833     // Obtain a copy of the default attributes
1834     GtkTextAttributes * const
1835         pattr = gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(m_text));
1836     wxON_BLOCK_EXIT1(gtk_text_attributes_unref, pattr);
1837 
1838     // And query GTK for the attributes at the given position using it as base
1839     if ( !gtk_text_iter_get_attributes(&positioni, pattr) )
1840     {
1841         style = m_defaultStyle;
1842     }
1843     else // have custom attributes
1844     {
1845         style.SetBackgroundColour(pattr->appearance.bg_color);
1846         style.SetTextColour(pattr->appearance.fg_color);
1847 
1848         const wxGtkString
1849             pangoFontString(pango_font_description_to_string(pattr->font));
1850 
1851         wxFont font;
1852         if ( font.SetNativeFontInfo(wxString(pangoFontString)) )
1853             style.SetFont(font);
1854 
1855         // TODO: set alignment, tabs and indents
1856     }
1857 
1858     return true;
1859 }
1860 
DoApplyWidgetStyle(GtkRcStyle * style)1861 void wxTextCtrl::DoApplyWidgetStyle(GtkRcStyle *style)
1862 {
1863     GTKApplyStyle(m_text, style);
1864 }
1865 
OnCut(wxCommandEvent & WXUNUSED (event))1866 void wxTextCtrl::OnCut(wxCommandEvent& WXUNUSED(event))
1867 {
1868     Cut();
1869 }
1870 
OnCopy(wxCommandEvent & WXUNUSED (event))1871 void wxTextCtrl::OnCopy(wxCommandEvent& WXUNUSED(event))
1872 {
1873     Copy();
1874 }
1875 
OnPaste(wxCommandEvent & WXUNUSED (event))1876 void wxTextCtrl::OnPaste(wxCommandEvent& WXUNUSED(event))
1877 {
1878     Paste();
1879 }
1880 
OnUndo(wxCommandEvent & WXUNUSED (event))1881 void wxTextCtrl::OnUndo(wxCommandEvent& WXUNUSED(event))
1882 {
1883     Undo();
1884 }
1885 
OnRedo(wxCommandEvent & WXUNUSED (event))1886 void wxTextCtrl::OnRedo(wxCommandEvent& WXUNUSED(event))
1887 {
1888     Redo();
1889 }
1890 
OnUpdateCut(wxUpdateUIEvent & event)1891 void wxTextCtrl::OnUpdateCut(wxUpdateUIEvent& event)
1892 {
1893     event.Enable( CanCut() );
1894 }
1895 
OnUpdateCopy(wxUpdateUIEvent & event)1896 void wxTextCtrl::OnUpdateCopy(wxUpdateUIEvent& event)
1897 {
1898     event.Enable( CanCopy() );
1899 }
1900 
OnUpdatePaste(wxUpdateUIEvent & event)1901 void wxTextCtrl::OnUpdatePaste(wxUpdateUIEvent& event)
1902 {
1903     event.Enable( CanPaste() );
1904 }
1905 
OnUpdateUndo(wxUpdateUIEvent & event)1906 void wxTextCtrl::OnUpdateUndo(wxUpdateUIEvent& event)
1907 {
1908     event.Enable( CanUndo() );
1909 }
1910 
OnUpdateRedo(wxUpdateUIEvent & event)1911 void wxTextCtrl::OnUpdateRedo(wxUpdateUIEvent& event)
1912 {
1913     event.Enable( CanRedo() );
1914 }
1915 
DoGetBestSize() const1916 wxSize wxTextCtrl::DoGetBestSize() const
1917 {
1918     return DoGetSizeFromTextSize(80);
1919 }
1920 
DoGetSizeFromTextSize(int xlen,int ylen) const1921 wxSize wxTextCtrl::DoGetSizeFromTextSize(int xlen, int ylen) const
1922 {
1923     wxASSERT_MSG( m_widget, wxS("GetSizeFromTextSize called before creation") );
1924 
1925     wxSize tsize(xlen, 0);
1926     int cHeight = GetCharHeight();
1927 
1928     if ( IsSingleLine() )
1929     {
1930         if ( HasFlag(wxBORDER_NONE) )
1931         {
1932             tsize.y = cHeight;
1933 #ifdef __WXGTK3__
1934             tsize.IncBy(9, 0);
1935 #else
1936             tsize.IncBy(4, 0);
1937 #endif // GTK3
1938         }
1939         else
1940         {
1941             // default height
1942             tsize.y = GTKGetPreferredSize(m_widget).y;
1943             // Add the margins we have previously set, but only the horizontal border
1944             // as vertical one has been taken account at GTKGetPreferredSize().
1945             // Also get other GTK+ margins.
1946             tsize.IncBy( GTKGetEntryMargins(GetEntry()).x, 0);
1947         }
1948     }
1949 
1950     //multiline
1951     else
1952     {
1953         // add space for vertical scrollbar
1954         if ( m_scrollBar[1] && !(m_windowStyle & wxTE_NO_VSCROLL) )
1955             tsize.IncBy(GTKGetPreferredSize(GTK_WIDGET(m_scrollBar[1])).x + 3, 0);
1956 
1957         // height
1958         tsize.y = cHeight;
1959         if ( ylen <= 0 )
1960         {
1961             tsize.y = 1 + cHeight * wxMax(wxMin(GetNumberOfLines(), 10), 2);
1962             // add space for horizontal scrollbar
1963             if ( m_scrollBar[0] && (m_windowStyle & wxHSCROLL) )
1964                 tsize.IncBy(0, GTKGetPreferredSize(GTK_WIDGET(m_scrollBar[0])).y + 3);
1965         }
1966 
1967         if ( !HasFlag(wxBORDER_NONE) )
1968         {
1969             // hardcode borders, margins, etc
1970             tsize.IncBy(5, 4);
1971         }
1972     }
1973 
1974     // Perhaps the user wants something different from CharHeight, or ylen
1975     // is used as the height of a multiline text.
1976     if ( ylen > 0 )
1977         tsize.IncBy(0, ylen - cHeight);
1978 
1979     return tsize;
1980 }
1981 
1982 
1983 // ----------------------------------------------------------------------------
1984 // freeze/thaw
1985 // ----------------------------------------------------------------------------
1986 
DoFreeze()1987 void wxTextCtrl::DoFreeze()
1988 {
1989     wxCHECK_RET(m_text != NULL, wxT("invalid text ctrl"));
1990 
1991     GTKFreezeWidget(m_text);
1992     if (m_widget != m_text)
1993         GTKFreezeWidget(m_widget);
1994 
1995     if ( HasFlag(wxTE_MULTILINE) )
1996     {
1997         // removing buffer dramatically speeds up insertion:
1998         g_object_ref(m_buffer);
1999         GtkTextBuffer* buf_new = gtk_text_buffer_new(NULL);
2000         gtk_text_view_set_buffer(GTK_TEXT_VIEW(m_text), buf_new);
2001         // gtk_text_view_set_buffer adds its own reference
2002         g_object_unref(buf_new);
2003         // These marks should be deleted when the buffer is changed,
2004         // but they are not (in GTK+ up to at least 3.0.1).
2005         // Otherwise these anonymous marks start to build up in the buffer,
2006         // and Freeze takes longer and longer each time it is called.
2007         if (m_anonymousMarkList)
2008         {
2009             for (GSList* item = m_anonymousMarkList; item; item = item->next)
2010             {
2011                 GtkTextMark* mark = static_cast<GtkTextMark*>(item->data);
2012                 if (GTK_IS_TEXT_MARK(mark) && !gtk_text_mark_get_deleted(mark))
2013                     gtk_text_buffer_delete_mark(m_buffer, mark);
2014             }
2015             g_slist_free(m_anonymousMarkList);
2016             m_anonymousMarkList = NULL;
2017         }
2018     }
2019 }
2020 
DoThaw()2021 void wxTextCtrl::DoThaw()
2022 {
2023     if ( HasFlag(wxTE_MULTILINE) )
2024     {
2025         // reattach buffer:
2026         gulong sig_id = g_signal_connect(m_buffer, "mark_set", G_CALLBACK(mark_set), &m_anonymousMarkList);
2027         gtk_text_view_set_buffer(GTK_TEXT_VIEW(m_text), m_buffer);
2028         g_object_unref(m_buffer);
2029         g_signal_handler_disconnect(m_buffer, sig_id);
2030 
2031         if (m_showPositionOnThaw != NULL)
2032         {
2033             gtk_text_view_scroll_mark_onscreen(
2034                 GTK_TEXT_VIEW(m_text), m_showPositionOnThaw);
2035             m_showPositionOnThaw = NULL;
2036         }
2037     }
2038 
2039     GTKThawWidget(m_text);
2040     if (m_widget != m_text)
2041         GTKThawWidget(m_widget);
2042 }
2043 
2044 // ----------------------------------------------------------------------------
2045 // wxTextUrlEvent passing if style & wxTE_AUTO_URL
2046 // ----------------------------------------------------------------------------
2047 
2048 // FIXME: when dragging on a link the sample gets an "Unknown event".
2049 // This might be an excessive event from us or a buggy wxMouseEvent::Moving() or
2050 // a buggy sample, or something else
OnUrlMouseEvent(wxMouseEvent & event)2051 void wxTextCtrl::OnUrlMouseEvent(wxMouseEvent& event)
2052 {
2053     event.Skip();
2054     if( !HasFlag(wxTE_AUTO_URL) )
2055         return;
2056 
2057     gint x, y;
2058     GtkTextIter start, end;
2059     GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(m_buffer),
2060                                                 "wxUrl");
2061 
2062     gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(m_text), GTK_TEXT_WINDOW_WIDGET,
2063                                           event.GetX(), event.GetY(), &x, &y);
2064 
2065     gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(m_text), &end, x, y);
2066     if (!gtk_text_iter_has_tag(&end, tag))
2067     {
2068         SetCursor(wxCursor());
2069         return;
2070     }
2071 
2072     SetCursor(wxCursor(wxCURSOR_HAND));
2073 
2074     start = end;
2075     if(!gtk_text_iter_begins_tag(&start, tag))
2076         gtk_text_iter_backward_to_tag_toggle(&start, tag);
2077     if(!gtk_text_iter_ends_tag(&end, tag))
2078         gtk_text_iter_forward_to_tag_toggle(&end, tag);
2079 
2080     // Native context menu is probably not desired on an URL.
2081     // Consider making this dependent on ProcessEvent(wxTextUrlEvent) return value
2082     if(event.GetEventType() == wxEVT_RIGHT_DOWN)
2083         event.Skip(false);
2084 
2085     wxTextUrlEvent url_event(m_windowId, event,
2086                              gtk_text_iter_get_offset(&start),
2087                              gtk_text_iter_get_offset(&end));
2088 
2089     InitCommandEvent(url_event);
2090     // Is that a good idea? Seems not (pleasure with gtk_text_view_start_selection_drag)
2091     //event.Skip(!HandleWindowEvent(url_event));
2092     HandleWindowEvent(url_event);
2093 }
2094 
GTKProcessEvent(wxEvent & event) const2095 bool wxTextCtrl::GTKProcessEvent(wxEvent& event) const
2096 {
2097     bool rc = wxTextCtrlBase::GTKProcessEvent(event);
2098 
2099     // GtkTextView starts a drag operation when left mouse button is pressed
2100     // and ends it when it is released and if it doesn't get the release event
2101     // the next click on a control results in an assertion failure inside
2102     // gtk_text_view_start_selection_drag() which simply *kills* the program
2103     // without anything we can do about it, so always let GTK+ have this event
2104     return rc && (IsSingleLine() || event.GetEventType() != wxEVT_LEFT_UP);
2105 }
2106 
2107 // static
2108 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))2109 wxTextCtrl::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
2110 {
2111     return GetDefaultAttributesFromGTKWidget(gtk_entry_new(), true);
2112 }
2113 
2114 #endif // wxUSE_TEXTCTRL
2115