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