X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/a5125dc6edfdf0bc1963b8c797da3c874e864549..7d6a4d96961eac84d05db8bb24c64d39003f6e54:/src/gtk/textctrl.cpp diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp index e30c23e4da..eedc7c026f 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -22,6 +22,7 @@ #include "wx/math.h" #endif +#include "wx/scopeguard.h" #include "wx/strconv.h" #include "wx/fontutil.h" // for wxNativeFontInfo (GetNativeFontInfo()) @@ -29,7 +30,9 @@ #include #include +#include #include "wx/gtk/private.h" +#include "wx/gtk/private/gtk2-compat.h" // ---------------------------------------------------------------------------- // helpers @@ -109,6 +112,18 @@ static void wxGtkTextApplyTagsFromAttr(GtkWidget *text, NULL ); gtk_text_buffer_apply_tag (text_buffer, tag, start, end); } + if ( font.GetStrikethrough() ) + { + g_snprintf(buf, sizeof(buf), "WXFONTSTRIKETHROUGH"); + tag = gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table( text_buffer ), + buf ); + if (!tag) + tag = gtk_text_buffer_create_tag( text_buffer, buf, + "strikethrough-set", TRUE, + "strikethrough", TRUE, + NULL ); + gtk_text_buffer_apply_tag (text_buffer, tag, start, end); + } } if (attr.HasTextColour()) @@ -149,30 +164,34 @@ static void wxGtkTextApplyTagsFromAttr(GtkWidget *text, gtk_text_iter_get_line(start) ); gtk_text_iter_forward_line(¶_end); - wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXALIGNMENT", start, end); + wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXALIGNMENT", ¶_start, ¶_end); GtkJustification align; switch (attr.GetAlignment()) { - default: - align = GTK_JUSTIFY_LEFT; - break; case wxTEXT_ALIGNMENT_RIGHT: align = GTK_JUSTIFY_RIGHT; break; case wxTEXT_ALIGNMENT_CENTER: align = GTK_JUSTIFY_CENTER; break; + case wxTEXT_ALIGNMENT_JUSTIFIED: +#ifdef __WXGTK3__ + align = GTK_JUSTIFY_FILL; + break; +#elif GTK_CHECK_VERSION(2,11,0) // gtk+ doesn't support justify before gtk+-2.11.0 with pango-1.17 being available // (but if new enough pango isn't available it's a mere gtk warning) -#if GTK_CHECK_VERSION(2,11,0) - case wxTEXT_ALIGNMENT_JUSTIFIED: if (!gtk_check_version(2,11,0)) + { align = GTK_JUSTIFY_FILL; - else - align = GTK_JUSTIFY_LEFT; - break; + break; + } + // fallthrough #endif + default: + align = GTK_JUSTIFY_LEFT; + break; } g_snprintf(buf, sizeof(buf), "WXALIGNMENT %d", align); @@ -195,7 +214,7 @@ static void wxGtkTextApplyTagsFromAttr(GtkWidget *text, gtk_text_iter_get_line(start) ); gtk_text_iter_forward_line(¶_end); - wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXINDENT", start, end); + wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXINDENT", ¶_start, ¶_end); // Convert indent from 1/10th of a mm into pixels float factor = @@ -239,14 +258,14 @@ static void wxGtkTextApplyTagsFromAttr(GtkWidget *text, gtk_text_iter_get_line(start) ); gtk_text_iter_forward_line(¶_end); - wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXTABS", start, end); + wxGtkTextRemoveTagsWithPrefix(text_buffer, "WXTABS", ¶_start, ¶_end); const wxArrayInt& tabs = attr.GetTabs(); - wxString tagname = _T("WXTABS"); + wxString tagname = wxT("WXTABS"); g_snprintf(buf, sizeof(buf), "WXTABS"); for (size_t i = 0; i < tabs.GetCount(); i++) - tagname += wxString::Format(_T(" %d"), tabs[i]); + tagname += wxString::Format(wxT(" %d"), tabs[i]); const wxWX2MBbuf buftag = tagname.utf8_str(); @@ -350,7 +369,7 @@ extern "C" { static void au_check_word( GtkTextIter *s, GtkTextIter *e ) { - static const char *URIPrefixes[] = + static const char *const URIPrefixes[] = { "http://", "ftp://", @@ -390,7 +409,7 @@ au_check_word( GtkTextIter *s, GtkTextIter *e ) for( n = 0; n < WXSIZEOF(URIPrefixes); ++n ) { prefix_len = strlen(URIPrefixes[n]); - if((len > prefix_len) && !strncasecmp(text, URIPrefixes[n], prefix_len)) + if((len > prefix_len) && !wxStrnicmp(text, URIPrefixes[n], prefix_len)) break; } @@ -494,6 +513,27 @@ au_delete_range_callback(GtkTextBuffer * WXUNUSED(buffer), } } +//----------------------------------------------------------------------------- +// "populate_popup" from text control and "unmap" from its poup menu +//----------------------------------------------------------------------------- + +extern "C" { +static void +gtk_textctrl_popup_unmap( GtkMenu *WXUNUSED(menu), wxTextCtrl* win ) +{ + win->GTKEnableFocusOutEvent(); +} +} + +extern "C" { +static void +gtk_textctrl_populate_popup( GtkEntry *WXUNUSED(entry), GtkMenu *menu, wxTextCtrl *win ) +{ + win->GTKDisableFocusOutEvent(); + + g_signal_connect (menu, "unmap", G_CALLBACK (gtk_textctrl_popup_unmap), win ); +} +} //----------------------------------------------------------------------------- // "changed" @@ -501,7 +541,7 @@ au_delete_range_callback(GtkTextBuffer * WXUNUSED(buffer), extern "C" { static void -gtk_text_changed_callback( GtkWidget * WXUNUSED(widget), wxTextCtrl *win ) +gtk_text_changed_callback( GtkWidget *WXUNUSED(widget), wxTextCtrl *win ) { if ( win->IgnoreTextUpdate() ) return; @@ -558,10 +598,20 @@ gtk_paste_clipboard_callback( GtkWidget *widget, wxTextCtrl *win ) } //----------------------------------------------------------------------------- -// wxTextCtrl +// "mark_set" //----------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl, wxTextCtrlBase) +extern "C" { +static void mark_set(GtkTextBuffer*, GtkTextIter*, GtkTextMark* mark, GSList** markList) +{ + if (gtk_text_mark_get_name(mark) == NULL) + *markList = g_slist_prepend(*markList, mark); +} +} + +//----------------------------------------------------------------------------- +// wxTextCtrl +//----------------------------------------------------------------------------- BEGIN_EVENT_TABLE(wxTextCtrl, wxTextCtrlBase) EVT_CHAR(wxTextCtrl::OnChar) @@ -600,16 +650,13 @@ void wxTextCtrl::Init() m_text = NULL; m_showPositionOnThaw = NULL; - m_gdkHandCursor = NULL; - m_gdkXTermCursor = NULL; + m_anonymousMarkList = NULL; } wxTextCtrl::~wxTextCtrl() { - if(m_gdkHandCursor) - gdk_cursor_unref(m_gdkHandCursor); - if(m_gdkXTermCursor) - gdk_cursor_unref(m_gdkXTermCursor); + if (m_anonymousMarkList) + g_slist_free(m_anonymousMarkList); } wxTextCtrl::wxTextCtrl( wxWindow *parent, @@ -646,10 +693,13 @@ bool wxTextCtrl::Create( wxWindow *parent, if (multi_line) { + m_buffer = gtk_text_buffer_new(NULL); + gulong sig_id = g_signal_connect(m_buffer, "mark_set", G_CALLBACK(mark_set), &m_anonymousMarkList); // Create view - m_text = gtk_text_view_new(); - - m_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(m_text) ); + m_text = gtk_text_view_new_with_buffer(m_buffer); + // gtk_text_view_set_buffer adds its own reference + g_object_unref(m_buffer); + g_signal_handler_disconnect(m_buffer, sig_id); // create "ShowPosition" marker GtkTextIter iter; @@ -664,24 +714,27 @@ bool wxTextCtrl::Create( wxWindow *parent, ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC ); // for ScrollLines/Pages - m_scrollBar[1] = (GtkRange*)((GtkScrolledWindow*)m_widget)->vscrollbar; + m_scrollBar[1] = GTK_RANGE(gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(m_widget))); // Insert view into scrolled window gtk_container_add( GTK_CONTAINER(m_widget), m_text ); GTKSetWrapMode(); - GtkScrolledWindowSetBorder(m_widget, style); + GTKScrolledWindowSetBorder(m_widget, style); gtk_widget_add_events( GTK_WIDGET(m_text), GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK ); - GTK_WIDGET_UNSET_FLAGS( m_widget, GTK_CAN_FOCUS ); + gtk_widget_set_can_focus(m_widget, FALSE); } else { // a single-line text control: no need for scrollbars m_widget = m_text = gtk_entry_new(); + // work around probable bug in GTK+ 2.18 when calling WriteText on a + // new, empty control, see http://trac.wxwidgets.org/ticket/11409 + gtk_entry_get_text((GtkEntry*)m_text); if (style & wxNO_BORDER) g_object_set (m_text, "has-frame", FALSE, NULL); @@ -712,6 +765,11 @@ bool wxTextCtrl::Create( wxWindow *parent, G_CALLBACK (gtk_text_changed_callback), this); } + // Catch to disable focus out handling + g_signal_connect (m_text, "populate_popup", + G_CALLBACK (gtk_textctrl_populate_popup), + this); + if (!value.empty()) { SetValue( value ); @@ -733,11 +791,9 @@ bool wxTextCtrl::Create( wxWindow *parent, if (style & wxTE_AUTO_URL) { GtkTextIter start, end; - m_gdkHandCursor = gdk_cursor_new(GDK_HAND2); - m_gdkXTermCursor = gdk_cursor_new(GDK_XTERM); // We create our wxUrl tag here for slight efficiency gain - we - // don't have to check for the tag existance in callbacks, + // don't have to check for the tag existence in callbacks, // hereby it's guaranteed to exist. gtk_text_buffer_create_tag(m_buffer, "wxUrl", "foreground", "blue", @@ -792,6 +848,11 @@ GtkEditable *wxTextCtrl::GetEditable() const return GTK_EDITABLE(m_text); } +GtkEntry *wxTextCtrl::GetEntry() const +{ + return GTK_ENTRY(m_text); +} + // ---------------------------------------------------------------------------- // flags handling // ---------------------------------------------------------------------------- @@ -1057,13 +1118,19 @@ void wxTextCtrl::WriteText( const wxString &text ) // Insert the text wxGtkTextInsert( m_text, m_buffer, m_defaultStyle, buffer ); - GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(m_widget) ); // Scroll to cursor, but only if scrollbar thumb is at the very bottom // won't work when frozen, text view is not using m_buffer then - if (!IsFrozen() && wxIsSameDouble(adj->value, adj->upper - adj->page_size)) + if (!IsFrozen()) { - gtk_text_view_scroll_to_mark( GTK_TEXT_VIEW(m_text), - gtk_text_buffer_get_insert( m_buffer ), 0.0, FALSE, 0.0, 1.0 ); + GtkAdjustment* adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(m_widget)); + const double value = gtk_adjustment_get_value(adj); + const double upper = gtk_adjustment_get_upper(adj); + const double page_size = gtk_adjustment_get_page_size(adj); + if (wxIsSameDouble(value, upper - page_size)) + { + gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(m_text), + gtk_text_buffer_get_insert(m_buffer), 0, false, 0, 1); + } } } @@ -1116,7 +1183,7 @@ bool wxTextCtrl::PositionToXY(long pos, long *x, long *y ) const } else // single line control { - if ( pos <= GTK_ENTRY(m_text)->text_length ) + if (pos <= gtk_entry_get_text_length(GTK_ENTRY(m_text))) { if ( y ) *y = 0; @@ -1169,6 +1236,37 @@ int wxTextCtrl::GetLineLength(long lineNo) const } } +wxPoint wxTextCtrl::DoPositionToCoords(long pos) const +{ + if ( !IsMultiLine() ) + { + // Single line text entry (GtkTextEntry) doesn't have support for + // getting the coordinates for the given offset. Perhaps we could + // find them ourselves by using GetTextExtent() but for now just leave + // it unimplemented, this function is more useful for multiline + // controls anyhow. + return wxDefaultPosition; + } + + // Window coordinates for the given position is calculated by getting + // the buffer coordinates and converting them to window coordinates. + GtkTextView *textview = GTK_TEXT_VIEW(m_text); + + GtkTextIter iter; + gtk_text_buffer_get_iter_at_offset(m_buffer, &iter, pos); + + GdkRectangle bufferCoords; + gtk_text_view_get_iter_location(textview, &iter, &bufferCoords); + + gint winCoordX = 0, + winCoordY = 0; + gtk_text_view_buffer_to_window_coords(textview, GTK_TEXT_WINDOW_WIDGET, + bufferCoords.x, bufferCoords.y, + &winCoordX, &winCoordY); + + return wxPoint(winCoordX, winCoordY); +} + int wxTextCtrl::GetNumberOfLines() const { if ( IsMultiLine() ) @@ -1226,6 +1324,7 @@ bool wxTextCtrl::Enable( bool enable ) } gtk_widget_set_sensitive( m_text, enable ); + SetCursor(enable ? wxCursor(wxCURSOR_IBEAM) : wxCursor()); return true; } @@ -1239,7 +1338,7 @@ void wxTextCtrl::OnEnabled(bool WXUNUSED(enable)) // disabled and enabled mode, or we end up with a different colour under the // text. wxColour oldColour = GetBackgroundColour(); - if (oldColour.Ok()) + if (oldColour.IsOk()) { // Need to set twice or it'll optimize the useful stuff out if (oldColour == * wxWHITE) @@ -1306,20 +1405,19 @@ void wxTextCtrl::SetSelection( long from, long to ) { wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") ); - if (from == -1 && to == -1) - { - from = 0; - to = GetValue().length(); - } - if ( IsMultiLine() ) { + if (from == -1 && to == -1) + { + from = 0; + to = GetValue().length(); + } + GtkTextIter fromi, toi; gtk_text_buffer_get_iter_at_offset( m_buffer, &fromi, from ); gtk_text_buffer_get_iter_at_offset( m_buffer, &toi, to ); - gtk_text_buffer_place_cursor( m_buffer, &toi ); - gtk_text_buffer_move_mark_by_name( m_buffer, "selection_bound", &fromi ); + gtk_text_buffer_select_range( m_buffer, &fromi, &toi ); } else // single line { @@ -1507,7 +1605,7 @@ bool wxTextCtrl::IsEditable() const if ( IsMultiLine() ) { - return gtk_text_view_get_editable(GTK_TEXT_VIEW(m_text)); + return gtk_text_view_get_editable(GTK_TEXT_VIEW(m_text)) != 0; } else { @@ -1553,7 +1651,12 @@ GdkWindow *wxTextCtrl::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const } else { - return GTK_ENTRY(m_text)->text_area; +#ifdef __WXGTK3__ + // no access to internal GdkWindows + return NULL; +#else + return gtk_entry_get_text_window(GTK_ENTRY(m_text)); +#endif } } @@ -1587,7 +1690,7 @@ void wxTextCtrl::ChangeFontGlobally() // // TODO: it can be implemented much more efficiently for GTK2 wxASSERT_MSG( IsMultiLine(), - _T("shouldn't be called for single line controls") ); + wxT("shouldn't be called for single line controls") ); wxString value = GetValue(); if ( !value.empty() ) @@ -1617,7 +1720,7 @@ bool wxTextCtrl::SetBackgroundColour( const wxColour &colour ) if ( !wxControl::SetBackgroundColour( colour ) ) return false; - if (!m_backgroundColour.Ok()) + if (!m_backgroundColour.IsOk()) return false; // change active background color too @@ -1639,7 +1742,7 @@ bool wxTextCtrl::SetStyle( long start, long end, const wxTextAttr& style ) gint l = gtk_text_buffer_get_char_count( m_buffer ); wxCHECK_MSG( start >= 0 && end <= l, false, - _T("invalid range in wxTextCtrl::SetStyle") ); + wxT("invalid range in wxTextCtrl::SetStyle") ); GtkTextIter starti, endi; gtk_text_buffer_get_iter_at_offset( m_buffer, &starti, start ); @@ -1654,9 +1757,53 @@ bool wxTextCtrl::SetStyle( long start, long end, const wxTextAttr& style ) return false; } +bool wxTextCtrl::GetStyle(long position, wxTextAttr& style) +{ + if ( !IsMultiLine() ) + { + // no styles for GtkEntry + return false; + } + + gint l = gtk_text_buffer_get_char_count( m_buffer ); + + wxCHECK_MSG( position >= 0 && position <= l, false, + wxT("invalid range in wxTextCtrl::GetStyle") ); + + GtkTextIter positioni; + gtk_text_buffer_get_iter_at_offset(m_buffer, &positioni, position); + + // Obtain a copy of the default attributes + GtkTextAttributes * const + pattr = gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(m_text)); + wxON_BLOCK_EXIT1(gtk_text_attributes_unref, pattr); + + // And query GTK for the attributes at the given position using it as base + if ( !gtk_text_iter_get_attributes(&positioni, pattr) ) + { + style = m_defaultStyle; + } + else // have custom attributes + { + style.SetBackgroundColour(pattr->appearance.bg_color); + style.SetTextColour(pattr->appearance.fg_color); + + const wxGtkString + pangoFontString(pango_font_description_to_string(pattr->font)); + + wxFont font; + if ( font.SetNativeFontInfo(wxString(pangoFontString)) ) + style.SetFont(font); + + // TODO: set alignment, tabs and indents + } + + return true; +} + void wxTextCtrl::DoApplyWidgetStyle(GtkRcStyle *style) { - gtk_widget_modify_style(m_text, style); + GTKApplyStyle(m_text, style); } void wxTextCtrl::OnCut(wxCommandEvent& WXUNUSED(event)) @@ -1735,16 +1882,24 @@ void wxTextCtrl::DoFreeze() // removing buffer dramatically speeds up insertion: g_object_ref(m_buffer); GtkTextBuffer* buf_new = gtk_text_buffer_new(NULL); - GtkTextMark* mark = GTK_TEXT_VIEW(m_text)->first_para_mark; gtk_text_view_set_buffer(GTK_TEXT_VIEW(m_text), buf_new); // gtk_text_view_set_buffer adds its own reference g_object_unref(buf_new); - // This mark should be deleted when the buffer is changed, - // but it's not (in GTK+ up to at least 2.10.6). + // These marks should be deleted when the buffer is changed, + // but they are not (in GTK+ up to at least 3.0.1). // Otherwise these anonymous marks start to build up in the buffer, // and Freeze takes longer and longer each time it is called. - if (GTK_IS_TEXT_MARK(mark) && !gtk_text_mark_get_deleted(mark)) - gtk_text_buffer_delete_mark(m_buffer, mark); + if (m_anonymousMarkList) + { + for (GSList* item = m_anonymousMarkList; item; item = item->next) + { + GtkTextMark* mark = static_cast(item->data); + if (GTK_IS_TEXT_MARK(mark) && !gtk_text_mark_get_deleted(mark)) + gtk_text_buffer_delete_mark(m_buffer, mark); + } + g_slist_free(m_anonymousMarkList); + m_anonymousMarkList = NULL; + } } } @@ -1753,8 +1908,10 @@ void wxTextCtrl::DoThaw() if ( HasFlag(wxTE_MULTILINE) ) { // reattach buffer: + gulong sig_id = g_signal_connect(m_buffer, "mark_set", G_CALLBACK(mark_set), &m_anonymousMarkList); gtk_text_view_set_buffer(GTK_TEXT_VIEW(m_text), m_buffer); g_object_unref(m_buffer); + g_signal_handler_disconnect(m_buffer, sig_id); if (m_showPositionOnThaw != NULL) { @@ -1794,13 +1951,11 @@ void wxTextCtrl::OnUrlMouseEvent(wxMouseEvent& event) gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(m_text), &end, x, y); if (!gtk_text_iter_has_tag(&end, tag)) { - gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(m_text), - GTK_TEXT_WINDOW_TEXT), m_gdkXTermCursor); + SetCursor(wxCursor(wxCURSOR_IBEAM)); return; } - gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(m_text), - GTK_TEXT_WINDOW_TEXT), m_gdkHandCursor); + SetCursor(wxCursor(wxCURSOR_HAND)); start = end; if(!gtk_text_iter_begins_tag(&start, tag)) @@ -1809,7 +1964,7 @@ void wxTextCtrl::OnUrlMouseEvent(wxMouseEvent& event) gtk_text_iter_forward_to_tag_toggle(&end, tag); // Native context menu is probably not desired on an URL. - // Consider making this dependant on ProcessEvent(wxTextUrlEvent) return value + // Consider making this dependent on ProcessEvent(wxTextUrlEvent) return value if(event.GetEventType() == wxEVT_RIGHT_DOWN) event.Skip(false);