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