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 ¶_start,
213 gtk_text_iter_get_line(start) );
214 gtk_text_iter_forward_line(¶_end);
215
216 wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXALIGNMENT", ¶_start, ¶_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, ¶_start, ¶_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 ¶_start,
263 gtk_text_iter_get_line(start) );
264 gtk_text_iter_forward_line(¶_end);
265
266 wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXINDENT", ¶_start, ¶_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, ¶_start, ¶_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 ¶_start,
317 gtk_text_iter_get_line(start) );
318 gtk_text_iter_forward_line(¶_end);
319
320 wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXTABS", ¶_start, ¶_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, ¶_start, ¶_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