wxTE_AUTO_URL for wxGTK2 from Mart R. [patch 1126182]
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@32159 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -32,6 +32,8 @@ public:
|
|||||||
const wxValidator& validator = wxDefaultValidator,
|
const wxValidator& validator = wxDefaultValidator,
|
||||||
const wxString &name = wxTextCtrlNameStr);
|
const wxString &name = wxTextCtrlNameStr);
|
||||||
|
|
||||||
|
~wxTextCtrl();
|
||||||
|
|
||||||
bool Create(wxWindow *parent,
|
bool Create(wxWindow *parent,
|
||||||
wxWindowID id,
|
wxWindowID id,
|
||||||
const wxString &value = wxEmptyString,
|
const wxString &value = wxEmptyString,
|
||||||
@@ -224,6 +226,11 @@ private:
|
|||||||
|
|
||||||
// number of calls to Freeze() minus number of calls to Thaw()
|
// number of calls to Freeze() minus number of calls to Thaw()
|
||||||
unsigned int m_frozenness;
|
unsigned int m_frozenness;
|
||||||
|
|
||||||
|
// For wxTE_AUTO_URL
|
||||||
|
void OnUrlMouseEvent(wxMouseEvent&);
|
||||||
|
GdkCursor *m_gdkHandCursor;
|
||||||
|
GdkCursor *m_gdkXTermCursor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
|
@@ -32,6 +32,8 @@ public:
|
|||||||
const wxValidator& validator = wxDefaultValidator,
|
const wxValidator& validator = wxDefaultValidator,
|
||||||
const wxString &name = wxTextCtrlNameStr);
|
const wxString &name = wxTextCtrlNameStr);
|
||||||
|
|
||||||
|
~wxTextCtrl();
|
||||||
|
|
||||||
bool Create(wxWindow *parent,
|
bool Create(wxWindow *parent,
|
||||||
wxWindowID id,
|
wxWindowID id,
|
||||||
const wxString &value = wxEmptyString,
|
const wxString &value = wxEmptyString,
|
||||||
@@ -224,6 +226,11 @@ private:
|
|||||||
|
|
||||||
// number of calls to Freeze() minus number of calls to Thaw()
|
// number of calls to Freeze() minus number of calls to Thaw()
|
||||||
unsigned int m_frozenness;
|
unsigned int m_frozenness;
|
||||||
|
|
||||||
|
// For wxTE_AUTO_URL
|
||||||
|
void OnUrlMouseEvent(wxMouseEvent&);
|
||||||
|
GdkCursor *m_gdkHandCursor;
|
||||||
|
GdkCursor *m_gdkXTermCursor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
|
@@ -106,7 +106,7 @@ const wxTextCoord wxInvalidTextCoord = -2;
|
|||||||
// automatically detect the URLs and generate the events when mouse is
|
// automatically detect the URLs and generate the events when mouse is
|
||||||
// moved/clicked over an URL
|
// moved/clicked over an URL
|
||||||
//
|
//
|
||||||
// this is for Win32 richedit controls only so far
|
// this is for Win32 richedit and wxGTK2 multiline controls only so far
|
||||||
#define wxTE_AUTO_URL 0x1000
|
#define wxTE_AUTO_URL 0x1000
|
||||||
|
|
||||||
// by default, the Windows text control doesn't show the selection when it
|
// by default, the Windows text control doesn't show the selection when it
|
||||||
|
@@ -180,6 +180,197 @@ gtk_insert_text_callback(GtkEditable *editable,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
// Implementation of wxTE_AUTO_URL for wxGTK2 by Mart Raudsepp,
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_apply_tag_callback(GtkTextBuffer *buffer,
|
||||||
|
GtkTextTag *tag,
|
||||||
|
GtkTextIter *start,
|
||||||
|
GtkTextIter *end,
|
||||||
|
gpointer textctrl)
|
||||||
|
{
|
||||||
|
if(tag == gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl"))
|
||||||
|
g_signal_stop_emission_by_name(buffer, "apply_tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// GtkTextCharPredicates for gtk_text_iter_*_find_char
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_whitespace (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return g_unichar_isspace(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_non_whitespace (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return !g_unichar_isspace(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_nonpunct (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return !g_unichar_ispunct(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_nonpunct_or_slash (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return !g_unichar_ispunct(ch) || ch == '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Check for links between s and e and correct tags as necessary
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// This function should be made match better while being efficient at one point.
|
||||||
|
// Most probably with a row of regular expressions.
|
||||||
|
static void
|
||||||
|
au_check_word( GtkTextIter *s, GtkTextIter *e )
|
||||||
|
{
|
||||||
|
static const char *URIPrefixes[] =
|
||||||
|
{
|
||||||
|
"http://",
|
||||||
|
"ftp://",
|
||||||
|
"www.",
|
||||||
|
"ftp.",
|
||||||
|
"mailto://",
|
||||||
|
"https://",
|
||||||
|
"file://",
|
||||||
|
"nntp://",
|
||||||
|
"news://",
|
||||||
|
"telnet://",
|
||||||
|
"mms://",
|
||||||
|
"gopher://",
|
||||||
|
"prospero://",
|
||||||
|
"wais://",
|
||||||
|
};
|
||||||
|
|
||||||
|
GtkTextIter start = *s, end = *e;
|
||||||
|
GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
|
||||||
|
|
||||||
|
// Get our special link tag
|
||||||
|
GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
|
||||||
|
|
||||||
|
// Get rid of punctuation from beginning and end.
|
||||||
|
// Might want to move this to au_check_range if an improved link checking doesn't
|
||||||
|
// use some intelligent punctuation checking itself (beware of undesired iter modifications).
|
||||||
|
if(g_unichar_ispunct( gtk_text_iter_get_char( &start ) ) )
|
||||||
|
gtk_text_iter_forward_find_char( &start, pred_nonpunct, NULL, e );
|
||||||
|
|
||||||
|
gtk_text_iter_backward_find_char( &end, pred_nonpunct_or_slash, NULL, &start );
|
||||||
|
gtk_text_iter_forward_char(&end);
|
||||||
|
|
||||||
|
gchar* text = gtk_text_iter_get_text( &start, &end );
|
||||||
|
size_t len = strlen(text), prefix_len;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
for( n = 0; n < WXSIZEOF(URIPrefixes); ++n )
|
||||||
|
{
|
||||||
|
prefix_len = strlen(URIPrefixes[n]);
|
||||||
|
if((len > prefix_len) && !strncasecmp(text, URIPrefixes[n], prefix_len))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(n < WXSIZEOF(URIPrefixes))
|
||||||
|
{
|
||||||
|
gulong signal_id = g_signal_handler_find(buffer,
|
||||||
|
(GSignalMatchType) (G_SIGNAL_MATCH_FUNC),
|
||||||
|
0, 0, NULL,
|
||||||
|
(gpointer)au_apply_tag_callback, NULL);
|
||||||
|
|
||||||
|
g_signal_handler_block(buffer, signal_id);
|
||||||
|
gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
|
||||||
|
g_signal_handler_unblock(buffer, signal_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_check_range(GtkTextIter *s,
|
||||||
|
GtkTextIter *range_end)
|
||||||
|
{
|
||||||
|
GtkTextIter range_start = *s;
|
||||||
|
GtkTextIter word_end;
|
||||||
|
GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
|
||||||
|
GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
|
||||||
|
|
||||||
|
gtk_text_buffer_remove_tag(buffer, tag, s, range_end);
|
||||||
|
|
||||||
|
if(g_unichar_isspace(gtk_text_iter_get_char(&range_start)))
|
||||||
|
gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
|
||||||
|
|
||||||
|
while(!gtk_text_iter_equal(&range_start, range_end))
|
||||||
|
{
|
||||||
|
word_end = range_start;
|
||||||
|
gtk_text_iter_forward_find_char(&word_end, pred_whitespace, NULL, range_end);
|
||||||
|
|
||||||
|
// Now we should have a word delimited by range_start and word_end, correct link tags
|
||||||
|
au_check_word(&range_start, &word_end);
|
||||||
|
|
||||||
|
range_start = word_end;
|
||||||
|
gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// "insert-text" for GtkTextBuffer
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_insert_text_callback(GtkTextBuffer *buffer,
|
||||||
|
GtkTextIter *end,
|
||||||
|
gchar *text,
|
||||||
|
gint len,
|
||||||
|
wxTextCtrl *win)
|
||||||
|
{
|
||||||
|
if (!len || !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
GtkTextIter start = *end;
|
||||||
|
gtk_text_iter_backward_chars(&start, g_utf8_strlen(text, len));
|
||||||
|
|
||||||
|
GtkTextIter line_start = start;
|
||||||
|
GtkTextIter line_end = *end;
|
||||||
|
GtkTextIter words_start = start;
|
||||||
|
GtkTextIter words_end = *end;
|
||||||
|
|
||||||
|
gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(&start));
|
||||||
|
gtk_text_iter_forward_to_line_end(&line_end);
|
||||||
|
gtk_text_iter_backward_find_char(&words_start, pred_whitespace, NULL, &line_start);
|
||||||
|
gtk_text_iter_forward_find_char(&words_end, pred_whitespace, NULL, &line_end);
|
||||||
|
|
||||||
|
au_check_range(&words_start, &words_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// "delete-range" for GtkTextBuffer
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_delete_range_callback(GtkTextBuffer *buffer,
|
||||||
|
GtkTextIter *start,
|
||||||
|
GtkTextIter *end,
|
||||||
|
wxTextCtrl *win)
|
||||||
|
{
|
||||||
|
if( !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
GtkTextIter line_start = *start, line_end = *end;
|
||||||
|
|
||||||
|
gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(start));
|
||||||
|
gtk_text_iter_forward_to_line_end(&line_end);
|
||||||
|
gtk_text_iter_backward_find_char(start, pred_whitespace, NULL, &line_start);
|
||||||
|
gtk_text_iter_forward_find_char(end, pred_whitespace, NULL, &line_end);
|
||||||
|
|
||||||
|
au_check_range(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// "changed"
|
// "changed"
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -287,6 +478,18 @@ BEGIN_EVENT_TABLE(wxTextCtrl, wxControl)
|
|||||||
EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
|
EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
|
||||||
EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
|
EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
|
||||||
EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
|
EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
|
||||||
|
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
// wxTE_AUTO_URL wxTextUrl support. Currently only creates
|
||||||
|
// wxTextUrlEvent in the same cases as wxMSW, more can be added here.
|
||||||
|
EVT_MOTION (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_LEFT_DOWN (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_LEFT_UP (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_LEFT_DCLICK (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_RIGHT_DOWN (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_RIGHT_UP (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_RIGHT_DCLICK(wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
#endif
|
||||||
END_EVENT_TABLE()
|
END_EVENT_TABLE()
|
||||||
|
|
||||||
void wxTextCtrl::Init()
|
void wxTextCtrl::Init()
|
||||||
@@ -298,6 +501,18 @@ void wxTextCtrl::Init()
|
|||||||
m_vScrollbar = (GtkWidget *)NULL;
|
m_vScrollbar = (GtkWidget *)NULL;
|
||||||
#ifdef __WXGTK20__
|
#ifdef __WXGTK20__
|
||||||
m_frozenness = 0;
|
m_frozenness = 0;
|
||||||
|
m_gdkHandCursor = NULL;
|
||||||
|
m_gdkXTermCursor = NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
wxTextCtrl::~wxTextCtrl()
|
||||||
|
{
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
if(m_gdkHandCursor)
|
||||||
|
gdk_cursor_unref(m_gdkHandCursor);
|
||||||
|
if(m_gdkXTermCursor)
|
||||||
|
gdk_cursor_unref(m_gdkXTermCursor);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,6 +727,42 @@ bool wxTextCtrl::Create( wxWindow *parent,
|
|||||||
{
|
{
|
||||||
g_signal_connect( G_OBJECT(m_buffer), "changed",
|
g_signal_connect( G_OBJECT(m_buffer), "changed",
|
||||||
GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
|
GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
|
||||||
|
|
||||||
|
// .. and handle URLs on multi-line controls with wxTE_AUTO_URL style
|
||||||
|
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,
|
||||||
|
// hereby it's guaranteed to exist.
|
||||||
|
gtk_text_buffer_create_tag(m_buffer, "wxUrl",
|
||||||
|
"foreground", "blue",
|
||||||
|
"underline", PANGO_UNDERLINE_SINGLE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
// Check for URLs after each text change
|
||||||
|
g_signal_connect_after( G_OBJECT(m_buffer), "insert_text",
|
||||||
|
GTK_SIGNAL_FUNC(au_insert_text_callback), (gpointer)this);
|
||||||
|
g_signal_connect_after( G_OBJECT(m_buffer), "delete_range",
|
||||||
|
GTK_SIGNAL_FUNC(au_delete_range_callback), (gpointer)this);
|
||||||
|
|
||||||
|
// Block all wxUrl tag applying unless we do it ourselves, in which case we
|
||||||
|
// block this callback temporarily. This takes care of gtk+ internal
|
||||||
|
// gtk_text_buffer_insert_range* calls that would copy our URL tag otherwise,
|
||||||
|
// which is undesired because only a part of the URL might be copied.
|
||||||
|
// The insert-text signal emitted inside it will take care of newly formed
|
||||||
|
// or wholly copied URLs.
|
||||||
|
g_signal_connect( G_OBJECT(m_buffer), "apply_tag",
|
||||||
|
GTK_SIGNAL_FUNC(au_apply_tag_callback), NULL);
|
||||||
|
|
||||||
|
// Check for URLs in the initial string passed to Create
|
||||||
|
gtk_text_buffer_get_start_iter(m_buffer, &start);
|
||||||
|
gtk_text_buffer_get_end_iter(m_buffer, &end);
|
||||||
|
au_check_range(&start, &end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
@@ -1777,6 +2028,62 @@ void wxTextCtrl::Thaw()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// wxTextUrlEvent passing if style & wxTE_AUTO_URL
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
|
||||||
|
// FIXME: when dragging on a link the sample gets an "Unknown event".
|
||||||
|
// This might be an excessive event from us or a buggy wxMouseEvent::Moving() or
|
||||||
|
// a buggy sample, or something else
|
||||||
|
void wxTextCtrl::OnUrlMouseEvent(wxMouseEvent& event)
|
||||||
|
{
|
||||||
|
event.Skip();
|
||||||
|
if(!(m_windowStyle & wxTE_AUTO_URL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
gint x, y;
|
||||||
|
GtkTextIter start, end;
|
||||||
|
GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(m_buffer),
|
||||||
|
"wxUrl");
|
||||||
|
|
||||||
|
gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(m_text), GTK_TEXT_WINDOW_WIDGET,
|
||||||
|
event.GetX(), event.GetY(), &x, &y);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(m_text),
|
||||||
|
GTK_TEXT_WINDOW_TEXT), m_gdkHandCursor);
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
if(!gtk_text_iter_begins_tag(&start, tag))
|
||||||
|
gtk_text_iter_backward_to_tag_toggle(&start, tag);
|
||||||
|
if(!gtk_text_iter_ends_tag(&end, tag))
|
||||||
|
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
|
||||||
|
if(event.GetEventType() == wxEVT_RIGHT_DOWN)
|
||||||
|
event.Skip(false);
|
||||||
|
|
||||||
|
wxTextUrlEvent url_event(m_windowId, event,
|
||||||
|
gtk_text_iter_get_offset(&start),
|
||||||
|
gtk_text_iter_get_offset(&end));
|
||||||
|
|
||||||
|
InitCommandEvent(url_event);
|
||||||
|
// Is that a good idea? Seems not (pleasure with gtk_text_view_start_selection_drag)
|
||||||
|
//event.Skip(!GetEventHandler()->ProcessEvent(url_event));
|
||||||
|
GetEventHandler()->ProcessEvent(url_event);
|
||||||
|
}
|
||||||
|
#endif // gtk2
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// scrolling
|
// scrolling
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@@ -180,6 +180,197 @@ gtk_insert_text_callback(GtkEditable *editable,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
// Implementation of wxTE_AUTO_URL for wxGTK2 by Mart Raudsepp,
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_apply_tag_callback(GtkTextBuffer *buffer,
|
||||||
|
GtkTextTag *tag,
|
||||||
|
GtkTextIter *start,
|
||||||
|
GtkTextIter *end,
|
||||||
|
gpointer textctrl)
|
||||||
|
{
|
||||||
|
if(tag == gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl"))
|
||||||
|
g_signal_stop_emission_by_name(buffer, "apply_tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// GtkTextCharPredicates for gtk_text_iter_*_find_char
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_whitespace (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return g_unichar_isspace(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_non_whitespace (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return !g_unichar_isspace(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_nonpunct (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return !g_unichar_ispunct(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
pred_nonpunct_or_slash (gunichar ch, gpointer user_data)
|
||||||
|
{
|
||||||
|
return !g_unichar_ispunct(ch) || ch == '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Check for links between s and e and correct tags as necessary
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// This function should be made match better while being efficient at one point.
|
||||||
|
// Most probably with a row of regular expressions.
|
||||||
|
static void
|
||||||
|
au_check_word( GtkTextIter *s, GtkTextIter *e )
|
||||||
|
{
|
||||||
|
static const char *URIPrefixes[] =
|
||||||
|
{
|
||||||
|
"http://",
|
||||||
|
"ftp://",
|
||||||
|
"www.",
|
||||||
|
"ftp.",
|
||||||
|
"mailto://",
|
||||||
|
"https://",
|
||||||
|
"file://",
|
||||||
|
"nntp://",
|
||||||
|
"news://",
|
||||||
|
"telnet://",
|
||||||
|
"mms://",
|
||||||
|
"gopher://",
|
||||||
|
"prospero://",
|
||||||
|
"wais://",
|
||||||
|
};
|
||||||
|
|
||||||
|
GtkTextIter start = *s, end = *e;
|
||||||
|
GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
|
||||||
|
|
||||||
|
// Get our special link tag
|
||||||
|
GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
|
||||||
|
|
||||||
|
// Get rid of punctuation from beginning and end.
|
||||||
|
// Might want to move this to au_check_range if an improved link checking doesn't
|
||||||
|
// use some intelligent punctuation checking itself (beware of undesired iter modifications).
|
||||||
|
if(g_unichar_ispunct( gtk_text_iter_get_char( &start ) ) )
|
||||||
|
gtk_text_iter_forward_find_char( &start, pred_nonpunct, NULL, e );
|
||||||
|
|
||||||
|
gtk_text_iter_backward_find_char( &end, pred_nonpunct_or_slash, NULL, &start );
|
||||||
|
gtk_text_iter_forward_char(&end);
|
||||||
|
|
||||||
|
gchar* text = gtk_text_iter_get_text( &start, &end );
|
||||||
|
size_t len = strlen(text), prefix_len;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
for( n = 0; n < WXSIZEOF(URIPrefixes); ++n )
|
||||||
|
{
|
||||||
|
prefix_len = strlen(URIPrefixes[n]);
|
||||||
|
if((len > prefix_len) && !strncasecmp(text, URIPrefixes[n], prefix_len))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(n < WXSIZEOF(URIPrefixes))
|
||||||
|
{
|
||||||
|
gulong signal_id = g_signal_handler_find(buffer,
|
||||||
|
(GSignalMatchType) (G_SIGNAL_MATCH_FUNC),
|
||||||
|
0, 0, NULL,
|
||||||
|
(gpointer)au_apply_tag_callback, NULL);
|
||||||
|
|
||||||
|
g_signal_handler_block(buffer, signal_id);
|
||||||
|
gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
|
||||||
|
g_signal_handler_unblock(buffer, signal_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_check_range(GtkTextIter *s,
|
||||||
|
GtkTextIter *range_end)
|
||||||
|
{
|
||||||
|
GtkTextIter range_start = *s;
|
||||||
|
GtkTextIter word_end;
|
||||||
|
GtkTextBuffer *buffer = gtk_text_iter_get_buffer(s);
|
||||||
|
GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "wxUrl");
|
||||||
|
|
||||||
|
gtk_text_buffer_remove_tag(buffer, tag, s, range_end);
|
||||||
|
|
||||||
|
if(g_unichar_isspace(gtk_text_iter_get_char(&range_start)))
|
||||||
|
gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
|
||||||
|
|
||||||
|
while(!gtk_text_iter_equal(&range_start, range_end))
|
||||||
|
{
|
||||||
|
word_end = range_start;
|
||||||
|
gtk_text_iter_forward_find_char(&word_end, pred_whitespace, NULL, range_end);
|
||||||
|
|
||||||
|
// Now we should have a word delimited by range_start and word_end, correct link tags
|
||||||
|
au_check_word(&range_start, &word_end);
|
||||||
|
|
||||||
|
range_start = word_end;
|
||||||
|
gtk_text_iter_forward_find_char(&range_start, pred_non_whitespace, NULL, range_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// "insert-text" for GtkTextBuffer
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_insert_text_callback(GtkTextBuffer *buffer,
|
||||||
|
GtkTextIter *end,
|
||||||
|
gchar *text,
|
||||||
|
gint len,
|
||||||
|
wxTextCtrl *win)
|
||||||
|
{
|
||||||
|
if (!len || !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
GtkTextIter start = *end;
|
||||||
|
gtk_text_iter_backward_chars(&start, g_utf8_strlen(text, len));
|
||||||
|
|
||||||
|
GtkTextIter line_start = start;
|
||||||
|
GtkTextIter line_end = *end;
|
||||||
|
GtkTextIter words_start = start;
|
||||||
|
GtkTextIter words_end = *end;
|
||||||
|
|
||||||
|
gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(&start));
|
||||||
|
gtk_text_iter_forward_to_line_end(&line_end);
|
||||||
|
gtk_text_iter_backward_find_char(&words_start, pred_whitespace, NULL, &line_start);
|
||||||
|
gtk_text_iter_forward_find_char(&words_end, pred_whitespace, NULL, &line_end);
|
||||||
|
|
||||||
|
au_check_range(&words_start, &words_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// "delete-range" for GtkTextBuffer
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
au_delete_range_callback(GtkTextBuffer *buffer,
|
||||||
|
GtkTextIter *start,
|
||||||
|
GtkTextIter *end,
|
||||||
|
wxTextCtrl *win)
|
||||||
|
{
|
||||||
|
if( !(win->GetWindowStyleFlag() & wxTE_AUTO_URL) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
GtkTextIter line_start = *start, line_end = *end;
|
||||||
|
|
||||||
|
gtk_text_iter_set_line(&line_start, gtk_text_iter_get_line(start));
|
||||||
|
gtk_text_iter_forward_to_line_end(&line_end);
|
||||||
|
gtk_text_iter_backward_find_char(start, pred_whitespace, NULL, &line_start);
|
||||||
|
gtk_text_iter_forward_find_char(end, pred_whitespace, NULL, &line_end);
|
||||||
|
|
||||||
|
au_check_range(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// "changed"
|
// "changed"
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -287,6 +478,18 @@ BEGIN_EVENT_TABLE(wxTextCtrl, wxControl)
|
|||||||
EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
|
EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
|
||||||
EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
|
EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
|
||||||
EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
|
EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
|
||||||
|
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
// wxTE_AUTO_URL wxTextUrl support. Currently only creates
|
||||||
|
// wxTextUrlEvent in the same cases as wxMSW, more can be added here.
|
||||||
|
EVT_MOTION (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_LEFT_DOWN (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_LEFT_UP (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_LEFT_DCLICK (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_RIGHT_DOWN (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_RIGHT_UP (wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
EVT_RIGHT_DCLICK(wxTextCtrl::OnUrlMouseEvent)
|
||||||
|
#endif
|
||||||
END_EVENT_TABLE()
|
END_EVENT_TABLE()
|
||||||
|
|
||||||
void wxTextCtrl::Init()
|
void wxTextCtrl::Init()
|
||||||
@@ -298,6 +501,18 @@ void wxTextCtrl::Init()
|
|||||||
m_vScrollbar = (GtkWidget *)NULL;
|
m_vScrollbar = (GtkWidget *)NULL;
|
||||||
#ifdef __WXGTK20__
|
#ifdef __WXGTK20__
|
||||||
m_frozenness = 0;
|
m_frozenness = 0;
|
||||||
|
m_gdkHandCursor = NULL;
|
||||||
|
m_gdkXTermCursor = NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
wxTextCtrl::~wxTextCtrl()
|
||||||
|
{
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
if(m_gdkHandCursor)
|
||||||
|
gdk_cursor_unref(m_gdkHandCursor);
|
||||||
|
if(m_gdkXTermCursor)
|
||||||
|
gdk_cursor_unref(m_gdkXTermCursor);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,6 +727,42 @@ bool wxTextCtrl::Create( wxWindow *parent,
|
|||||||
{
|
{
|
||||||
g_signal_connect( G_OBJECT(m_buffer), "changed",
|
g_signal_connect( G_OBJECT(m_buffer), "changed",
|
||||||
GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
|
GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
|
||||||
|
|
||||||
|
// .. and handle URLs on multi-line controls with wxTE_AUTO_URL style
|
||||||
|
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,
|
||||||
|
// hereby it's guaranteed to exist.
|
||||||
|
gtk_text_buffer_create_tag(m_buffer, "wxUrl",
|
||||||
|
"foreground", "blue",
|
||||||
|
"underline", PANGO_UNDERLINE_SINGLE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
// Check for URLs after each text change
|
||||||
|
g_signal_connect_after( G_OBJECT(m_buffer), "insert_text",
|
||||||
|
GTK_SIGNAL_FUNC(au_insert_text_callback), (gpointer)this);
|
||||||
|
g_signal_connect_after( G_OBJECT(m_buffer), "delete_range",
|
||||||
|
GTK_SIGNAL_FUNC(au_delete_range_callback), (gpointer)this);
|
||||||
|
|
||||||
|
// Block all wxUrl tag applying unless we do it ourselves, in which case we
|
||||||
|
// block this callback temporarily. This takes care of gtk+ internal
|
||||||
|
// gtk_text_buffer_insert_range* calls that would copy our URL tag otherwise,
|
||||||
|
// which is undesired because only a part of the URL might be copied.
|
||||||
|
// The insert-text signal emitted inside it will take care of newly formed
|
||||||
|
// or wholly copied URLs.
|
||||||
|
g_signal_connect( G_OBJECT(m_buffer), "apply_tag",
|
||||||
|
GTK_SIGNAL_FUNC(au_apply_tag_callback), NULL);
|
||||||
|
|
||||||
|
// Check for URLs in the initial string passed to Create
|
||||||
|
gtk_text_buffer_get_start_iter(m_buffer, &start);
|
||||||
|
gtk_text_buffer_get_end_iter(m_buffer, &end);
|
||||||
|
au_check_range(&start, &end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
@@ -1777,6 +2028,62 @@ void wxTextCtrl::Thaw()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// wxTextUrlEvent passing if style & wxTE_AUTO_URL
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef __WXGTK20__
|
||||||
|
|
||||||
|
// FIXME: when dragging on a link the sample gets an "Unknown event".
|
||||||
|
// This might be an excessive event from us or a buggy wxMouseEvent::Moving() or
|
||||||
|
// a buggy sample, or something else
|
||||||
|
void wxTextCtrl::OnUrlMouseEvent(wxMouseEvent& event)
|
||||||
|
{
|
||||||
|
event.Skip();
|
||||||
|
if(!(m_windowStyle & wxTE_AUTO_URL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
gint x, y;
|
||||||
|
GtkTextIter start, end;
|
||||||
|
GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(m_buffer),
|
||||||
|
"wxUrl");
|
||||||
|
|
||||||
|
gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(m_text), GTK_TEXT_WINDOW_WIDGET,
|
||||||
|
event.GetX(), event.GetY(), &x, &y);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(m_text),
|
||||||
|
GTK_TEXT_WINDOW_TEXT), m_gdkHandCursor);
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
if(!gtk_text_iter_begins_tag(&start, tag))
|
||||||
|
gtk_text_iter_backward_to_tag_toggle(&start, tag);
|
||||||
|
if(!gtk_text_iter_ends_tag(&end, tag))
|
||||||
|
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
|
||||||
|
if(event.GetEventType() == wxEVT_RIGHT_DOWN)
|
||||||
|
event.Skip(false);
|
||||||
|
|
||||||
|
wxTextUrlEvent url_event(m_windowId, event,
|
||||||
|
gtk_text_iter_get_offset(&start),
|
||||||
|
gtk_text_iter_get_offset(&end));
|
||||||
|
|
||||||
|
InitCommandEvent(url_event);
|
||||||
|
// Is that a good idea? Seems not (pleasure with gtk_text_view_start_selection_drag)
|
||||||
|
//event.Skip(!GetEventHandler()->ProcessEvent(url_event));
|
||||||
|
GetEventHandler()->ProcessEvent(url_event);
|
||||||
|
}
|
||||||
|
#endif // gtk2
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// scrolling
|
// scrolling
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
Reference in New Issue
Block a user