* @file gtkimhtml.c GTK+ IMHtml * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #define _PIDGIN_GTKIMHTML_C_ #include "gtksourceiter.h" #include "gtksourceundomanager.h" #include "gtksourceview-marshal.h" #include <gdk/gdkkeysyms.h> #ifdef HAVE_LANGINFO_CODESET #include <gdk/gdkwin32.h> #include <pango/pango-font.h> #define TOOLTIP_TIMEOUT 500 static GtkTextViewClass * parent_class = NULL ; GtkIMHtmlScalable * scalable ; GtkIMHtmlScalable * image ; struct _GtkIMHtmlScalable { void ( * scale )( struct _GtkIMHtmlScalable * , int , int ); void ( * add_to )( struct _GtkIMHtmlScalable * , GtkIMHtml * , GtkTextIter * ); void ( * free )( struct _GtkIMHtmlScalable * ); GtkIMHtmlScalable scalable ; GtkIMHtmlScalable scalable ; GtkImage * image ; /**< Contains the scaled version of this pixbuf. */ GdkPixbuf * pixbuf ; /**< The original pixbuf, before any scaling. */ struct _GtkIMHtmlAnimation { GtkIMHtmlImage imhtmlimage ; GdkPixbufAnimation * anim ; /**< The original animation, before any scaling. */ GdkPixbufAnimationIter * iter ; GtkSmileyTree ** children ; gboolean ( * activate )( GtkIMHtml * imhtml , GtkIMHtmlLink * link ); gboolean ( * context_menu )( GtkIMHtml * imhtml , GtkIMHtmlLink * link , GtkWidget * menu ); /* The five elements contained in a FONT tag */ gtk_text_view_drag_motion ( GtkWidget * widget , static void preinsert_cb ( GtkTextBuffer * buffer , GtkTextIter * iter , gchar * text , gint len , GtkIMHtml * imhtml ); static void insert_cb ( GtkTextBuffer * buffer , GtkTextIter * iter , gchar * text , gint len , GtkIMHtml * imhtml ); static void delete_cb ( GtkTextBuffer * buffer , GtkTextIter * iter , GtkTextIter * end , GtkIMHtml * imhtml ); static void insert_ca_cb ( GtkTextBuffer * buffer , GtkTextIter * arg1 , GtkTextChildAnchor * arg2 , gpointer user_data ); static void gtk_imhtml_apply_tags_on_insert ( GtkIMHtml * imhtml , GtkTextIter * start , GtkTextIter * end ); void gtk_imhtml_close_tags ( GtkIMHtml * imhtml , GtkTextIter * iter ); static void gtk_imhtml_link_drop_cb ( GtkWidget * widget , GdkDragContext * context , gint x , gint y , guint time , gpointer user_data ); static void gtk_imhtml_link_drag_rcv_cb ( GtkWidget * widget , GdkDragContext * dc , guint x , guint y , GtkSelectionData * sd , guint info , guint t , GtkIMHtml * imhtml ); static void mark_set_cb ( GtkTextBuffer * buffer , GtkTextIter * arg1 , GtkTextMark * mark , GtkIMHtml * imhtml ); static void hijack_menu_cb ( GtkIMHtml * imhtml , GtkMenu * menu , gpointer data ); static void paste_received_cb ( GtkClipboard * clipboard , GtkSelectionData * selection_data , gpointer data ); static void paste_plaintext_received_cb ( GtkClipboard * clipboard , const gchar * text , gpointer data ); static void imhtml_paste_insert ( GtkIMHtml * imhtml , const char * text , gboolean plaintext ); static void imhtml_toggle_bold ( GtkIMHtml * imhtml ); static void imhtml_toggle_italic ( GtkIMHtml * imhtml ); static void imhtml_toggle_strike ( GtkIMHtml * imhtml ); static void imhtml_toggle_underline ( GtkIMHtml * imhtml ); static void imhtml_font_grow ( GtkIMHtml * imhtml ); static void imhtml_font_shrink ( GtkIMHtml * imhtml ); static void imhtml_clear_formatting ( GtkIMHtml * imhtml ); static int gtk_imhtml_is_protocol ( const char * text ); static void gtk_imhtml_activate_tag ( GtkIMHtml * imhtml , GtkTextTag * tag ); static void gtk_imhtml_link_destroy ( GtkIMHtmlLink * link ); /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */ #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1]) static const gdouble _point_sizes [] = { .85 , .95 , 1 , 1.2 , 1.44 , 1.728 , 2.0736 }; static guint signals [ LAST_SIGNAL ] = { 0 }; static char * html_clipboard = NULL ; static char * text_clipboard = NULL ; static GtkClipboard * clipboard_selection = NULL ; static const GtkTargetEntry selection_targets [] = { { "text/html" , 0 , TARGET_HTML }, { "HTML Format" , 0 , TARGET_HTML }, { "UTF8_STRING" , 0 , TARGET_UTF8_STRING }, { "COMPOUND_TEXT" , 0 , TARGET_COMPOUND_TEXT }, { "STRING" , 0 , TARGET_STRING }, { "TEXT" , 0 , TARGET_TEXT }}; static const GtkTargetEntry link_drag_drop_targets [] = { clipboard_win32_to_html ( char * clipboard ) { int clipboard_length = 0 ; #if 0 /* Debugging for Windows clipboard */ purple_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard); fd = g_fopen("c:\\purplecb.txt", "wb"); fprintf(fd, "%s", clipboard); clipboard_length = strlen ( clipboard ); if ( ! ( header = strstr ( clipboard , "StartFragment:" )) || ( header - clipboard ) >= clipboard_length ) sscanf ( header , "StartFragment:%d" , & start ); if ( ! ( header = strstr ( clipboard , "EndFragment:" )) || ( header - clipboard ) >= clipboard_length ) sscanf ( header , "EndFragment:%d" , & finish ); if ( finish > clipboard_length ) finish = clipboard_length ; begin = clipboard + start ; end = clipboard + finish ; html = g_strndup ( begin , end - begin ); /* any newlines in the string will now be \r\n, so we need to strip out the \r */ split = g_strsplit ( html , " \r\n " , 0 ); html = g_strjoinv ( " \n " , split ); #if 0 /* Debugging for Windows clipboard */ purple_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html); clipboard_html_to_win32 ( char * html ) { clipboard = g_string_new ( "Version:1.0 \r\n " ); g_string_append ( clipboard , "StartHTML:0000000105 \r\n " ); g_string_append_printf ( clipboard , "EndHTML:%010d \r\n " , 147 + length ); g_string_append ( clipboard , "StartFragment:0000000127 \r\n " ); g_string_append_printf ( clipboard , "EndFragment:%010d \r\n " , 127 + length ); g_string_append ( clipboard , "<!--StartFragment--> \r\n " ); g_string_append ( clipboard , html ); g_string_append ( clipboard , " \r\n <!--EndFragment-->" ); return g_string_free ( clipboard , FALSE ); static gboolean clipboard_paste_html_win32 ( GtkIMHtml * imhtml ) { /* Win32 clipboard format value, and functions to convert back and * forth between HTML and the clipboard format. static UINT win_html_fmt = 0 ; /* Register HTML Format as desired clipboard format */ win_html_fmt = RegisterClipboardFormat ( "HTML Format" ); if ( gtk_text_view_get_editable ( GTK_TEXT_VIEW ( imhtml )) && IsClipboardFormatAvailable ( win_html_fmt )) { gboolean error_reading_clipboard = FALSE ; HWND hwnd = GDK_WINDOW_HWND ( GTK_WIDGET ( imhtml ) -> window ); if ( OpenClipboard ( hwnd )) { HGLOBAL hdata = GetClipboardData ( win_html_fmt ); if ( GetLastError () != ERROR_SUCCESS ) error_reading_clipboard = TRUE ; char * buffer = GlobalLock ( hdata ); error_reading_clipboard = TRUE ; char * text = clipboard_win32_to_html ( imhtml_paste_insert ( imhtml , text , error_reading_clipboard = TRUE ; if ( error_reading_clipboard ) { gchar * err_msg = g_win32_error_message ( GetLastError ()); purple_debug_info ( "html clipboard" , "Unable to read clipboard data: %s \n " , err_msg ? err_msg : "Unknown Error" ); gtk_smiley_tree_new ( void ) return g_new0 ( GtkSmileyTree , 1 ); gtk_smiley_tree_insert ( GtkSmileyTree * tree , const gchar * x = smiley -> smile ; t -> values = g_string_new ( "" ); pos = strchr ( t -> values -> str , * x ); t -> values = g_string_append_c ( t -> values , * x ); index = t -> values -> len - 1 ; t -> children = g_realloc ( t -> children , t -> values -> len * sizeof ( GtkSmileyTree * )); t -> children [ index ] = g_new0 ( GtkSmileyTree , 1 ); index = GPOINTER_TO_INT ( pos ) - GPOINTER_TO_INT ( t -> values -> str ); gtk_smiley_tree_destroy ( GtkSmileyTree * tree ) GSList * list = g_slist_prepend ( NULL , tree ); GtkSmileyTree * t = list -> data ; list = g_slist_remove ( list , t ); for ( i = 0 ; i < t -> values -> len ; i ++ ) list = g_slist_prepend ( list , t -> children [ i ]); g_string_free ( t -> values , TRUE ); static void ( * parent_size_allocate )( GtkWidget * widget , GtkAllocation * alloc ); static void gtk_imhtml_size_allocate ( GtkWidget * widget , GtkAllocation * alloc ) GtkIMHtml * imhtml = GTK_IMHTML ( widget ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & iter ); gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( widget ), & rect ); gtk_text_view_get_line_yrange ( GTK_TEXT_VIEW ( imhtml ), & iter , & y , & height ); if ((( y + height ) - ( rect . y + rect . height )) > height && gtk_text_buffer_get_char_count ( imhtml -> text_buffer )) { if ( imhtml -> old_rect . width != rect . width || imhtml -> old_rect . height != rect . height ) { GList * iter = GTK_IMHTML ( widget ) -> scalables ; xminus = gtk_text_view_get_left_margin ( GTK_TEXT_VIEW ( widget )) + gtk_text_view_get_right_margin ( GTK_TEXT_VIEW ( widget )); struct scalable_data * sd = iter -> data ; GtkIMHtmlScalable * scale = GTK_IMHTML_SCALABLE ( sd -> scalable ); scale -> scale ( scale , rect . width - xminus , rect . height ); parent_size_allocate ( widget , alloc ); /* Don't scroll here if we're in the middle of a smooth scroll */ if ( scroll && imhtml -> scroll_time == NULL && gtk_widget_get_realized ( GTK_WIDGET ( imhtml ))) gtk_imhtml_scroll_to_end ( imhtml , FALSE ); #define DEFAULT_SEND_COLOR "#204a87" #define DEFAULT_RECV_COLOR "#cc0000" #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00" #define DEFAULT_ACTION_COLOR "#062585" #define DEFAULT_WHISPER_ACTION_COLOR "#6C2585" #define DEFAULT_WHISPER_COLOR "#00FF00" static void ( * parent_style_set )( GtkWidget * widget , GtkStyle * prev_style ); gtk_imhtml_style_set ( GtkWidget * widget , GtkStyle * prev_style ) { "send-name" , "send-name-color" , DEFAULT_SEND_COLOR }, { "receive-name" , "receive-name-color" , DEFAULT_RECV_COLOR }, { "highlight-name" , "highlight-name-color" , DEFAULT_HIGHLIGHT_COLOR }, { "action-name" , "action-name-color" , DEFAULT_ACTION_COLOR }, { "whisper-action-name" , "whisper-action-name-color" , DEFAULT_WHISPER_ACTION_COLOR }, { "whisper-name" , "whisper-name-color" , DEFAULT_WHISPER_COLOR }, GtkIMHtml * imhtml = GTK_IMHTML ( widget ); GtkTextTagTable * table = gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ); for ( i = 0 ; styles [ i ]. tag ; i ++ ) { GtkTextTag * tag = gtk_text_tag_table_lookup ( table , styles [ i ]. tag ); purple_debug_warning ( "gtkimhtml" , "Cannot find tag '%s'. This should never happen. Please file a bug. \n " , styles [ i ]. tag ); gtk_widget_style_get ( widget , styles [ i ]. color , & color , NULL ); g_object_set ( tag , "foreground-gdk" , color , NULL ); gdk_color_parse ( styles [ i ]. def , & defcolor ); g_object_set ( tag , "foreground-gdk" , & defcolor , NULL ); parent_style_set ( widget , prev_style ); imhtml_get_iter_bounds ( GtkIMHtml * imhtml , GtkTextIter * start , GtkTextIter * end ) gtk_text_buffer_get_bounds ( imhtml -> text_buffer , start , end ); } else if ( imhtml -> editable ) { if ( ! gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , start , end )) { GtkTextMark * mark = gtk_text_buffer_get_insert ( imhtml -> text_buffer ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , start , mark ); gtk_imhtml_set_link_color ( GtkIMHtml * imhtml , GtkTextTag * tag ) gboolean visited = !! g_object_get_data ( G_OBJECT ( tag ), "visited" ); gtk_widget_style_get ( GTK_WIDGET ( imhtml ), visited ? "hyperlink-visited-color" : "hyperlink-color" , & color , NULL ); g_object_set ( G_OBJECT ( tag ), "foreground-gdk" , color , NULL ); g_object_set ( G_OBJECT ( tag ), "foreground" , visited ? "#800000" : "blue" , NULL ); #if GTK_CHECK_VERSION(3,0,0) gtk_imhtml_tip_paint ( GtkIMHtml * imhtml , cairo_t * cr , GtkWidget * w ) gtk_imhtml_tip_paint ( GtkIMHtml * imhtml , GdkEvent * event , GtkWidget * w ) g_return_val_if_fail ( GTK_IS_IMHTML ( imhtml ), FALSE ); /* We set the text in a separate function call so we can specify a max length. This is important so the tooltip isn't too wide for the screen, and also because some X library function exits the process when it can't allocate enough memory for a super wide layout = gtk_widget_create_pango_layout ( imhtml -> tip_window , NULL ); pango_layout_set_text ( layout , imhtml -> tip , 200 ); style = gtk_widget_get_style ( imhtml -> tip_window ); #if GTK_CHECK_VERSION(3,0,0) gtk_paint_flat_box ( style , cr , GTK_STATE_NORMAL , GTK_SHADOW_OUT , imhtml -> tip_window , "tooltip" , gtk_paint_layout ( style , cr , GTK_STATE_NORMAL , TRUE , imhtml -> tip_window , NULL , gtk_paint_flat_box ( style , gtk_widget_get_window ( imhtml -> tip_window ), GTK_STATE_NORMAL , GTK_SHADOW_OUT , NULL , imhtml -> tip_window , "tooltip" , 0 , 0 , -1 , -1 ); gtk_paint_layout ( style , gtk_widget_get_window ( imhtml -> tip_window ), GTK_STATE_NORMAL , FALSE , NULL , imhtml -> tip_window , NULL , gtk_imhtml_tip ( gpointer data ) GtkIMHtml * imhtml = data ; PangoFontMetrics * font_metrics ; GtkStyle * style = gtk_widget_get_style ( imhtml -> tip_window ); GtkAllocation allocation ; gint gap , x , y , h , w , scr_w , baseline_skip ; g_return_val_if_fail ( GTK_IS_IMHTML ( imhtml ), FALSE ); gtk_widget_get_allocation ( GTK_WIDGET ( imhtml ), & allocation ); if ( ! imhtml -> tip || ! gtk_widget_is_drawable ( GTK_WIDGET ( imhtml ))) { gtk_widget_destroy ( imhtml -> tip_window ); imhtml -> tip_window = NULL ; imhtml -> tip_window = gtk_window_new ( GTK_WINDOW_POPUP ); gtk_widget_set_app_paintable ( imhtml -> tip_window , TRUE ); gtk_window_set_title ( GTK_WINDOW ( imhtml -> tip_window ), "GtkIMHtml" ); gtk_window_set_resizable ( GTK_WINDOW ( imhtml -> tip_window ), FALSE ); gtk_widget_set_name ( imhtml -> tip_window , "gtk-tooltips" ); gtk_window_set_type_hint ( GTK_WINDOW ( imhtml -> tip_window ), GDK_WINDOW_TYPE_HINT_TOOLTIP ); #if GTK_CHECK_VERSION(3,0,0) g_signal_connect_swapped ( G_OBJECT ( imhtml -> tip_window ), "draw" , G_CALLBACK ( gtk_imhtml_tip_paint ), imhtml ); g_signal_connect_swapped ( G_OBJECT ( imhtml -> tip_window ), "expose_event" , G_CALLBACK ( gtk_imhtml_tip_paint ), imhtml ); gtk_widget_ensure_style ( imhtml -> tip_window ); /* We set the text in a separate function call so we can specify a max length. This is important so the tooltip isn't too wide for the screen, and also because some X library function exits the process when it can't allocate enough memory for a super wide layout = gtk_widget_create_pango_layout ( imhtml -> tip_window , NULL ); pango_layout_set_text ( layout , imhtml -> tip , 200 ); font = pango_context_load_font ( pango_layout_get_context ( layout ), char * tmp = pango_font_description_to_string ( style -> font_desc ); purple_debug ( PURPLE_DEBUG_ERROR , "gtk_imhtml_tip" , "pango_context_load_font() couldn't load font: '%s' \n " , font_metrics = pango_font_get_metrics ( font , NULL ); pango_layout_get_pixel_size ( layout , & scr_w , NULL ); gap = PANGO_PIXELS (( pango_font_metrics_get_ascent ( font_metrics ) + pango_font_metrics_get_descent ( font_metrics )) / 4 ); baseline_skip = PANGO_PIXELS ( pango_font_metrics_get_ascent ( font_metrics ) + pango_font_metrics_get_descent ( font_metrics )); gdk_window_get_pointer ( NULL , & x , & y , NULL ); if (( ! gtk_widget_get_has_window ( GTK_WIDGET ( imhtml )))) scr_w = gdk_screen_width (); y = y + PANGO_PIXELS ( pango_font_metrics_get_ascent ( font_metrics ) + pango_font_metrics_get_descent ( font_metrics )); gtk_widget_set_size_request ( imhtml -> tip_window , w , h ); gtk_window_move ( GTK_WINDOW ( imhtml -> tip_window ), x , y ); gtk_widget_show ( imhtml -> tip_window ); pango_font_metrics_unref ( font_metrics ); gtk_motion_event_notify ( GtkWidget * imhtml , GdkEventMotion * event , gpointer data ) GdkWindow * win = event -> window ; GSList * tags = NULL , * templist = NULL ; GtkTextTag * tag = NULL , * oldprelit_tag ; GtkTextChildAnchor * anchor ; GdkCursor * cursor = NULL ; oldprelit_tag = GTK_IMHTML ( imhtml ) -> prelit_tag ; gdk_window_get_pointer ( gtk_widget_get_window ( GTK_WIDGET ( imhtml )), NULL , NULL , NULL ); gtk_text_view_window_to_buffer_coords ( GTK_TEXT_VIEW ( imhtml ), GTK_TEXT_WINDOW_WIDGET , event -> x , event -> y , & x , & y ); gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( imhtml ), & iter , x , y ); tags = gtk_text_iter_get_tags ( & iter ); tip = g_object_get_data ( G_OBJECT ( tag ), "link_url" ); templist = templist -> next ; if ( tip && ( ! tag || ! g_object_get_data ( G_OBJECT ( tag ), "visited" ))) { GTK_IMHTML ( imhtml ) -> prelit_tag = tag ; if ( tag != oldprelit_tag ) { gtk_widget_style_get ( GTK_WIDGET ( imhtml ), "hyperlink-prelight-color" , & pre , NULL ); g_object_set ( G_OBJECT ( tag ), "foreground-gdk" , pre , NULL ); g_object_set ( G_OBJECT ( tag ), "foreground" , "#70a0ff" , NULL ); GTK_IMHTML ( imhtml ) -> prelit_tag = NULL ; if (( oldprelit_tag != NULL ) && ( GTK_IMHTML ( imhtml ) -> prelit_tag != oldprelit_tag )) { gtk_imhtml_set_link_color ( GTK_IMHTML ( imhtml ), oldprelit_tag ); if ( GTK_IMHTML ( imhtml ) -> tip ) { if ( tip == GTK_IMHTML ( imhtml ) -> tip ) { /* We've left the cell. Remove the timeout and create a new one below */ if ( GTK_IMHTML ( imhtml ) -> tip_window ) { gtk_widget_destroy ( GTK_IMHTML ( imhtml ) -> tip_window ); GTK_IMHTML ( imhtml ) -> tip_window = NULL ; if ( GTK_IMHTML ( imhtml ) -> editable ) cursor = GTK_IMHTML ( imhtml ) -> text_cursor ; cursor = GTK_IMHTML ( imhtml ) -> arrow_cursor ; if ( GTK_IMHTML ( imhtml ) -> tip_timer ) g_source_remove ( GTK_IMHTML ( imhtml ) -> tip_timer ); GTK_IMHTML ( imhtml ) -> tip_timer = 0 ; /* If we don't have a tip from a URL, let's see if we have a tip from a smiley */ anchor = gtk_text_iter_get_child_anchor ( & iter ); tip = g_object_get_data ( G_OBJECT ( anchor ), "gtkimhtml_tiptext" ); GTK_IMHTML ( imhtml ) -> tip_timer = g_timeout_add ( TOOLTIP_TIMEOUT , for ( templist = tags ; templist ; templist = templist -> next ) { if (( tip = g_object_get_data ( G_OBJECT ( tag ), "cursor" ))) { if ( hand && ! ( GTK_IMHTML ( imhtml ) -> editable )) cursor = GTK_IMHTML ( imhtml ) -> hand_cursor ; gdk_window_set_cursor ( win , cursor ); GTK_IMHTML ( imhtml ) -> tip = tip ; gtk_enter_event_notify ( GtkWidget * imhtml , GdkEventCrossing * event , gpointer data ) if ( GTK_IMHTML ( imhtml ) -> editable ) gtk_text_view_get_window ( GTK_TEXT_VIEW ( imhtml ), GTK_IMHTML ( imhtml ) -> text_cursor ); gtk_text_view_get_window ( GTK_TEXT_VIEW ( imhtml ), GTK_IMHTML ( imhtml ) -> arrow_cursor ); /* propagate the event normally */ gtk_leave_event_notify ( GtkWidget * imhtml , GdkEventCrossing * event , gpointer data ) /* when leaving the widget, clear any current & pending tooltips and restore the cursor */ if ( GTK_IMHTML ( imhtml ) -> prelit_tag ) { gtk_imhtml_set_link_color ( GTK_IMHTML ( imhtml ), GTK_IMHTML ( imhtml ) -> prelit_tag ); GTK_IMHTML ( imhtml ) -> prelit_tag = NULL ; if ( GTK_IMHTML ( imhtml ) -> tip_window ) { gtk_widget_destroy ( GTK_IMHTML ( imhtml ) -> tip_window ); GTK_IMHTML ( imhtml ) -> tip_window = NULL ; if ( GTK_IMHTML ( imhtml ) -> tip_timer ) { g_source_remove ( GTK_IMHTML ( imhtml ) -> tip_timer ); GTK_IMHTML ( imhtml ) -> tip_timer = 0 ; gtk_text_view_get_window ( GTK_TEXT_VIEW ( imhtml ), GTK_TEXT_WINDOW_TEXT ), NULL ); /* propagate the event normally */ /* TODO: I think this can be removed for GTK+ 3.0... */ #if !GTK_CHECK_VERSION(3,0,0) gtk_imhtml_expose_event ( GtkWidget * widget , GtkTextIter start , end , cur ; GdkRectangle visible_rect ; cairo_t * cr = gdk_cairo_create ( GDK_DRAWABLE ( event -> window )); gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( widget ), & visible_rect ); gtk_text_view_buffer_to_window_coords ( GTK_TEXT_VIEW ( widget ), gtk_text_view_window_to_buffer_coords ( GTK_TEXT_VIEW ( widget ), GTK_TEXT_WINDOW_TEXT , event -> area . x , event -> area . y , & buf_x , & buf_y ); if ( GTK_IMHTML ( widget ) -> editable || GTK_IMHTML ( widget ) -> wbfo ) { if ( GTK_IMHTML ( widget ) -> edit . background ) { gdk_color_parse ( GTK_IMHTML ( widget ) -> edit . background , & gcolor ); gdk_cairo_set_source_color ( cr , & gcolor ); gdk_cairo_set_source_color ( cr , & ( gtk_widget_get_style ( widget ) -> base [ gtk_widget_get_state ( widget )])); visible_rect . x , visible_rect . y , visible_rect . width , visible_rect . height ); if ( GTK_WIDGET_CLASS ( parent_class ) -> expose_event ) return ( * GTK_WIDGET_CLASS ( parent_class ) -> expose_event ) gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( widget ), & start , buf_x , buf_y ); gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( widget ), & end , buf_x + event -> area . width , buf_y + event -> area . height ); gtk_text_iter_order ( & start , & end ); while ( gtk_text_iter_in_range ( & cur , & start , & end )) { GSList * tags = gtk_text_iter_get_tags ( & cur ); for ( l = tags ; l ; l = l -> next ) { GtkTextTag * tag = l -> data ; if ( strncmp ( tag -> name , "BACKGROUND " , 11 )) if ( gtk_text_iter_ends_tag ( & cur , tag )) gtk_text_view_get_iter_location ( GTK_TEXT_VIEW ( widget ), & cur , & tag_area ); gtk_text_view_buffer_to_window_coords ( GTK_TEXT_VIEW ( widget ), rect . width = visible_rect . width ; gtk_text_iter_forward_to_tag_toggle ( & cur , tag ); while ( ! gtk_text_iter_is_end ( & cur ) && gtk_text_iter_begins_tag ( & cur , tag )); gtk_text_view_get_iter_location ( GTK_TEXT_VIEW ( widget ), & cur , & tag_area ); gtk_text_view_buffer_to_window_coords ( GTK_TEXT_VIEW ( widget ), rect . height = tag_area . y + tag_area . height - rect . y + gtk_text_view_get_pixels_above_lines ( GTK_TEXT_VIEW ( widget )) + gtk_text_view_get_pixels_below_lines ( GTK_TEXT_VIEW ( widget )); if ( ! gdk_color_parse ( color , & gcolor )) { strncpy ( & tmp [ 1 ], color , 7 ); if ( ! gdk_color_parse ( tmp , & gcolor )) gdk_color_parse ( "white" , & gcolor ); gdk_cairo_set_source_color ( cr , & gcolor ); rect . width , rect . height ); gtk_text_iter_backward_char ( & cur ); /* go back one, in case the end is the begining is the end * note that above, we always moved cur ahead by at least /* loop until another tag begins, or no tag begins */ while ( gtk_text_iter_forward_to_tag_toggle ( & cur , NULL ) && ! gtk_text_iter_is_end ( & cur ) && ! gtk_text_iter_begins_tag ( & cur , NULL )); if ( GTK_WIDGET_CLASS ( parent_class ) -> expose_event ) return ( * GTK_WIDGET_CLASS ( parent_class ) -> expose_event ) static void paste_unformatted_cb ( GtkMenuItem * menu , GtkIMHtml * imhtml ) GtkClipboard * clipboard = gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_CLIPBOARD ); gtk_clipboard_request_text ( clipboard , paste_plaintext_received_cb , imhtml ); static void clear_formatting_cb ( GtkMenuItem * menu , GtkIMHtml * imhtml ) gtk_imhtml_clear_formatting ( imhtml ); static void disable_smiley_selected ( GtkMenuItem * item , GtkIMHtml * imhtml ) if ( ! gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , & start , & end )) text = gtk_imhtml_get_markup_range ( imhtml , & start , & end ); mark = gtk_text_buffer_get_selection_bound ( imhtml -> text_buffer ); gtk_text_buffer_delete_selection ( imhtml -> text_buffer , FALSE , FALSE ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & start , mark ); gtk_imhtml_insert_html_at_iter ( imhtml , text , GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_SMILEY , & start ); static void hijack_menu_cb ( GtkIMHtml * imhtml , GtkMenu * menu , gpointer data ) menuitem = gtk_menu_item_new_with_mnemonic ( _ ( "Paste as Plain _Text" )); gtk_widget_show ( menuitem ); * TODO: gtk_clipboard_wait_is_text_available() iterates the glib * mainloop, which tends to be a source of bugs. It would * be good to audit this or change it to not wait. gtk_widget_set_sensitive ( menuitem , gtk_clipboard_wait_is_text_available ( gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_CLIPBOARD )))); /* put it after "Paste" */ gtk_menu_shell_insert ( GTK_MENU_SHELL ( menu ), menuitem , 3 ); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( paste_unformatted_cb ), imhtml ); menuitem = gtk_menu_item_new_with_mnemonic ( _ ( "_Reset formatting" )); gtk_widget_show ( menuitem ); gtk_widget_set_sensitive ( menuitem , imhtml -> editable ); /* put it after Delete */ gtk_menu_shell_insert ( GTK_MENU_SHELL ( menu ), menuitem , 5 ); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( clear_formatting_cb ), imhtml ); menuitem = gtk_menu_item_new_with_mnemonic ( _ ( "Disable _smileys in selected text" )); gtk_widget_show ( menuitem ); if ( gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , & start , & end )) { g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( disable_smiley_selected ), imhtml ); gtk_widget_set_sensitive ( menuitem , FALSE ); gtk_menu_shell_insert ( GTK_MENU_SHELL ( menu ), menuitem , 6 ); ucs2_order ( gboolean swap ) be = G_BYTE_ORDER == G_BIG_ENDIAN ; /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/ utf16_to_utf8_with_bom_check ( gchar * data , guint len ) { * Unicode Techinical Report 20 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an * initial 0xfeff (ZWNBSP) as a byte order indicator so that is * what we do. If there is no indicator assume it is in the default fromcode = ucs2_order ( c == 0xfeff ); utf8_ret = g_convert ( data , len , "UTF-8" , fromcode , NULL , NULL , & error ); purple_debug_warning ( "gtkimhtml" , "g_convert error: %s \n " , error -> message ); static void gtk_imhtml_clipboard_get ( GtkClipboard * clipboard , GtkSelectionData * selection_data , guint info , GtkIMHtml * imhtml ) { gboolean primary = ( clipboard != clipboard_selection ); GtkTextMark * sel = NULL , * ins = NULL ; g_return_if_fail ( imhtml != NULL ); ins = gtk_text_buffer_get_insert ( imhtml -> text_buffer ); sel = gtk_text_buffer_get_selection_bound ( imhtml -> text_buffer ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & start , sel ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & end , ins ); if ( info == TARGET_HTML ) { text = gtk_imhtml_get_markup_range ( imhtml , & start , & end ); /* Mozilla asks that we start our text/html with the Unicode byte order mark */ selection = g_convert ( text , -1 , "UTF-16" , "UTF-8" , NULL , & len , NULL ); gtk_selection_data_set ( selection_data , gdk_atom_intern ( "text/html" , FALSE ), 16 , ( const guchar * ) selection , len ); selection = clipboard_html_to_win32 ( html_clipboard ); gtk_selection_data_set ( selection_data , gdk_atom_intern ( "HTML Format" , FALSE ), 8 , ( const guchar * ) selection , strlen ( selection )); text = gtk_imhtml_get_text ( imhtml , & start , & end ); gtk_selection_data_set_text ( selection_data , text , strlen ( text )); if ( primary ) /* This was allocated here */ static void gtk_imhtml_primary_clipboard_clear ( GtkClipboard * clipboard , GtkIMHtml * imhtml ) GtkTextIter selection_bound ; gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & insert , gtk_text_buffer_get_mark ( imhtml -> text_buffer , "insert" )); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & selection_bound , gtk_text_buffer_get_mark ( imhtml -> text_buffer , "selection_bound" )); if ( ! gtk_text_iter_equal ( & insert , & selection_bound )) gtk_text_buffer_move_mark ( imhtml -> text_buffer , gtk_text_buffer_get_mark ( imhtml -> text_buffer , "selection_bound" ), static void gtk_imhtml_clipboard_clear ( GtkClipboard * clipboard , GtkSelectionData * sel_data , guint info , gpointer user_data_or_owner ) static void copy_clipboard_cb ( GtkIMHtml * imhtml , gpointer unused ) if ( gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , & start , & end )) { if ( ! clipboard_selection ) clipboard_selection = gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_CLIPBOARD ); gtk_clipboard_set_with_data ( clipboard_selection , selection_targets , sizeof ( selection_targets ) / sizeof ( GtkTargetEntry ), ( GtkClipboardGetFunc ) gtk_imhtml_clipboard_get , ( GtkClipboardClearFunc ) gtk_imhtml_clipboard_clear , NULL ); html_clipboard = gtk_imhtml_get_markup_range ( imhtml , & start , & end ); text_clipboard = gtk_imhtml_get_text ( imhtml , & start , & end ); g_signal_stop_emission_by_name ( imhtml , "copy-clipboard" ); static void cut_clipboard_cb ( GtkIMHtml * imhtml , gpointer unused ) if ( gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , & start , & end )) { if ( ! clipboard_selection ) clipboard_selection = gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_CLIPBOARD ); gtk_clipboard_set_with_data ( clipboard_selection , selection_targets , sizeof ( selection_targets ) / sizeof ( GtkTargetEntry ), ( GtkClipboardGetFunc ) gtk_imhtml_clipboard_get , ( GtkClipboardClearFunc ) gtk_imhtml_clipboard_clear , NULL ); html_clipboard = gtk_imhtml_get_markup_range ( imhtml , & start , & end ); text_clipboard = gtk_imhtml_get_text ( imhtml , & start , & end ); gtk_text_buffer_delete_selection ( imhtml -> text_buffer , FALSE , FALSE ); g_signal_stop_emission_by_name ( imhtml , "cut-clipboard" ); static void imhtml_paste_insert ( GtkIMHtml * imhtml , const char * text , gboolean plaintext ) GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : ( GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS ); /* Delete any currently selected text */ gtk_text_buffer_delete_selection ( imhtml -> text_buffer , TRUE , TRUE ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , gtk_text_buffer_get_insert ( imhtml -> text_buffer )); if ( ! imhtml -> wbfo && ! plaintext ) gtk_imhtml_close_tags ( imhtml , & iter ); gtk_imhtml_insert_html_at_iter ( imhtml , text , flags , & iter ); gtk_text_buffer_move_mark_by_name ( imhtml -> text_buffer , "insert" , & iter ); gtk_text_view_scroll_to_mark ( GTK_TEXT_VIEW ( imhtml ), gtk_text_buffer_get_insert ( imhtml -> text_buffer ), if ( ! imhtml -> wbfo && ! plaintext ) gtk_imhtml_close_tags ( imhtml , & iter ); static void paste_plaintext_received_cb ( GtkClipboard * clipboard , const gchar * text , gpointer data ) if ( text == NULL || ! ( * text )) tmp = g_markup_escape_text ( text , -1 ); imhtml_paste_insert ( data , tmp , TRUE ); static void paste_received_cb ( GtkClipboard * clipboard , GtkSelectionData * selection_data , gpointer data ) GtkIMHtml * imhtml = data ; gint length = gtk_selection_data_get_length ( selection_data ); if ( ! gtk_text_view_get_editable ( GTK_TEXT_VIEW ( imhtml ))) if ( imhtml -> wbfo || length <= 0 ) { gtk_clipboard_request_text ( clipboard , paste_plaintext_received_cb , imhtml ); /* Here's some debug code, for figuring out what sent to us over the clipboard. */ purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t", selection_data->format, selection_data->length); for (i = 0; i < (/*(selection_data->format / 8) **/ selection_data->length); i++) { if (selection_data->data[i] == '\0') printf("%c", selection_data->data[i]); text = g_malloc ( length + 1 ); memcpy ( text , gtk_selection_data_get_data ( selection_data ), length ); /* Make sure the paste data is null-terminated. Given that * we're passed length (but assume later that it is * null-terminated), this seems sensible to me. if ( gtk_selection_data_get_data_type ( selection_data ) == gdk_atom_intern ( "HTML Format" , FALSE )) { char * tmp = clipboard_win32_to_html ( text ); ( * ( guint16 * ) text == 0xfeff || * ( guint16 * ) text == 0xfffe )) { char * utf8 = utf16_to_utf8_with_bom_check ( text , length ); purple_debug_warning ( "gtkimhtml" , "g_convert from UTF-16 failed in paste_received_cb \n " ); if ( ! ( * text ) || ! g_utf8_validate ( text , -1 , NULL )) { purple_debug_warning ( "gtkimhtml" , "empty string or invalid UTF-8 in paste_received_cb \n " ); imhtml_paste_insert ( imhtml , text , FALSE ); static void smart_backspace_cb ( GtkIMHtml * imhtml , gpointer blah ) GtkTextChildAnchor * anchor ; gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , gtk_text_buffer_get_insert ( imhtml -> text_buffer )); /* Get the character before the insertion point */ offset = gtk_text_iter_get_offset ( & iter ); gtk_text_iter_backward_char ( & iter ); anchor = gtk_text_iter_get_child_anchor ( & iter ); return ; /* No smiley here */ text = g_object_get_data ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" ); /* ok, then we need to insert the image buffer text before the anchor */ gtk_text_buffer_insert ( imhtml -> text_buffer , & iter , text , -1 ); static void paste_clipboard_cb ( GtkIMHtml * imhtml , gpointer blah ) /* If we're on windows, let's see if we can get data from the HTML Format clipboard before we try to paste from the GTK buffer */ if ( ! clipboard_paste_html_win32 ( imhtml ) && gtk_text_view_get_editable ( GTK_TEXT_VIEW ( imhtml ))) { GtkClipboard * clipboard = gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_CLIPBOARD ); gtk_clipboard_request_text ( clipboard , paste_plaintext_received_cb , imhtml ); GtkClipboard * clipboard = gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_CLIPBOARD ); gtk_clipboard_request_contents ( clipboard , gdk_atom_intern ( "text/html" , FALSE ), paste_received_cb , imhtml ); g_signal_stop_emission_by_name ( imhtml , "paste-clipboard" ); static void imhtml_realized_remove_primary ( GtkIMHtml * imhtml , gpointer unused ) gtk_text_buffer_remove_selection_clipboard ( GTK_IMHTML ( imhtml ) -> text_buffer , gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_PRIMARY )); static void imhtml_destroy_add_primary ( GtkIMHtml * imhtml , gpointer unused ) gtk_text_buffer_add_selection_clipboard ( GTK_IMHTML ( imhtml ) -> text_buffer , gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_PRIMARY )); static void mark_set_so_update_selection_cb ( GtkTextBuffer * buffer , GtkTextIter * arg1 , GtkTextMark * mark , GtkIMHtml * imhtml ) if ( gtk_text_buffer_get_selection_bounds ( buffer , NULL , NULL )) { gtk_clipboard_set_with_owner ( gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_PRIMARY ), selection_targets , sizeof ( selection_targets ) / sizeof ( GtkTargetEntry ), ( GtkClipboardGetFunc ) gtk_imhtml_clipboard_get , ( GtkClipboardClearFunc ) gtk_imhtml_primary_clipboard_clear , G_OBJECT ( imhtml )); static gboolean gtk_imhtml_button_press_event ( GtkIMHtml * imhtml , GdkEventButton * event , gpointer unused ) if ( event -> button == 2 ) { GtkClipboard * clipboard = gtk_widget_get_clipboard ( GTK_WIDGET ( imhtml ), GDK_SELECTION_PRIMARY ); gtk_text_view_window_to_buffer_coords ( GTK_TEXT_VIEW ( imhtml ), gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( imhtml ), & iter , x , y ); gtk_text_buffer_place_cursor ( imhtml -> text_buffer , & iter ); gtk_clipboard_request_contents ( clipboard , gdk_atom_intern ( "text/html" , FALSE ), paste_received_cb , imhtml ); gtk_imhtml_undo ( GtkIMHtml * imhtml ) g_return_if_fail ( GTK_IS_IMHTML ( imhtml )); gtk_source_undo_manager_can_undo ( imhtml -> undo_manager )) gtk_source_undo_manager_undo ( imhtml -> undo_manager ); gtk_imhtml_redo ( GtkIMHtml * imhtml ) g_return_if_fail ( GTK_IS_IMHTML ( imhtml )); gtk_source_undo_manager_can_redo ( imhtml -> undo_manager )) gtk_source_undo_manager_redo ( imhtml -> undo_manager ); static gboolean imhtml_message_send ( GtkIMHtml * imhtml ) imhtml_paste_cb ( GtkIMHtml * imhtml , const char * str ) if ( ! gtk_text_view_get_editable ( GTK_TEXT_VIEW ( imhtml ))) if ( ! str || !* str || ! strcmp ( str , "html" )) g_signal_emit_by_name ( imhtml , "paste_clipboard" ); else if ( ! strcmp ( str , "text" )) paste_unformatted_cb ( NULL , imhtml ); static void imhtml_toggle_format ( GtkIMHtml * imhtml , GtkIMHtmlButtons buttons ) /* since this function is the handler for the formatting keystrokes, we need to check here that the formatting attempted is permitted */ buttons &= imhtml -> format_functions ; imhtml_toggle_bold ( imhtml ); imhtml_toggle_italic ( imhtml ); case GTK_IMHTML_UNDERLINE : imhtml_toggle_underline ( imhtml ); imhtml_toggle_strike ( imhtml ); imhtml_font_shrink ( imhtml ); imhtml_font_grow ( imhtml ); gtk_imhtml_finalize ( GObject * object ) GtkIMHtml * imhtml = GTK_IMHTML ( object ); g_source_remove ( imhtml -> scroll_src ); g_timer_destroy ( imhtml -> scroll_time ); g_hash_table_destroy ( imhtml -> smiley_data ); gtk_smiley_tree_destroy ( imhtml -> default_smilies ); gdk_cursor_unref ( imhtml -> hand_cursor ); gdk_cursor_unref ( imhtml -> arrow_cursor ); gdk_cursor_unref ( imhtml -> text_cursor ); gtk_widget_destroy ( imhtml -> tip_window ); g_source_remove ( imhtml -> tip_timer ); for ( scalables = imhtml -> scalables ; scalables ; scalables = scalables -> next ) { struct scalable_data * sd = scalables -> data ; GtkIMHtmlScalable * scale = GTK_IMHTML_SCALABLE ( sd -> scalable ); for ( l = imhtml -> im_images ; l ; l = l -> next ) { struct im_image_data * img_data = l -> data ; if ( imhtml -> funcs -> image_unref ) imhtml -> funcs -> image_unref ( img_data -> id ); g_list_free ( imhtml -> scalables ); g_slist_free ( imhtml -> im_images ); g_queue_free ( imhtml -> animations ); g_free ( imhtml -> protocol_name ); g_free ( imhtml -> search_string ); g_object_unref ( imhtml -> undo_manager ); G_OBJECT_CLASS ( parent_class ) -> finalize ( object ); static GtkIMHtmlProtocol * imhtml_find_protocol ( const char * url , gboolean reverse ) GtkIMHtmlProtocol * proto = NULL ; gssize length = reverse ? ( gssize ) strlen ( url ) : -1 ; klass = g_type_class_ref ( GTK_TYPE_IMHTML ); for ( iter = klass -> protocols ; iter ; iter = iter -> next ) { if ( g_ascii_strncasecmp ( url , proto -> name , reverse ? MIN ( length , proto -> length ) : proto -> length ) == 0 ) { imhtml_url_clicked ( GtkIMHtml * imhtml , const char * url ) GtkIMHtmlProtocol * proto = imhtml_find_protocol ( url , FALSE ); link = g_new0 ( GtkIMHtmlLink , 1 ); link -> imhtml = g_object_ref ( imhtml ); link -> url = g_strdup ( url ); proto -> activate ( imhtml , link ); /* XXX: Do something with the return value? */ gtk_imhtml_link_destroy ( link ); static void gtk_imhtml_class_init ( GtkIMHtmlClass * klass ) GtkWidgetClass * widget_class = ( GtkWidgetClass * ) klass ; GObjectClass * gobject_class ; gobject_class = ( GObjectClass * ) klass ; parent_class = g_type_class_ref ( GTK_TYPE_TEXT_VIEW ); signals [ URL_CLICKED ] = g_signal_new ( "url_clicked" , G_TYPE_FROM_CLASS ( gobject_class ), G_STRUCT_OFFSET ( GtkIMHtmlClass , url_clicked ), g_cclosure_marshal_VOID__POINTER , signals [ BUTTONS_UPDATE ] = g_signal_new ( "format_buttons_update" , G_TYPE_FROM_CLASS ( gobject_class ), G_STRUCT_OFFSET ( GtkIMHtmlClass , buttons_update ), g_cclosure_marshal_VOID__INT , signals [ TOGGLE_FORMAT ] = g_signal_new ( "format_function_toggle" , G_TYPE_FROM_CLASS ( gobject_class ), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION , G_STRUCT_OFFSET ( GtkIMHtmlClass , toggle_format ), g_cclosure_marshal_VOID__INT , signals [ CLEAR_FORMAT ] = g_signal_new ( "format_function_clear" , G_TYPE_FROM_CLASS ( gobject_class ), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION , G_STRUCT_OFFSET ( GtkIMHtmlClass , clear_format ), g_cclosure_marshal_VOID__VOID , signals [ UPDATE_FORMAT ] = g_signal_new ( "format_function_update" , G_TYPE_FROM_CLASS ( gobject_class ), G_STRUCT_OFFSET ( GtkIMHtmlClass , update_format ), g_cclosure_marshal_VOID__VOID , signals [ MESSAGE_SEND ] = g_signal_new ( "message_send" , G_TYPE_FROM_CLASS ( gobject_class ), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION , G_STRUCT_OFFSET ( GtkIMHtmlClass , message_send ), 0 , g_cclosure_marshal_VOID__VOID , signals [ PASTE ] = g_signal_new ( "paste" , G_TYPE_FROM_CLASS ( gobject_class ), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION , 0 , g_cclosure_marshal_VOID__STRING , G_TYPE_NONE , 1 , G_TYPE_STRING ); signals [ UNDO ] = g_signal_new ( "undo" , G_TYPE_FROM_CLASS ( klass ), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION , G_STRUCT_OFFSET ( GtkIMHtmlClass , undo ), gtksourceview_marshal_VOID__VOID , signals [ REDO ] = g_signal_new ( "redo" , G_TYPE_FROM_CLASS ( klass ), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION , G_STRUCT_OFFSET ( GtkIMHtmlClass , redo ), gtksourceview_marshal_VOID__VOID , klass -> toggle_format = imhtml_toggle_format ; klass -> message_send = imhtml_message_send ; klass -> clear_format = imhtml_clear_formatting ; klass -> url_clicked = imhtml_url_clicked ; klass -> undo = gtk_imhtml_undo ; klass -> redo = gtk_imhtml_redo ; gobject_class -> finalize = gtk_imhtml_finalize ; widget_class -> drag_motion = gtk_text_view_drag_motion ; /* TODO: I _think_ this should be removed for GTK+ 3.0 */ #if !GTK_CHECK_VERSION(3,0,0) widget_class -> expose_event = gtk_imhtml_expose_event ; parent_size_allocate = widget_class -> size_allocate ; widget_class -> size_allocate = gtk_imhtml_size_allocate ; parent_style_set = widget_class -> style_set ; widget_class -> style_set = gtk_imhtml_style_set ; gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "hyperlink-color" , "Color to draw hyperlinks." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "hyperlink-visited-color" , "Hyperlink visited color" , "Color to draw hyperlink after it has been visited (or activated)." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "hyperlink-prelight-color" , "Hyperlink prelight color" , "Color to draw hyperlinks when mouse is over them." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "send-name-color" , "Sent Message Name Color" , "Color to draw the name of a message you sent." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "receive-name-color" , "Received Message Name Color" , "Color to draw the name of a message you received." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "highlight-name-color" , " \" Attention \" Name Color" , "Color to draw the name of a message you received containing your name." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "action-name-color" , "Action Message Name Color" , "Color to draw the name of an action message." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "whisper-action-name-color" , "Action Message Name Color for Whispered Message" , "Color to draw the name of a whispered action message." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "whisper-name-color" , "Whisper Message Name Color" , "Color to draw the name of a whispered message." , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); /* Customizable typing notification ... sort of. Example: * GtkIMHtml::typing-notification-font = "monospace italic light 8.0" * GtkIMHtml::typing-notification-color = "#ff0000" * GtkIMHtml::typing-notification-enable = 1 gtk_widget_class_install_style_property ( widget_class , g_param_spec_boxed ( "typing-notification-color" , "Typing notification color" , "The color to use for the typing notification" , GDK_TYPE_COLOR , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_string ( "typing-notification-font" , "Typing notification font" , "The font to use for the typing notification" , "light 8.0" , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); gtk_widget_class_install_style_property ( widget_class , g_param_spec_boolean ( "typing-notification-enable" , "Enable typing notification" , "Enable typing notification" , TRUE , G_PARAM_READABLE | G_PARAM_STATIC_STRINGS )); GtkBindingSet *binding_set; binding_set = gtk_binding_set_by_class (parent_class); gtk_binding_entry_add_signal (binding_set, GDK_KEY_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD); gtk_binding_entry_add_signal (binding_set, GDK_KEY_i, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_ITALIC); gtk_binding_entry_add_signal (binding_set, GDK_KEY_u, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_UNDERLINE); gtk_binding_entry_add_signal (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW); gtk_binding_entry_add_signal (binding_set, GDK_KEY_equal, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_GROW); gtk_binding_entry_add_signal (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_SHRINK); binding_set = gtk_binding_set_by_class(klass); gtk_binding_entry_add_signal (binding_set, GDK_KEY_r, GDK_CONTROL_MASK, "format_function_clear", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, "message_send", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, "message_send", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK, "undo", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_F14, 0, "undo", 0); gtk_binding_entry_add_signal(binding_set, GDK_KEY_v, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "paste", 1, G_TYPE_STRING, "text"); static void gtk_imhtml_init ( GtkIMHtml * imhtml ) imhtml -> text_buffer = gtk_text_buffer_new ( NULL ); imhtml -> undo_manager = gtk_source_undo_manager_new ( imhtml -> text_buffer ); gtk_text_view_set_buffer ( GTK_TEXT_VIEW ( imhtml ), imhtml -> text_buffer ); gtk_text_view_set_wrap_mode ( GTK_TEXT_VIEW ( imhtml ), GTK_WRAP_WORD_CHAR ); gtk_text_view_set_pixels_above_lines ( GTK_TEXT_VIEW ( imhtml ), 2 ); gtk_text_view_set_pixels_below_lines ( GTK_TEXT_VIEW ( imhtml ), 3 ); gtk_text_view_set_left_margin ( GTK_TEXT_VIEW ( imhtml ), 2 ); gtk_text_view_set_right_margin ( GTK_TEXT_VIEW ( imhtml ), 2 ); /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/ /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/ /* These tags will be used often and can be reused--we create them on init and then apply them by name * other tags (color, size, face, etc.) will have to be created and applied dynamically * Note that even though we created SUB, SUP, and PRE tags here, we don't really * apply them anywhere yet. */ gtk_text_buffer_create_tag ( imhtml -> text_buffer , "BOLD" , "weight" , PANGO_WEIGHT_BOLD , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "ITALICS" , "style" , PANGO_STYLE_ITALIC , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "UNDERLINE" , "underline" , PANGO_UNDERLINE_SINGLE , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "STRIKE" , "strikethrough" , TRUE , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "SUB" , "rise" , -5000 , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "SUP" , "rise" , 5000 , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "PRE" , "family" , "Monospace" , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "search" , "background" , "#22ff00" , "weight" , "bold" , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "comment" , "weight" , PANGO_WEIGHT_NORMAL , gtk_text_buffer_create_tag ( imhtml -> text_buffer , "send-name" , "weight" , PANGO_WEIGHT_BOLD , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "receive-name" , "weight" , PANGO_WEIGHT_BOLD , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "highlight-name" , "weight" , PANGO_WEIGHT_BOLD , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "action-name" , "weight" , PANGO_WEIGHT_BOLD , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "whisper-action-name" , "weight" , PANGO_WEIGHT_BOLD , NULL ); gtk_text_buffer_create_tag ( imhtml -> text_buffer , "whisper-name" , "weight" , PANGO_WEIGHT_BOLD , NULL ); /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */ imhtml -> hand_cursor = gdk_cursor_new ( GDK_HAND2 ); imhtml -> arrow_cursor = gdk_cursor_new ( GDK_LEFT_PTR ); imhtml -> text_cursor = gdk_cursor_new ( GDK_XTERM ); imhtml -> show_comments = TRUE ; imhtml -> smiley_data = g_hash_table_new_full ( g_str_hash , g_str_equal , g_free , ( GDestroyNotify ) gtk_smiley_tree_destroy ); imhtml -> default_smilies = gtk_smiley_tree_new (); g_signal_connect ( G_OBJECT ( imhtml ), "motion-notify-event" , G_CALLBACK ( gtk_motion_event_notify ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "leave-notify-event" , G_CALLBACK ( gtk_leave_event_notify ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "enter-notify-event" , G_CALLBACK ( gtk_enter_event_notify ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "button_press_event" , G_CALLBACK ( gtk_imhtml_button_press_event ), NULL ); g_signal_connect ( G_OBJECT ( imhtml -> text_buffer ), "insert-text" , G_CALLBACK ( preinsert_cb ), imhtml ); g_signal_connect ( G_OBJECT ( imhtml -> text_buffer ), "delete_range" , G_CALLBACK ( delete_cb ), imhtml ); g_signal_connect_after ( G_OBJECT ( imhtml -> text_buffer ), "insert-text" , G_CALLBACK ( insert_cb ), imhtml ); g_signal_connect_after ( G_OBJECT ( imhtml -> text_buffer ), "insert-child-anchor" , G_CALLBACK ( insert_ca_cb ), imhtml ); gtk_drag_dest_set ( GTK_WIDGET ( imhtml ), 0 , link_drag_drop_targets , sizeof ( link_drag_drop_targets ) / sizeof ( GtkTargetEntry ), g_signal_connect ( G_OBJECT ( imhtml ), "drag_data_received" , G_CALLBACK ( gtk_imhtml_link_drag_rcv_cb ), imhtml ); g_signal_connect ( G_OBJECT ( imhtml ), "drag_drop" , G_CALLBACK ( gtk_imhtml_link_drop_cb ), imhtml ); g_signal_connect ( G_OBJECT ( imhtml ), "copy-clipboard" , G_CALLBACK ( copy_clipboard_cb ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "cut-clipboard" , G_CALLBACK ( cut_clipboard_cb ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "paste-clipboard" , G_CALLBACK ( paste_clipboard_cb ), NULL ); g_signal_connect_after ( G_OBJECT ( imhtml ), "realize" , G_CALLBACK ( imhtml_realized_remove_primary ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "unrealize" , G_CALLBACK ( imhtml_destroy_add_primary ), NULL ); g_signal_connect ( G_OBJECT ( imhtml ), "paste" , G_CALLBACK ( imhtml_paste_cb ), NULL ); g_signal_connect_after ( G_OBJECT ( GTK_IMHTML ( imhtml ) -> text_buffer ), "mark-set" , G_CALLBACK ( mark_set_so_update_selection_cb ), imhtml ); gtk_widget_add_events ( GTK_WIDGET ( imhtml ), GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK ); imhtml -> tip_window = NULL ; imhtml -> edit . bold = FALSE ; imhtml -> edit . italic = FALSE ; imhtml -> edit . underline = FALSE ; imhtml -> edit . forecolor = NULL ; imhtml -> edit . backcolor = NULL ; imhtml -> edit . fontface = NULL ; imhtml -> edit . fontsize = 0 ; imhtml -> edit . link = NULL ; imhtml -> scalables = NULL ; imhtml -> animations = g_queue_new (); gtk_imhtml_set_editable ( imhtml , FALSE ); g_signal_connect ( G_OBJECT ( imhtml ), "populate-popup" , G_CALLBACK ( hijack_menu_cb ), NULL ); GtkWidget * gtk_imhtml_new ( void * a , void * b ) return GTK_WIDGET ( g_object_new ( gtk_imhtml_get_type (), NULL )); GType gtk_imhtml_get_type () static GType imhtml_type = 0 ; static const GTypeInfo imhtml_info = { ( GClassInitFunc ) gtk_imhtml_class_init , ( GInstanceInitFunc ) gtk_imhtml_init , imhtml_type = g_type_register_static ( gtk_text_view_get_type (), "GtkIMHtml" , & imhtml_info , 0 ); static void gtk_imhtml_link_destroy ( GtkIMHtmlLink * link ) g_object_unref ( link -> imhtml ); g_object_unref ( link -> tag ); /* The callback for an event on a link tag. */ static gboolean tag_event ( GtkTextTag * tag , GObject * imhtml , GdkEvent * event , GtkTextIter * arg2 , gpointer unused ) GdkEventButton * event_button = ( GdkEventButton * ) event ; if ( GTK_IMHTML ( imhtml ) -> editable ) if ( event -> type == GDK_BUTTON_RELEASE ) { if (( event_button -> button == 1 ) || ( event_button -> button == 2 )) { /* we shouldn't open a URL if the user has selected something: */ if ( gtk_text_buffer_get_selection_bounds ( gtk_text_iter_get_buffer ( arg2 ), & start , & end )) gtk_imhtml_activate_tag ( GTK_IMHTML ( imhtml ), tag ); } else if ( event_button -> button == 3 ) { GtkIMHtmlProtocol * proto ; GtkIMHtmlLink * link = g_new ( GtkIMHtmlLink , 1 ); link -> imhtml = g_object_ref ( imhtml ); link -> url = g_strdup ( g_object_get_data ( G_OBJECT ( tag ), "link_url" )); link -> tag = g_object_ref ( tag ); /* Don't want the tooltip around if user right-clicked on link */ if ( GTK_IMHTML ( imhtml ) -> tip_window ) { gtk_widget_destroy ( GTK_IMHTML ( imhtml ) -> tip_window ); GTK_IMHTML ( imhtml ) -> tip_window = NULL ; if ( GTK_IMHTML ( imhtml ) -> tip_timer ) { g_source_remove ( GTK_IMHTML ( imhtml ) -> tip_timer ); GTK_IMHTML ( imhtml ) -> tip_timer = 0 ; if ( GTK_IMHTML ( imhtml ) -> editable ) gdk_window_set_cursor ( event_button -> window , GTK_IMHTML ( imhtml ) -> text_cursor ); gdk_window_set_cursor ( event_button -> window , GTK_IMHTML ( imhtml ) -> arrow_cursor ); g_object_set_data_full ( G_OBJECT ( menu ), "x-imhtml-url-data" , link , ( GDestroyNotify ) gtk_imhtml_link_destroy ); proto = imhtml_find_protocol ( link -> url , FALSE ); if ( proto && proto -> context_menu ) { proto -> context_menu ( GTK_IMHTML ( link -> imhtml ), link , menu ); children = gtk_container_get_children ( GTK_CONTAINER ( menu )); GtkWidget * item = gtk_menu_item_new_with_label ( _ ( "No actions available" )); gtk_widget_set_sensitive ( item , FALSE ); gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item ); gtk_widget_show_all ( menu ); gtk_menu_popup ( GTK_MENU ( menu ), NULL , NULL , NULL , NULL , event_button -> button , event_button -> time ); if ( event -> type == GDK_BUTTON_PRESS && event_button -> button == 3 ) return TRUE ; /* Clicking the right mouse button on a link shouldn't be caught by the regular GtkTextView menu */ return FALSE ; /* Let clicks go through if we didn't catch anything */ gtk_text_view_drag_motion ( GtkWidget * widget , GdkDragAction suggested_action = 0 ; if ( gtk_drag_dest_find_target ( widget , context , NULL ) == GDK_NONE ) { /* can't accept any of the offered targets */ GtkWidget * source_widget ; suggested_action = gdk_drag_context_get_suggested_action ( context ); source_widget = gtk_drag_get_source_widget ( context ); if ( source_widget == widget ) { /* Default to MOVE, unless the user has * pressed ctrl or alt to affect available actions if (( gdk_drag_context_get_actions ( context ) & GDK_ACTION_MOVE ) != 0 ) suggested_action = GDK_ACTION_MOVE ; gdk_drag_status ( context , suggested_action , time ); /* TRUE return means don't propagate the drag motion to parent * widgets that may also be drop sites. gtk_imhtml_link_drop_cb ( GtkWidget * widget , GdkDragContext * context , gint x , gint y , guint time , gpointer user_data ) GdkAtom target = gtk_drag_dest_find_target ( widget , context , NULL ); gtk_drag_get_data ( widget , context , target , time ); gtk_drag_finish ( context , FALSE , FALSE , time ); gtk_imhtml_link_drag_rcv_cb ( GtkWidget * widget , GdkDragContext * dc , guint x , guint y , GtkSelectionData * sd , guint info , guint t , GtkIMHtml * imhtml ) char * text = ( char * ) gtk_selection_data_get_data ( sd ); GtkTextMark * mark = gtk_text_buffer_get_insert ( imhtml -> text_buffer ); gint length = gtk_selection_data_get_length ( sd ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , mark ); if ( gtk_imhtml_get_editable ( imhtml ) && text ) { case GTK_IMHTML_DRAG_URL : /* TODO: Is it really ok to change sd->data...? */ purple_str_strip_char ( text , '\r' ); links = g_strsplit ( text , " \n " , 0 ); while (( link = links [ i ]) != NULL ) { if ( gtk_imhtml_is_protocol ( link )) { gtk_imhtml_insert_link ( imhtml , mark , link , label ); } else if ( * link == '\0' ) { /* Special reasons, aka images being put in via other tag, etc. */ /* ... don't pretend we handled it if we didn't */ gtk_drag_finish ( dc , FALSE , FALSE , t ); case GTK_IMHTML_DRAG_HTML : /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.' * as explained by this comment in gtkhtml: * FIXME This hack decides the charset of the selection. It seems that * mozilla/netscape alway use ucs2 for text/html * and openoffice.org seems to always use utf8 so we try to validate * the string as utf8 and if that fails we assume it is ucs2 * See also the comment on text/html here: * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html if ( length >= 2 && ! g_utf8_validate ( text , length - 1 , NULL )) { utf8 = utf16_to_utf8_with_bom_check ( text , length ); purple_debug_warning ( "gtkimhtml" , "g_convert from UTF-16 failed in drag_rcv_cb \n " ); } else if ( ! ( * text ) || ! g_utf8_validate ( text , -1 , NULL )) { purple_debug_warning ( "gtkimhtml" , "empty string or invalid UTF-8 in drag_rcv_cb \n " ); gtk_imhtml_insert_html_at_iter ( imhtml , utf8 ? utf8 : text , 0 , & iter ); case GTK_IMHTML_DRAG_TEXT : if ( ! ( * text ) || ! g_utf8_validate ( text , -1 , NULL )) { purple_debug_warning ( "gtkimhtml" , "empty string or invalid UTF-8 in drag_rcv_cb \n " ); char * tmp = g_markup_escape_text ( text , -1 ); gtk_imhtml_insert_html_at_iter ( imhtml , tmp , 0 , & iter ); gtk_drag_finish ( dc , FALSE , FALSE , t ); gtk_drag_finish ( dc , TRUE , gdk_drag_context_get_actions ( dc ) == GDK_ACTION_MOVE , t ); gtk_drag_finish ( dc , FALSE , FALSE , t ); static void gtk_smiley_tree_remove ( GtkSmileyTree * tree , const gchar * x = smiley -> smile ; pos = strchr ( t -> values -> str , * x ); t = t -> children [ pos - t -> values -> str ]; gtk_smiley_tree_lookup ( GtkSmileyTree * tree , if ( * x == '&' && ( amp = purple_markup_unescape_entity ( x , & alen ))) { /* Make sure all chars of the unescaped value match */ pos = strchr ( t -> values -> str , * amp ); t = t -> children [ GPOINTER_TO_INT ( pos ) - GPOINTER_TO_INT ( t -> values -> str )]; pos = strchr ( t -> values -> str , * amp ); else if ( * x == '<' ) /* Because we're all WYSIWYG now, a '<' * char should only appear as the start of a tag. Perhaps a safer (but costlier) * check would be to call gtk_imhtml_is_tag on it */ pos = strchr ( t -> values -> str , * x ); t = t -> children [ GPOINTER_TO_INT ( pos ) - GPOINTER_TO_INT ( t -> values -> str )]; gtk_imhtml_disassociate_smiley_foreach ( gpointer key , gpointer value , GtkSmileyTree * tree = ( GtkSmileyTree * ) value ; GtkIMHtmlSmiley * smiley = ( GtkIMHtmlSmiley * ) user_data ; gtk_smiley_tree_remove ( tree , smiley ); gtk_imhtml_disconnect_smiley ( GtkIMHtml * imhtml , GtkIMHtmlSmiley * smiley ) g_signal_handlers_disconnect_matched ( imhtml , G_SIGNAL_MATCH_DATA , 0 , 0 , gtk_imhtml_disassociate_smiley ( GtkIMHtmlSmiley * smiley ) gtk_smiley_tree_remove ( smiley -> imhtml -> default_smilies , smiley ); g_hash_table_foreach ( smiley -> imhtml -> smiley_data , gtk_imhtml_disassociate_smiley_foreach , smiley ); g_signal_handlers_disconnect_matched ( smiley -> imhtml , G_SIGNAL_MATCH_DATA , 0 , 0 , NULL , NULL , smiley ); gtk_imhtml_associate_smiley ( GtkIMHtml * imhtml , g_return_if_fail ( imhtml != NULL ); g_return_if_fail ( GTK_IS_IMHTML ( imhtml )); tree = imhtml -> default_smilies ; else if ( ! ( tree = g_hash_table_lookup ( imhtml -> smiley_data , sml ))) { tree = gtk_smiley_tree_new (); g_hash_table_insert ( imhtml -> smiley_data , g_strdup ( sml ), tree ); /* need to disconnect old imhtml, if there is one */ g_signal_handlers_disconnect_matched ( smiley -> imhtml , G_SIGNAL_MATCH_DATA , 0 , 0 , NULL , NULL , smiley ); gtk_smiley_tree_insert ( tree , smiley ); /* connect destroy signal for the imhtml */ g_signal_connect ( imhtml , "destroy" , G_CALLBACK ( gtk_imhtml_disconnect_smiley ), gtk_imhtml_is_smiley ( GtkIMHtml * imhtml , GtkIMHtmlFontDetail * font ; sml = imhtml -> protocol_name ; if ( ! sml || ! ( tree = g_hash_table_lookup ( imhtml -> smiley_data , sml ))) tree = imhtml -> default_smilies ; * len = gtk_smiley_tree_lookup ( tree , text ); static GtkIMHtmlSmiley * gtk_imhtml_smiley_get_from_tree ( GtkSmileyTree * t , const gchar * text ) pos = strchr ( t -> values -> str , * x ); t = t -> children [ GPOINTER_TO_INT ( pos ) - GPOINTER_TO_INT ( t -> values -> str )]; gtk_imhtml_smiley_get ( GtkIMHtml * imhtml , const gchar * sml , const gchar * text ) /* Look for custom smileys first */ ret = gtk_imhtml_smiley_get_from_tree ( g_hash_table_lookup ( imhtml -> smiley_data , sml ), text ); /* Fall back to check for default smileys */ return gtk_imhtml_smiley_get_from_tree ( imhtml -> default_smilies , text ); static GdkPixbufAnimation * gtk_smiley_get_image ( GtkIMHtmlSmiley * smiley ) smiley -> icon = gdk_pixbuf_animation_new_from_file ( smiley -> file , NULL ); } else if ( smiley -> loader ) { smiley -> icon = gdk_pixbuf_loader_get_animation ( smiley -> loader ); g_object_ref ( G_OBJECT ( smiley -> icon )); #define VALID_TAG(x) do { \ if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \ if (tag) *tag = g_strndup (string, strlen (x)); \ if (len) *len = strlen (x) + 1; \ #define VALID_OPT_TAG(x) do { \ if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \ const gchar *c = string + strlen (x " "); \ gboolean quote = FALSE; \ if (*c == '"' || *c == '\'') { \ if (quote && (*c == e)) \ } else if (!quote && (*c == '>')) \ if (tag) *tag = g_strndup (string, c - string); \ if (len) *len = c - string + 1; \ gtk_imhtml_is_tag ( const gchar * string , if ( ! ( close = strchr ( string , '>' ))) VALID_TAG ( "/UNDERLINE" ); VALID_TAG ( "BR/" ); /* hack until gtkimhtml handles things better */ if ( ! g_ascii_strncasecmp ( string , "!--" , strlen ( "!--" ))) { gchar * e = strstr ( string + strlen ( "!--" ), "-->" ); * len = e - string + strlen ( "-->" ); * tag = g_strndup ( string + strlen ( "!--" ), * len - strlen ( "!---->" )); * len = close - string + 1 ; * tag = g_strndup ( string , close - string ); gtk_imhtml_get_html_opt ( gchar * tag , while ( g_ascii_strncasecmp ( t , opt , strlen ( opt ))) { while ( * t && ! (( * t == ' ' ) && ! quote )) { while ( * t && ( * t == ' ' )) t ++ ; if ( ! g_ascii_strncasecmp ( t , opt , strlen ( opt ))) { if (( * t == '\"' ) || ( * t == '\'' )) { while ( * e && ( * e != * ( t - 1 ))) e ++ ; val = g_strndup ( a , e - a ); while ( * e && ! isspace (( gint ) * e )) e ++ ; val = g_strndup ( a , e - a ); if (( c = purple_markup_unescape_entity ( e , & len ))) { ret = g_string_append ( ret , c ); gunichar uni = g_utf8_get_char ( e ); ret = g_string_append_unichar ( ret , uni ); return g_string_free ( ret , FALSE ); /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so the caller knows how long the protocol string is. */ static int gtk_imhtml_is_protocol ( const char * text ) GtkIMHtmlProtocol * proto = imhtml_find_protocol ( text , FALSE ); return proto ? proto -> length : 0 ; static gboolean smooth_scroll_cb ( gpointer data ); <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :) [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/> [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_new [20:00] <marv> Robot101: so how does the image get passed to serv_got_im() and serv_send_im()? just as the <img id="#" and then the prpl looks it up from the store? [20:00] <KingAnt> marv: Right Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that images can be looked up like that, instead of passing a GSList of them. void gtk_imhtml_append_text_with_images ( GtkIMHtml * imhtml , GtkIMHtmlOptions options , GtkTextIter iter , ins , sel ; int ins_offset = 0 , sel_offset = 0 ; gboolean fixins = FALSE , fixsel = FALSE ; g_return_if_fail ( imhtml != NULL ); g_return_if_fail ( GTK_IS_IMHTML ( imhtml )); g_return_if_fail ( text != NULL ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & iter ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & ins , gtk_text_buffer_get_insert ( imhtml -> text_buffer )); if ( gtk_text_iter_equal ( & iter , & ins ) && gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , NULL , NULL )) { ins_offset = gtk_text_iter_get_offset ( & ins ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & sel , gtk_text_buffer_get_selection_bound ( imhtml -> text_buffer )); if ( gtk_text_iter_equal ( & iter , & sel ) && gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , NULL , NULL )) { sel_offset = gtk_text_iter_get_offset ( & sel ); if ( ! ( options & GTK_IMHTML_NO_SCROLL )) { gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( imhtml ), & rect ); gtk_text_view_get_line_yrange ( GTK_TEXT_VIEW ( imhtml ), & iter , & y , & height ); if ((( y + height ) - ( rect . y + rect . height )) > height && gtk_text_buffer_get_char_count ( imhtml -> text_buffer )) { /* If we are in the middle of smooth-scrolling, then take a scroll step. * If we are not in the middle of smooth-scrolling, that means we were * not looking at the end of the buffer before the new text was added, smooth_scroll_cb ( imhtml ); options |= GTK_IMHTML_NO_SCROLL ; gtk_imhtml_insert_html_at_iter ( imhtml , text , options , & iter ); gtk_text_buffer_get_iter_at_offset ( imhtml -> text_buffer , & ins , ins_offset ); gtk_text_buffer_move_mark ( imhtml -> text_buffer , gtk_text_buffer_get_insert ( imhtml -> text_buffer ), & ins ); gtk_text_buffer_get_iter_at_offset ( imhtml -> text_buffer , & sel , sel_offset ); gtk_text_buffer_move_mark ( imhtml -> text_buffer , gtk_text_buffer_get_selection_bound ( imhtml -> text_buffer ), & sel ); if ( ! ( options & GTK_IMHTML_NO_SCROLL )) { gtk_imhtml_scroll_to_end ( imhtml , ( options & GTK_IMHTML_USE_SMOOTHSCROLLING )); #define MAX_SCROLL_TIME 0.4 /* seconds */ #define SCROLL_DELAY 33 /* milliseconds */ * Smoothly scroll a GtkIMHtml. * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom. static gboolean smooth_scroll_cb ( gpointer data ) GtkIMHtml * imhtml = data ; GtkAdjustment * adj = gtk_text_view_get_vadjustment ( GTK_TEXT_VIEW ( imhtml )); gdouble max_val = gtk_adjustment_get_upper ( adj ) - gtk_adjustment_get_page_size ( adj ); gdouble scroll_val = gtk_adjustment_get_value ( adj ) + (( max_val - gtk_adjustment_get_value ( adj )) / 3 ); g_return_val_if_fail ( imhtml -> scroll_time != NULL , FALSE ); if ( g_timer_elapsed ( imhtml -> scroll_time , NULL ) > MAX_SCROLL_TIME || scroll_val >= max_val ) { /* time's up. jump to the end and kill the timer */ gtk_adjustment_set_value ( adj , max_val ); g_timer_destroy ( imhtml -> scroll_time ); imhtml -> scroll_time = NULL ; g_source_remove ( imhtml -> scroll_src ); /* scroll by 1/3rd the remaining distance */ gtk_adjustment_set_value ( adj , scroll_val ); static gboolean scroll_idle_cb ( gpointer data ) GtkIMHtml * imhtml = data ; GtkAdjustment * adj = gtk_text_view_get_vadjustment ( GTK_TEXT_VIEW ( imhtml )); gtk_adjustment_set_value ( adj , gtk_adjustment_get_upper ( adj ) - gtk_adjustment_get_page_size ( adj )); void gtk_imhtml_scroll_to_end ( GtkIMHtml * imhtml , gboolean smooth ) g_timer_destroy ( imhtml -> scroll_time ); g_source_remove ( imhtml -> scroll_src ); imhtml -> scroll_time = g_timer_new (); imhtml -> scroll_src = g_timeout_add_full ( G_PRIORITY_LOW , SCROLL_DELAY , smooth_scroll_cb , imhtml , NULL ); imhtml -> scroll_time = NULL ; imhtml -> scroll_src = g_idle_add_full ( G_PRIORITY_LOW , scroll_idle_cb , imhtml , NULL ); /* CSS colors are either rgb (x,y,z) or #hex * we need to convert to hex if it is RGB */ parse_css_color ( gchar * in_color ) if ( * tmp == 'r' && * ( ++ tmp ) == 'g' && * ( ++ tmp ) == 'b' && * ( ++ tmp )) { int rgbval [] = { 0 , 0 , 0 }; while ( * tmp && g_ascii_isspace ( * tmp )) /* We don't support rgba() */ purple_debug_warning ( "gtkimhtml" , "Invalid rgb CSS color in '%s'! \n " , in_color ); /* Skip any leading spaces */ while ( * tmp && g_ascii_isspace ( * tmp )) /* Find the subsequent contiguous digits */ while ( * tmp && g_ascii_isdigit ( * tmp )) rgbval [ count ] = atoi ( v_start ); while ( * tmp && g_ascii_isspace ( * tmp )) rgbval [ count ] = ( rgbval [ count ] / 100.0 ) * 255 ; purple_debug_warning ( "gtkimhtml" , "Invalid rgb CSS color in '%s'! \n " , in_color ); if ( rgbval [ count ] > 255 ) { } else if ( rgbval [ count ] < 0 ) { while ( * tmp && g_ascii_isspace ( * tmp )) return g_strdup_printf ( "#%02X%02X%02X" , rgbval [ 0 ], rgbval [ 1 ], rgbval [ 2 ]); void gtk_imhtml_insert_html_at_iter ( GtkIMHtml * imhtml , GtkIMHtmlOptions options , gint tlen , smilelen , wpos = 0 ; gboolean align_right = FALSE ; gboolean rtl_direction = FALSE ; GtkIMHtmlScalable * scalable = NULL ; g_return_if_fail ( imhtml != NULL ); g_return_if_fail ( GTK_IS_IMHTML ( imhtml )); g_return_if_fail ( text != NULL ); g_object_set_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_thismsg" , GINT_TO_POINTER ( 0 )); gtk_text_buffer_begin_user_action ( imhtml -> text_buffer ); if ( * c == '<' && gtk_imhtml_is_tag ( c + 1 , & tag , & tlen , & type )) { if ( ! ( options & GTK_IMHTML_NO_FORMATTING )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( bold == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_BOLD )) gtk_imhtml_toggle_bold ( imhtml ); if ( ! ( options & GTK_IMHTML_NO_FORMATTING )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( bold == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_BOLD ) && ! imhtml -> wbfo ) gtk_imhtml_toggle_bold ( imhtml ); if ( ! ( options & GTK_IMHTML_NO_FORMATTING )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( italics == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_ITALIC )) gtk_imhtml_toggle_italic ( imhtml ); if ( ! ( options & GTK_IMHTML_NO_FORMATTING )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( italics == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_ITALIC ) && ! imhtml -> wbfo ) gtk_imhtml_toggle_italic ( imhtml ); if ( ! ( options & GTK_IMHTML_NO_FORMATTING )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( underline == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_UNDERLINE )) gtk_imhtml_toggle_underline ( imhtml ); case 12 : /* /UNDERLINE */ if ( ! ( options & GTK_IMHTML_NO_FORMATTING )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( underline == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_UNDERLINE ) && ! imhtml -> wbfo ) gtk_imhtml_toggle_underline ( imhtml ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( strike == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_STRIKE )) gtk_imhtml_toggle_strike ( imhtml ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if (( strike == 0 ) && ( imhtml -> format_functions & GTK_IMHTML_STRIKE ) && ! imhtml -> wbfo ) gtk_imhtml_toggle_strike ( imhtml ); /* FIXME: reimpliment this */ /* FIXME: reimpliment this */ /* FIXME: reimplement this */ /* FIXME: reimplement this */ /* FIXME: reimplement this */ /* FIXME: reimplement this */ /* FIXME: what was this supposed to do anyway? */ /* FIXME: make this undo whatever 23 was supposed to do */ if ( options & GTK_IMHTML_NO_TITLE ) { struct scalable_data * sd = g_new ( struct scalable_data , 1 ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); sd -> scalable = scalable = gtk_imhtml_hr_new (); sd -> mark = gtk_text_buffer_create_mark ( imhtml -> text_buffer , NULL , iter , TRUE ); gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( imhtml ), & rect ); scalable -> add_to ( scalable , imhtml , iter ); minus = gtk_text_view_get_left_margin ( GTK_TEXT_VIEW ( imhtml )) + gtk_text_view_get_right_margin ( GTK_TEXT_VIEW ( imhtml )); scalable -> scale ( scalable , rect . width - minus , rect . height ); imhtml -> scalables = g_list_append ( imhtml -> scalables , sd ); if ( fonts && ! imhtml -> wbfo ) { GtkIMHtmlFontDetail * font = fonts -> data ; gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* NEW_BIT (NEW_TEXT_BIT); */ if ( font -> face && ( imhtml -> format_functions & GTK_IMHTML_FACE )) { gtk_imhtml_toggle_fontface ( imhtml , NULL ); if ( font -> fore && ( imhtml -> format_functions & GTK_IMHTML_FORECOLOR )) { gtk_imhtml_toggle_forecolor ( imhtml , NULL ); if ( font -> back && ( imhtml -> format_functions & GTK_IMHTML_BACKCOLOR )) { gtk_imhtml_toggle_backcolor ( imhtml , NULL ); if (( font -> size != 3 ) && ( imhtml -> format_functions & ( GTK_IMHTML_GROW | GTK_IMHTML_SHRINK ))) gtk_imhtml_font_set_size ( imhtml , 3 ); fonts = g_slist_remove ( fonts , font ); GtkIMHtmlFontDetail * font = fonts -> data ; if ( font -> face && ( imhtml -> format_functions & GTK_IMHTML_FACE )) gtk_imhtml_toggle_fontface ( imhtml , font -> face ); if ( font -> fore && ( imhtml -> format_functions & GTK_IMHTML_FORECOLOR )) gtk_imhtml_toggle_forecolor ( imhtml , font -> fore ); if ( font -> back && ( imhtml -> format_functions & GTK_IMHTML_BACKCOLOR )) gtk_imhtml_toggle_backcolor ( imhtml , font -> back ); if (( font -> size != 3 ) && ( imhtml -> format_functions & ( GTK_IMHTML_GROW | GTK_IMHTML_SHRINK ))) gtk_imhtml_font_set_size ( imhtml , font -> size ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); gtk_imhtml_toggle_link ( imhtml , NULL ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); gtk_imhtml_toggle_background ( imhtml , NULL ); case 43 : /* FONT (opt) */ gchar * color , * back , * face , * size , * sml ; GtkIMHtmlFontDetail * font , * oldfont = NULL ; color = gtk_imhtml_get_html_opt ( tag , "COLOR=" ); back = gtk_imhtml_get_html_opt ( tag , "BACK=" ); face = gtk_imhtml_get_html_opt ( tag , "FACE=" ); size = gtk_imhtml_get_html_opt ( tag , "SIZE=" ); sml = gtk_imhtml_get_html_opt ( tag , "SML=" ); if ( ! ( color || back || face || size || sml )) gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); font = g_new0 ( GtkIMHtmlFontDetail , 1 ); if ( color && ! ( options & GTK_IMHTML_NO_COLOURS ) && ( imhtml -> format_functions & GTK_IMHTML_FORECOLOR )) { gtk_imhtml_toggle_forecolor ( imhtml , font -> fore ); if ( back && ! ( options & GTK_IMHTML_NO_COLOURS ) && ( imhtml -> format_functions & GTK_IMHTML_BACKCOLOR )) { gtk_imhtml_toggle_backcolor ( imhtml , font -> back ); if ( face && ! ( options & GTK_IMHTML_NO_FONTS ) && ( imhtml -> format_functions & GTK_IMHTML_FACE )) { gtk_imhtml_toggle_fontface ( imhtml , font -> face ); if ( oldfont && oldfont -> sml ) font -> sml = g_strdup ( oldfont -> sml ); if ( size && ! ( options & GTK_IMHTML_NO_SIZES ) && ( imhtml -> format_functions & ( GTK_IMHTML_GROW | GTK_IMHTML_SHRINK ))) { sscanf ( size + 1 , "%hd" , & font -> size ); } else if ( * size == '-' ) { sscanf ( size + 1 , "%hd" , & font -> size ); font -> size = MAX ( 0 , 3 - font -> size ); } else if ( isdigit ( * size )) { sscanf ( size , "%hd" , & font -> size ); font -> size = oldfont -> size ; if (( imhtml -> format_functions & ( GTK_IMHTML_GROW | GTK_IMHTML_SHRINK )) && ( font -> size != 3 || ( oldfont && oldfont -> size == 3 ))) gtk_imhtml_font_set_size ( imhtml , font -> size ); fonts = g_slist_prepend ( fonts , font ); case 44 : /* BODY (opt) */ if ( ! ( options & GTK_IMHTML_NO_COLOURS )) { char * bgcolor = gtk_imhtml_get_html_opt ( tag , "BGCOLOR=" ); if ( bgcolor && ( imhtml -> format_functions & GTK_IMHTML_BACKCOLOR )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* NEW_BIT(NEW_TEXT_BIT); */ gtk_imhtml_toggle_background ( imhtml , bg ); gchar * href = gtk_imhtml_get_html_opt ( tag , "HREF=" ); if ( href && ( imhtml -> format_functions & GTK_IMHTML_LINK )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); gtk_imhtml_toggle_link ( imhtml , href ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if ( ! ( imhtml -> format_functions & GTK_IMHTML_IMAGE )) id = gtk_imhtml_get_html_opt ( tag , "ID=" ); gtk_imhtml_insert_image_at_iter ( imhtml , atoi ( id ), iter ); src = gtk_imhtml_get_html_opt ( tag , "SRC=" ); alt = gtk_imhtml_get_html_opt ( tag , "ALT=" ); gtk_imhtml_toggle_link ( imhtml , src ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , alt ? alt : src , -1 ); gtk_imhtml_toggle_link ( imhtml , NULL ); case 49 : /* HTML (opt) */ case 56 : /* SPAN (opt) */ /* Inline CSS Support - Douglas Thrift * text-decoration: underline gchar * style , * color , * background , * family , * size , * direction , * alignment ; GtkIMHtmlFontDetail * font , * oldfont = NULL ; style = gtk_imhtml_get_html_opt ( tag , "style=" ); color = purple_markup_get_css_property ( style , "color" ); background = purple_markup_get_css_property ( style , "background" ); family = purple_markup_get_css_property ( style , "font-family" ); size = purple_markup_get_css_property ( style , "font-size" ); textdec = purple_markup_get_css_property ( style , "text-decoration" ); weight = purple_markup_get_css_property ( style , "font-weight" ); direction = purple_markup_get_css_property ( style , "direction" ); alignment = purple_markup_get_css_property ( style , "text-align" ); if ( ! ( color || family || size || background || textdec || weight || direction || alignment )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* NEW_BIT (NEW_TEXT_BIT); */ /* Bi-Directional text support */ if ( direction && ( ! g_ascii_strncasecmp ( direction , "RTL" , 3 ))) { /* insert RLE character to set direction */ gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); if ( alignment && ( ! g_ascii_strncasecmp ( alignment , "RIGHT" , 5 ))) { align_line = gtk_text_iter_get_line ( iter ); font = g_new0 ( GtkIMHtmlFontDetail , 1 ); if ( color && ! ( options & GTK_IMHTML_NO_COLOURS ) && ( imhtml -> format_functions & GTK_IMHTML_FORECOLOR )) { font -> fore = parse_css_color ( color ); gtk_imhtml_toggle_forecolor ( imhtml , font -> fore ); if ( oldfont && oldfont -> fore ) font -> fore = g_strdup ( oldfont -> fore ); if ( background && ! ( options & GTK_IMHTML_NO_COLOURS ) && ( imhtml -> format_functions & GTK_IMHTML_BACKCOLOR )) { font -> back = parse_css_color ( background ); gtk_imhtml_toggle_backcolor ( imhtml , font -> back ); if ( oldfont && oldfont -> back ) font -> back = g_strdup ( oldfont -> back ); if ( family && ! ( options & GTK_IMHTML_NO_FONTS ) && ( imhtml -> format_functions & GTK_IMHTML_FACE )) { gtk_imhtml_toggle_fontface ( imhtml , font -> face ); if ( oldfont && oldfont -> face ) font -> face = g_strdup ( oldfont -> face ); if ( font -> face && ( atoi ( font -> face ) > 100 )) { /* Maybe it sets a max size on the font face? I seem to * remember bad things happening if the font size was font -> face = g_strdup ( "100" ); if ( oldfont && oldfont -> sml ) font -> sml = g_strdup ( oldfont -> sml ); if ( size && ! ( options & GTK_IMHTML_NO_SIZES ) && ( imhtml -> format_functions & ( GTK_IMHTML_SHRINK | GTK_IMHTML_GROW ))) { if ( g_ascii_strcasecmp ( size , "xx-small" ) == 0 ) else if ( g_ascii_strcasecmp ( size , "smaller" ) == 0 || g_ascii_strcasecmp ( size , "x-small" ) == 0 ) else if ( g_ascii_strcasecmp ( size , "medium" ) == 0 ) else if ( g_ascii_strcasecmp ( size , "large" ) == 0 || g_ascii_strcasecmp ( size , "larger" ) == 0 ) else if ( g_ascii_strcasecmp ( size , "x-large" ) == 0 ) else if ( g_ascii_strcasecmp ( size , "xx-large" ) == 0 ) * TODO: Handle other values, like percentages, or * lengths specified as em, ex, px, in, cm, mm, pt * or pc. Or even better, use an actual HTML gtk_imhtml_font_set_size ( imhtml , font -> size ); font -> size = oldfont -> size ; font -> underline = oldfont -> underline ; if ( textdec && font -> underline != 1 && g_ascii_strcasecmp ( textdec , "underline" ) == 0 && ( imhtml -> format_functions & GTK_IMHTML_UNDERLINE ) && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_underline ( imhtml ); font -> strike = oldfont -> strike ; if ( textdec && font -> strike != 1 && g_ascii_strcasecmp ( textdec , "line-through" ) == 0 && ( imhtml -> format_functions & GTK_IMHTML_STRIKE ) && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_strike ( imhtml ); font -> bold = oldfont -> bold ; if ( ! g_ascii_strcasecmp ( weight , "normal" )) { } else if ( ! g_ascii_strcasecmp ( weight , "bold" )) { } else if ( ! g_ascii_strcasecmp ( weight , "bolder" )) { } else if ( ! g_ascii_strcasecmp ( weight , "lighter" )) { if ((( font -> bold && oldfont && ! oldfont -> bold ) || ( oldfont && oldfont -> bold && ! font -> bold ) || ( font -> bold && ! oldfont )) && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_bold ( imhtml ); fonts = g_slist_prepend ( fonts , font ); /* Inline CSS Support - Douglas Thrift */ if ( fonts && ! imhtml -> wbfo ) { GtkIMHtmlFontDetail * oldfont = NULL ; GtkIMHtmlFontDetail * font = fonts -> data ; gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* NEW_BIT (NEW_TEXT_BIT); */ fonts = g_slist_remove ( fonts , font ); gtk_imhtml_font_set_size ( imhtml , 3 ); if ( font -> underline && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_underline ( imhtml ); if ( font -> strike && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_strike ( imhtml ); if ( font -> bold && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_bold ( imhtml ); if ( ! ( options & GTK_IMHTML_NO_FONTS )) gtk_imhtml_toggle_fontface ( imhtml , NULL ); if ( ! ( options & GTK_IMHTML_NO_COLOURS )) gtk_imhtml_toggle_forecolor ( imhtml , NULL ); if ( ! ( options & GTK_IMHTML_NO_COLOURS )) gtk_imhtml_toggle_backcolor ( imhtml , NULL ); if (( font -> size != oldfont -> size ) && ! ( options & GTK_IMHTML_NO_SIZES )) gtk_imhtml_font_set_size ( imhtml , oldfont -> size ); if (( font -> underline != oldfont -> underline ) && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_underline ( imhtml ); if (( font -> strike != oldfont -> strike ) && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_strike ( imhtml ); if ((( font -> bold && ! oldfont -> bold ) || ( oldfont -> bold && ! font -> bold )) && ! ( options & GTK_IMHTML_NO_FORMATTING )) gtk_imhtml_toggle_bold ( imhtml ); if ( font -> face && ( ! oldfont -> face || strcmp ( font -> face , oldfont -> face ) != 0 ) && ! ( options & GTK_IMHTML_NO_FONTS )) gtk_imhtml_toggle_fontface ( imhtml , oldfont -> face ); if ( font -> fore && ( ! oldfont -> fore || strcmp ( font -> fore , oldfont -> fore ) != 0 ) && ! ( options & GTK_IMHTML_NO_COLOURS )) gtk_imhtml_toggle_forecolor ( imhtml , oldfont -> fore ); if ( font -> back && ( ! oldfont -> back || strcmp ( font -> back , oldfont -> back ) != 0 ) && ! ( options & GTK_IMHTML_NO_COLOURS )) gtk_imhtml_toggle_backcolor ( imhtml , oldfont -> back ); /* NEW_BIT (NEW_TEXT_BIT); */ gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); wpos = g_snprintf ( ws , len , "%s" , tag ); gtk_text_buffer_insert_with_tags_by_name ( imhtml -> text_buffer , iter , ws , wpos , "comment" , NULL ); if ( imhtml -> show_comments && ! ( options & GTK_IMHTML_NO_COMMENTS )) { wpos = g_snprintf ( ws , len , "%s" , tag ); gtk_text_buffer_insert_with_tags_by_name ( imhtml -> text_buffer , iter , ws , wpos , "comment" , NULL ); /* NEW_BIT (NEW_COMMENT_BIT); */ g_free ( tag ); /* This was allocated back in VALID_TAG() */ } else if ( imhtml -> edit . link == NULL && ! ( options & GTK_IMHTML_NO_SMILEY ) && gtk_imhtml_is_smiley ( imhtml , fonts , c , & smilelen )) { sml = imhtml -> protocol_name ; gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); wpos = g_snprintf ( ws , smilelen + 1 , "%s" , c ); gtk_imhtml_insert_smiley_at_iter ( imhtml , sml , ws , iter ); } else if ( * c == '&' && ( amp = purple_markup_unescape_entity ( c , & tlen ))) { if ( ! ( options & GTK_IMHTML_NO_NEWLINE )) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* NEW_BIT (NEW_TEXT_BIT); */ } else if ( ! br ) { /* Don't insert a space immediately after an HTML break */ /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary. * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand? gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); } else if (( pos == 0 || wpos == 0 || isspace ( * ( c - 1 ))) && ( len_protocol = gtk_imhtml_is_protocol ( c )) > 0 && c [ len_protocol ] && ! isspace ( c [ len_protocol ]) && ( c [ len_protocol ] != '<' || ! gtk_imhtml_is_tag ( c + 1 , NULL , NULL , NULL ))) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* Skip the next len_protocol characters, but * make sure they're copied into the ws array. if ( ! imhtml -> edit . link && ( imhtml -> format_functions & GTK_IMHTML_LINK )) { while ( * c && ! isspace (( int ) * c ) && ( * c != '<' || ! gtk_imhtml_is_tag ( c + 1 , NULL , NULL , NULL ))) { if ( * c == '&' && ( amp = purple_markup_unescape_entity ( c , & tlen ))) { gtk_imhtml_toggle_link ( imhtml , ws ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); gtk_imhtml_toggle_link ( imhtml , NULL ); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , ws , wpos ); /* NEW_BIT(NEW_TEXT_BIT); */ /* insert RLM+LRM at beginning of the line to set alignment */ gtk_text_iter_set_line ( & line_iter , align_line ); /* insert RLM character to set alignment */ /* insert LRM character to set direction */ /* (alignment=right and direction=LTR) */ gtk_text_buffer_insert ( imhtml -> text_buffer , & line_iter , ws , wpos ); gtk_text_buffer_get_end_iter ( gtk_text_iter_get_buffer ( & line_iter ), iter ); GtkIMHtmlFontDetail * font = fonts -> data ; fonts = g_slist_remove ( fonts , font ); gtk_imhtml_close_tags ( imhtml , iter ); object = g_object_ref ( G_OBJECT ( imhtml )); g_signal_emit ( object , signals [ UPDATE_FORMAT ], 0 ); gtk_text_buffer_end_user_action ( imhtml -> text_buffer ); void gtk_imhtml_remove_smileys ( GtkIMHtml * imhtml ) g_hash_table_destroy ( imhtml -> smiley_data ); gtk_smiley_tree_destroy ( imhtml -> default_smilies ); imhtml -> smiley_data = g_hash_table_new_full ( g_str_hash , g_str_equal , g_free , ( GDestroyNotify ) gtk_smiley_tree_destroy ); imhtml -> default_smilies = gtk_smiley_tree_new (); void gtk_imhtml_show_comments ( GtkIMHtml * imhtml , tag = gtk_text_tag_table_lookup ( gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ), "comment" ); g_object_set ( G_OBJECT ( tag ), "invisible" , ! show , NULL ); imhtml -> show_comments = show ; gtk_imhtml_get_protocol_name ( GtkIMHtml * imhtml ) { return imhtml -> protocol_name ; gtk_imhtml_set_protocol_name ( GtkIMHtml * imhtml , const gchar * protocol_name ) { g_free ( imhtml -> protocol_name ); imhtml -> protocol_name = g_strdup ( protocol_name ); gtk_imhtml_delete ( GtkIMHtml * imhtml , GtkTextIter * start , GtkTextIter * end ) { GObject * object = g_object_ref ( G_OBJECT ( imhtml )); gtk_text_buffer_get_start_iter ( imhtml -> text_buffer , & i_s ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & i_e ); struct scalable_data * sd = l -> data ; gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , if ( gtk_text_iter_in_range ( & i , start , end )) { GtkIMHtmlScalable * scale = GTK_IMHTML_SCALABLE ( sd -> scalable ); imhtml -> scalables = g_list_delete_link ( imhtml -> scalables , l ); struct im_image_data * img_data = sl -> data ; gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , if ( gtk_text_iter_in_range ( & i , start , end )) { if ( imhtml -> funcs -> image_unref ) imhtml -> funcs -> image_unref ( img_data -> id ); imhtml -> im_images = g_slist_delete_link ( imhtml -> im_images , sl ); gtk_text_buffer_delete ( imhtml -> text_buffer , start , end ); g_object_set_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_total" , GINT_TO_POINTER ( 0 )); void gtk_imhtml_page_up ( GtkIMHtml * imhtml ) gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( imhtml ), & rect ); gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( imhtml ), & iter , rect . x , gtk_text_view_scroll_to_iter ( GTK_TEXT_VIEW ( imhtml ), & iter , 0 , TRUE , 0 , 0 ); void gtk_imhtml_page_down ( GtkIMHtml * imhtml ) gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( imhtml ), & rect ); gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( imhtml ), & iter , rect . x , gtk_text_view_scroll_to_iter ( GTK_TEXT_VIEW ( imhtml ), & iter , 0 , TRUE , 0 , 0 ); * Destroys and frees a GTK+ IM/HTML scalable image. * @param scale The GTK+ IM/HTML scalable. static void gtk_imhtml_image_free ( GtkIMHtmlScalable * scale ) GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) scale ; g_object_unref ( image -> pixbuf ); gtk_widget_destroy ( image -> filesel ); * Destroys and frees a GTK+ IM/HTML scalable animation. * @param scale The GTK+ IM/HTML scalable. static void gtk_imhtml_animation_free ( GtkIMHtmlScalable * scale ) GtkIMHtmlAnimation * animation = ( GtkIMHtmlAnimation * ) scale ; if ( animation -> timer > 0 ) g_source_remove ( animation -> timer ); if ( animation -> iter != NULL ) g_object_unref ( animation -> iter ); g_object_unref ( animation -> anim ); gtk_imhtml_image_free ( scale ); image_save_yes_cb ( GtkIMHtmlImageSave * save , const char * filename ) GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) save -> image ; gtk_widget_destroy ( image -> filesel ); if ( save -> data && save -> datasize ) { g_file_set_contents ( filename , save -> data , save -> datasize , & error ); GSList * formats = gdk_pixbuf_get_formats (); GdkPixbufFormat * format = formats -> data ; gchar ** extensions = gdk_pixbuf_format_get_extensions ( format ); while ( gdk_pixbuf_format_is_writable ( format ) && extensions && extensions [ 0 ]){ gchar * fmt_ext = extensions [ 0 ]; const gchar * file_ext = filename + strlen ( filename ) - strlen ( fmt_ext ); if ( ! g_ascii_strcasecmp ( fmt_ext , file_ext )){ type = gdk_pixbuf_format_get_name ( format ); /* If I can't find a valid type, I will just tell the user about it and then assume GtkWidget * dialog = gtk_message_dialog_new_with_markup ( NULL , 0 , GTK_MESSAGE_ERROR , GTK_BUTTONS_OK , _ ( "<span size='larger' weight='bold'>Unrecognized file type</span> \n\n Defaulting to PNG." )); g_signal_connect_swapped ( dialog , "response" , G_CALLBACK ( gtk_widget_destroy ), dialog ); dirname = g_path_get_dirname ( filename ); basename = g_path_get_basename ( filename ); tmp = strrchr ( basename , '.' ); newfilename = g_strdup_printf ( "%s" G_DIR_SEPARATOR_S "%s.png" , dirname , basename ); * We're able to save the file in it's original format, so we * can use the original file name. newfilename = g_strdup ( filename ); gdk_pixbuf_save ( image -> pixbuf , newfilename , type , & error , NULL ); GtkWidget * dialog = gtk_message_dialog_new_with_markup ( NULL , 0 , GTK_MESSAGE_ERROR , GTK_BUTTONS_OK , _ ( "<span size='larger' weight='bold'>Error saving image</span> \n\n %s" ), error -> message ); g_signal_connect_swapped ( dialog , "response" , G_CALLBACK ( gtk_widget_destroy ), dialog ); image_save_check_if_exists_cb ( GtkWidget * widget , gint response , GtkIMHtmlImageSave * save ) GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) save -> image ; if ( response != GTK_RESPONSE_ACCEPT ) { gtk_widget_destroy ( widget ); filename = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER ( widget )); * XXX - We should probably prompt the user to determine if they really * want to overwrite the file or not. However, I don't feel like doing * that, so we're just always going to overwrite if the file exists. if (g_file_test(filename, G_FILE_TEST_EXISTS)) { image_save_yes_cb(image, filename); image_save_yes_cb ( save , filename ); gtk_imhtml_image_save ( GtkWidget * w , GtkIMHtmlImageSave * save ) GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) save -> image ; if ( image -> filesel != NULL ) { gtk_window_present ( GTK_WINDOW ( image -> filesel )); image -> filesel = gtk_file_chooser_dialog_new ( _ ( "Save Image" ), GTK_FILE_CHOOSER_ACTION_SAVE , GTK_STOCK_CANCEL , GTK_RESPONSE_CANCEL , GTK_STOCK_SAVE , GTK_RESPONSE_ACCEPT , gtk_dialog_set_default_response ( GTK_DIALOG ( image -> filesel ), GTK_RESPONSE_ACCEPT ); if ( image -> filename != NULL ) gtk_file_chooser_set_current_name ( GTK_FILE_CHOOSER ( image -> filesel ), image -> filename ); g_signal_connect ( G_OBJECT ( GTK_FILE_CHOOSER ( image -> filesel )), "response" , G_CALLBACK ( image_save_check_if_exists_cb ), save ); gtk_widget_show ( image -> filesel ); gtk_imhtml_custom_smiley_save ( GtkWidget * w , GtkIMHtmlImageSave * save ) GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) save -> image ; /* Create an add dialog */ PidginSmiley * editor = pidgin_smiley_edit ( NULL , NULL ); pidgin_smiley_editor_set_shortcut ( editor , image -> filename ); pidgin_smiley_editor_set_image ( editor , image -> pixbuf ); pidgin_smiley_editor_set_data ( editor , save -> data , save -> datasize ); * So, um, AIM Direct IM lets you send any file, not just images. You can * just insert a sound or a file or whatever in a conversation. It's * basically like file transfer, except there is an icon to open the file * embedded in the conversation. Someone should make the Purple core handle static gboolean gtk_imhtml_image_clicked ( GtkWidget * w , GdkEvent * event , GtkIMHtmlImageSave * save ) GdkEventButton * event_button = ( GdkEventButton * ) event ; GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) save -> image ; if ( event -> type == GDK_BUTTON_RELEASE ) { if ( event_button -> button == 3 ) { GtkWidget * img , * item , * menu ; img = gtk_image_new_from_stock ( GTK_STOCK_SAVE , GTK_ICON_SIZE_MENU ); item = gtk_image_menu_item_new_with_mnemonic ( _ ( "_Save Image..." )); gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( item ), img ); g_signal_connect ( G_OBJECT ( item ), "activate" , G_CALLBACK ( gtk_imhtml_image_save ), save ); gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item ); /* Add menu item for adding custom smiley to local smileys */ /* we only add the menu if the image is of "custom smiley size" if ( image -> width <= 96 && image -> height <= 96 ) { img = gtk_image_new_from_stock ( GTK_STOCK_ADD , GTK_ICON_SIZE_MENU ); item = gtk_image_menu_item_new_with_mnemonic ( _ ( "_Add Custom Smiley..." )); gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( item ), img ); g_signal_connect ( G_OBJECT ( item ), "activate" , G_CALLBACK ( gtk_imhtml_custom_smiley_save ), save ); gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item ); gtk_widget_show_all ( menu ); gtk_menu_popup ( GTK_MENU ( menu ), NULL , NULL , NULL , NULL , event_button -> button , event_button -> time ); if ( event -> type == GDK_BUTTON_PRESS && event_button -> button == 3 ) return TRUE ; /* Clicking the right mouse button on a link shouldn't be caught by the regular GtkTextView menu */ return FALSE ; /* Let clicks go through if we didn't catch anything */ * Rescales a GTK+ IM/HTML scalable image to a given size. * @param scale The GTK+ IM/HTML scalable. * @param width The new width. * @param height The new height. static void gtk_imhtml_image_scale ( GtkIMHtmlScalable * scale , int width , int height ) GtkIMHtmlImage * im_image = ( GtkIMHtmlImage * ) scale ; if ( im_image -> width > width || im_image -> height > height ) { double ratio_w , ratio_h , ratio ; GdkPixbuf * new_image = NULL ; ratio_w = (( double ) width - 2 ) / im_image -> width ; ratio_h = (( double ) height - 2 ) / im_image -> height ; ratio = ( ratio_w < ratio_h ) ? ratio_w : ratio_h ; new_w = ( int )( im_image -> width * ratio ); new_h = ( int )( im_image -> height * ratio ); new_image = gdk_pixbuf_scale_simple ( im_image -> pixbuf , new_w , new_h , GDK_INTERP_BILINEAR ); gtk_image_set_from_pixbuf ( im_image -> image , new_image ); g_object_unref ( G_OBJECT ( new_image )); } else if ( gdk_pixbuf_get_width ( gtk_image_get_pixbuf ( im_image -> image )) != im_image -> width ) { /* Enough space to show the full-size of the image. */ new_image = gdk_pixbuf_scale_simple ( im_image -> pixbuf , im_image -> width , im_image -> height , GDK_INTERP_BILINEAR ); gtk_image_set_from_pixbuf ( im_image -> image , new_image ); g_object_unref ( G_OBJECT ( new_image )); * Adds a GTK+ IM/HTML scalable image to a given GTK+ IM/HTML at a given iter. * @param scale The GTK+ IM/HTML scalable. * @param imhtml The GTK+ IM/HTML. * @param iter The GtkTextIter at which to add the scalable. static void gtk_imhtml_image_add_to ( GtkIMHtmlScalable * scale , GtkIMHtml * imhtml , GtkTextIter * iter ) GtkIMHtmlImage * image = ( GtkIMHtmlImage * ) scale ; GtkWidget * box = gtk_event_box_new (); GtkTextChildAnchor * anchor = gtk_text_buffer_create_child_anchor ( imhtml -> text_buffer , iter ); GtkIMHtmlImageSave * save ; gtk_container_add ( GTK_CONTAINER ( box ), GTK_WIDGET ( image -> image )); g_object_set ( G_OBJECT ( box ), "visible-window" , FALSE , NULL ); gtk_widget_show ( GTK_WIDGET ( image -> image )); tag = g_strdup_printf ( "<IMG ID= \" %d \" >" , image -> id ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_htmltext" , tag , g_free ); g_object_set_data ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" , "[Image]" ); gtk_text_view_add_child_at_anchor ( GTK_TEXT_VIEW ( imhtml ), box , anchor ); save = g_new0 ( GtkIMHtmlImageSave , 1 ); g_signal_connect ( G_OBJECT ( box ), "event" , G_CALLBACK ( gtk_imhtml_image_clicked ), save ); g_object_set_data_full ( G_OBJECT ( box ), "image-save-data" , save , ( GDestroyNotify ) g_free ); * Creates and returns a new GTK+ IM/HTML scalable object with an image. * @param img A GdkPixbuf of the image to add. * @param filename The filename to associate with the image. * @param id The id to associate with the image. * @return A new IM/HTML Scalable object with an image. static GtkIMHtmlScalable * gtk_imhtml_image_new ( GdkPixbuf * img , const gchar * filename , int id ) GtkIMHtmlImage * im_image = g_malloc ( sizeof ( GtkIMHtmlImage )); GTK_IMHTML_SCALABLE ( im_image ) -> scale = gtk_imhtml_image_scale ; GTK_IMHTML_SCALABLE ( im_image ) -> add_to = gtk_imhtml_image_add_to ; GTK_IMHTML_SCALABLE ( im_image ) -> free = gtk_imhtml_image_free ; im_image -> image = GTK_IMAGE ( gtk_image_new_from_pixbuf ( im_image -> pixbuf )); im_image -> width = gdk_pixbuf_get_width ( img ); im_image -> height = gdk_pixbuf_get_height ( img ); im_image -> filename = g_strdup ( filename ); im_image -> filesel = NULL ; return GTK_IMHTML_SCALABLE ( im_image ); animate_image_cb ( gpointer data ) GtkIMHtmlImage * im_image ; /* Update the pointer to this GdkPixbuf frame of the animation */ if ( gdk_pixbuf_animation_iter_advance ( GTK_IMHTML_ANIMATION ( im_image ) -> iter , NULL )) { GdkPixbuf * pb = gdk_pixbuf_animation_iter_get_pixbuf ( GTK_IMHTML_ANIMATION ( im_image ) -> iter ); g_object_unref ( G_OBJECT ( im_image -> pixbuf )); im_image -> pixbuf = gdk_pixbuf_copy ( pb ); /* Update the displayed GtkImage */ width = gdk_pixbuf_get_width ( gtk_image_get_pixbuf ( im_image -> image )); height = gdk_pixbuf_get_height ( gtk_image_get_pixbuf ( im_image -> image )); if ( width > 0 && height > 0 ) /* Need to scale the new frame to the same size as the old frame */ tmp = gdk_pixbuf_scale_simple ( im_image -> pixbuf , width , height , GDK_INTERP_BILINEAR ); gtk_image_set_from_pixbuf ( im_image -> image , tmp ); g_object_unref ( G_OBJECT ( tmp )); /* Display at full-size */ gtk_image_set_from_pixbuf ( im_image -> image , im_image -> pixbuf ); delay = MIN ( gdk_pixbuf_animation_iter_get_delay_time ( GTK_IMHTML_ANIMATION ( im_image ) -> iter ), 100 ); GTK_IMHTML_ANIMATION ( im_image ) -> timer = g_timeout_add ( delay , animate_image_cb , im_image ); * Creates and returns a new GTK+ IM/HTML scalable object with an * @param img A GdkPixbufAnimation of the image to add. * @param filename The filename to associate with the image. * @param id The id to associate with the image. * @return A new IM/HTML Scalable object with an image. * TODO: All this animation code could be combined much better with * the image code. It couldn't be done when it was written * because it requires breaking backward compatibility. It * would be good to do it for 3.0.0. static GtkIMHtmlScalable * gtk_imhtml_animation_new ( GdkPixbufAnimation * anim , const gchar * filename , int id ) GtkIMHtmlImage * im_image = ( GtkIMHtmlImage * ) g_new0 ( GtkIMHtmlAnimation , 1 ); GTK_IMHTML_SCALABLE ( im_image ) -> scale = gtk_imhtml_image_scale ; GTK_IMHTML_SCALABLE ( im_image ) -> add_to = gtk_imhtml_image_add_to ; GTK_IMHTML_SCALABLE ( im_image ) -> free = gtk_imhtml_animation_free ; GTK_IMHTML_ANIMATION ( im_image ) -> anim = anim ; if ( gdk_pixbuf_animation_is_static_image ( anim )) { im_image -> pixbuf = gdk_pixbuf_animation_get_static_image ( anim ); g_object_ref ( im_image -> pixbuf ); GTK_IMHTML_ANIMATION ( im_image ) -> iter = gdk_pixbuf_animation_get_iter ( anim , NULL ); pb = gdk_pixbuf_animation_iter_get_pixbuf ( GTK_IMHTML_ANIMATION ( im_image ) -> iter ); im_image -> pixbuf = gdk_pixbuf_copy ( pb ); delay = MIN ( gdk_pixbuf_animation_iter_get_delay_time ( GTK_IMHTML_ANIMATION ( im_image ) -> iter ), 100 ); GTK_IMHTML_ANIMATION ( im_image ) -> timer = g_timeout_add ( delay , animate_image_cb , im_image ); im_image -> image = GTK_IMAGE ( gtk_image_new_from_pixbuf ( im_image -> pixbuf )); im_image -> width = gdk_pixbuf_animation_get_width ( anim ); im_image -> height = gdk_pixbuf_animation_get_height ( anim ); im_image -> filename = g_strdup ( filename ); return GTK_IMHTML_SCALABLE ( im_image ); GtkIMHtmlScalable * gtk_imhtml_hr_new () GtkIMHtmlHr * hr = g_malloc ( sizeof ( GtkIMHtmlHr )); GTK_IMHTML_SCALABLE ( hr ) -> scale = gtk_imhtml_hr_scale ; GTK_IMHTML_SCALABLE ( hr ) -> add_to = gtk_imhtml_hr_add_to ; GTK_IMHTML_SCALABLE ( hr ) -> free = gtk_imhtml_hr_free ; hr -> sep = gtk_hseparator_new (); gtk_widget_set_size_request ( hr -> sep , 5000 , 2 ); gtk_widget_show ( hr -> sep ); return GTK_IMHTML_SCALABLE ( hr ); void gtk_imhtml_hr_scale ( GtkIMHtmlScalable * scale , int width , int height ) gtk_widget_set_size_request ((( GtkIMHtmlHr * ) scale ) -> sep , width - 2 , 2 ); void gtk_imhtml_hr_add_to ( GtkIMHtmlScalable * scale , GtkIMHtml * imhtml , GtkTextIter * iter ) GtkIMHtmlHr * hr = ( GtkIMHtmlHr * ) scale ; GtkTextChildAnchor * anchor = gtk_text_buffer_create_child_anchor ( imhtml -> text_buffer , iter ); g_object_set_data ( G_OBJECT ( anchor ), "gtkimhtml_htmltext" , "<hr>" ); g_object_set_data ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" , " \n --- \n " ); gtk_text_view_add_child_at_anchor ( GTK_TEXT_VIEW ( imhtml ), hr -> sep , anchor ); void gtk_imhtml_hr_free ( GtkIMHtmlScalable * scale ) gboolean gtk_imhtml_search_find ( GtkIMHtml * imhtml , const gchar * text ) GtkTextIter iter , start , end ; gboolean new_search = TRUE ; g_return_val_if_fail ( imhtml != NULL , FALSE ); g_return_val_if_fail ( text != NULL , FALSE ); start_mark = gtk_text_buffer_get_mark ( imhtml -> text_buffer , "search" ); if ( start_mark && imhtml -> search_string && ! strcmp ( text , imhtml -> search_string )) gtk_imhtml_search_clear ( imhtml ); g_free ( imhtml -> search_string ); imhtml -> search_string = g_strdup ( text ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & iter ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , if ( gtk_source_iter_backward_search ( & iter , imhtml -> search_string , GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE , gtk_text_view_scroll_to_iter ( GTK_TEXT_VIEW ( imhtml ), & start , 0 , TRUE , 0 , 0 ); gtk_text_buffer_create_mark ( imhtml -> text_buffer , "search" , & start , FALSE ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "search" , & iter , & end ); gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "search" , & start , & end ); while ( gtk_source_iter_backward_search ( & start , imhtml -> search_string , GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE , /* We hit the end, so start at the beginning again. */ gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & iter ); if ( gtk_source_iter_backward_search ( & iter , imhtml -> search_string , GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_CASE_INSENSITIVE , gtk_text_view_scroll_to_iter ( GTK_TEXT_VIEW ( imhtml ), & start , 0 , TRUE , 0 , 0 ); gtk_text_buffer_create_mark ( imhtml -> text_buffer , "search" , & start , FALSE ); void gtk_imhtml_search_clear ( GtkIMHtml * imhtml ) g_return_if_fail ( imhtml != NULL ); gtk_text_buffer_get_start_iter ( imhtml -> text_buffer , & start ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "search" , & start , & end ); g_free ( imhtml -> search_string ); imhtml -> search_string = NULL ; static GtkTextTag * find_font_forecolor_tag ( GtkIMHtml * imhtml , gchar * color ) g_snprintf ( str , sizeof ( str ), "FORECOLOR %s" , color ); tag = gtk_text_tag_table_lookup ( gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ), str ); if ( ! gdk_color_parse ( color , & gcolor )) { strncpy ( & tmp [ 1 ], color , 7 ); if ( ! gdk_color_parse ( tmp , & gcolor )) gdk_color_parse ( "black" , & gcolor ); tag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , "foreground-gdk" , & gcolor , NULL ); static GtkTextTag * find_font_backcolor_tag ( GtkIMHtml * imhtml , gchar * color ) g_snprintf ( str , sizeof ( str ), "BACKCOLOR %s" , color ); tag = gtk_text_tag_table_lookup ( gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ), str ); if ( ! gdk_color_parse ( color , & gcolor )) { strncpy ( & tmp [ 1 ], color , 7 ); if ( ! gdk_color_parse ( tmp , & gcolor )) gdk_color_parse ( "white" , & gcolor ); tag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , "background-gdk" , & gcolor , NULL ); static GtkTextTag * find_font_background_tag ( GtkIMHtml * imhtml , gchar * color ) g_snprintf ( str , sizeof ( str ), "BACKGROUND %s" , color ); tag = gtk_text_tag_table_lookup ( gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ), str ); tag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , NULL ); static GtkTextTag * find_font_face_tag ( GtkIMHtml * imhtml , gchar * face ) g_snprintf ( str , sizeof ( str ), "FONT FACE %s" , face ); tag = gtk_text_tag_table_lookup ( gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ), str ); tag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , "family" , face , NULL ); static GtkTextTag * find_font_size_tag ( GtkIMHtml * imhtml , int size ) g_snprintf ( str , sizeof ( str ), "FONT SIZE %d" , size ); tag = gtk_text_tag_table_lookup ( gtk_text_buffer_get_tag_table ( imhtml -> text_buffer ), str ); /* For reasons I don't understand, setting "scale" here scaled * based on some default size other than my theme's default * size. Our size 4 was actually smaller than our size 3 for * me. So this works around that oddity. GtkTextAttributes * attr = gtk_text_view_get_default_attributes ( GTK_TEXT_VIEW ( imhtml )); tag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , "size" , ( gint ) ( pango_font_description_get_size ( attr -> font ) * ( double ) POINT_SIZE ( size )), NULL ); gtk_text_attributes_unref ( attr ); static void remove_tag_by_prefix ( GtkIMHtml * imhtml , const GtkTextIter * i , const GtkTextIter * e , const char * prefix , guint len , gboolean homo ) tags = gtk_text_iter_get_tags ( i ); for ( l = tags ; l ; l = l -> next ) { GtkTextTag * tag = l -> data ; g_object_get ( G_OBJECT ( tag ), "name" , & name , NULL ); if ( name && ! strncmp ( name , prefix , len )) gtk_text_buffer_remove_tag ( imhtml -> text_buffer , tag , i , e ); while ( gtk_text_iter_forward_char ( & iter ) && ! gtk_text_iter_equal ( & iter , e )) { if ( gtk_text_iter_begins_tag ( & iter , NULL )) { tags = gtk_text_iter_get_toggled_tags ( & iter , TRUE ); for ( l = tags ; l ; l = l -> next ) { GtkTextTag * tag = l -> data ; g_object_get ( G_OBJECT ( tag ), "name" , & name , NULL ); if ( name && ! strncmp ( name , prefix , len )) gtk_text_buffer_remove_tag ( imhtml -> text_buffer , tag , & iter , e ); static void remove_font_size ( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ) remove_tag_by_prefix ( imhtml , i , e , "FONT SIZE " , 10 , homo ); static void remove_font_face ( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ) remove_tag_by_prefix ( imhtml , i , e , "FONT FACE " , 10 , homo ); static void remove_font_forecolor ( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ) remove_tag_by_prefix ( imhtml , i , e , "FORECOLOR " , 10 , homo ); static void remove_font_backcolor ( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ) remove_tag_by_prefix ( imhtml , i , e , "BACKCOLOR " , 10 , homo ); static void remove_font_background ( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ) remove_tag_by_prefix ( imhtml , i , e , "BACKGROUND " , 10 , homo ); static void remove_font_link ( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ) remove_tag_by_prefix ( imhtml , i , e , "LINK " , 5 , homo ); imhtml_clear_formatting ( GtkIMHtml * imhtml ) if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "BOLD" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "ITALICS" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "UNDERLINE" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "STRIKE" , & start , & end ); remove_font_size ( imhtml , & start , & end , FALSE ); remove_font_face ( imhtml , & start , & end , FALSE ); remove_font_forecolor ( imhtml , & start , & end , FALSE ); remove_font_backcolor ( imhtml , & start , & end , FALSE ); remove_font_background ( imhtml , & start , & end , FALSE ); remove_font_link ( imhtml , & start , & end , FALSE ); imhtml -> edit . underline = 0 ; imhtml -> edit . fontsize = 0 ; g_free ( imhtml -> edit . fontface ); imhtml -> edit . fontface = NULL ; g_free ( imhtml -> edit . forecolor ); imhtml -> edit . forecolor = NULL ; g_free ( imhtml -> edit . backcolor ); imhtml -> edit . backcolor = NULL ; g_free ( imhtml -> edit . background ); imhtml -> edit . background = NULL ; static void preinsert_cb ( GtkTextBuffer * buffer , GtkTextIter * iter , gchar * text , gint len , GtkIMHtml * imhtml ) imhtml -> insert_offset = gtk_text_iter_get_offset ( iter ); static void insert_ca_cb ( GtkTextBuffer * buffer , GtkTextIter * arg1 , GtkTextChildAnchor * arg2 , gpointer user_data ) gtk_text_iter_backward_char ( & start ); gtk_imhtml_apply_tags_on_insert ( user_data , & start , arg1 ); static void insert_cb ( GtkTextBuffer * buffer , GtkTextIter * end , gchar * text , gint len , GtkIMHtml * imhtml ) gtk_text_iter_set_offset ( & start , imhtml -> insert_offset ); gtk_imhtml_apply_tags_on_insert ( imhtml , & start , end ); static void delete_cb ( GtkTextBuffer * buffer , GtkTextIter * start , GtkTextIter * end , GtkIMHtml * imhtml ) tags = gtk_text_iter_get_tags ( start ); for ( l = tags ; l != NULL ; l = l -> next ) { GtkTextTag * tag = GTK_TEXT_TAG ( l -> data ); if ( tag && /* Remove the formatting only if */ gtk_text_iter_starts_word ( start ) && /* beginning of a word */ gtk_text_iter_begins_tag ( start , tag ) && /* the tag starts with the selection */ ( ! gtk_text_iter_has_tag ( end , tag ) || /* the tag ends within the selection */ gtk_text_iter_ends_tag ( end , tag ))) { g_object_get ( G_OBJECT ( tag ), "name" , & name , NULL ); gtk_text_buffer_remove_tag ( imhtml -> text_buffer , tag , start , end ); if ( name && strncmp ( name , "LINK " , 5 ) == 0 && imhtml -> edit . link ) { gtk_imhtml_toggle_link ( imhtml , NULL ); static void gtk_imhtml_apply_tags_on_insert ( GtkIMHtml * imhtml , GtkTextIter * start , GtkTextIter * end ) gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "BOLD" , start , end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "BOLD" , start , end ); gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "ITALICS" , start , end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "ITALICS" , start , end ); if ( imhtml -> edit . underline ) gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "UNDERLINE" , start , end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "UNDERLINE" , start , end ); gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "STRIKE" , start , end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "STRIKE" , start , end ); if ( imhtml -> edit . forecolor ) { remove_font_forecolor ( imhtml , start , end , TRUE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_forecolor_tag ( imhtml , imhtml -> edit . forecolor ), if ( imhtml -> edit . backcolor ) { remove_font_backcolor ( imhtml , start , end , TRUE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_backcolor_tag ( imhtml , imhtml -> edit . backcolor ), if ( imhtml -> edit . background ) { remove_font_background ( imhtml , start , end , TRUE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_background_tag ( imhtml , imhtml -> edit . background ), if ( imhtml -> edit . fontface ) { remove_font_face ( imhtml , start , end , TRUE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_face_tag ( imhtml , imhtml -> edit . fontface ), if ( imhtml -> edit . fontsize ) { remove_font_size ( imhtml , start , end , TRUE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_size_tag ( imhtml , imhtml -> edit . fontsize ), remove_font_link ( imhtml , start , end , TRUE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , void gtk_imhtml_set_editable ( GtkIMHtml * imhtml , gboolean editable ) gtk_text_view_set_editable ( GTK_TEXT_VIEW ( imhtml ), editable ); * We need a visible caret for accessibility, so mouseless * people can highlight stuff. /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */ if ( editable && ! imhtml -> editable ) { g_signal_connect_after ( G_OBJECT ( GTK_IMHTML ( imhtml ) -> text_buffer ), "mark-set" , G_CALLBACK ( mark_set_cb ), imhtml ); g_signal_connect ( G_OBJECT ( imhtml ), "backspace" , G_CALLBACK ( smart_backspace_cb ), NULL ); } else if ( ! editable && imhtml -> editable ) { g_signal_handlers_disconnect_by_func ( G_OBJECT ( GTK_IMHTML ( imhtml ) -> text_buffer ), g_signal_handlers_disconnect_by_func ( G_OBJECT ( imhtml ), smart_backspace_cb , NULL ); imhtml -> editable = editable ; imhtml -> format_functions = GTK_IMHTML_ALL ; void gtk_imhtml_set_whole_buffer_formatting_only ( GtkIMHtml * imhtml , gboolean wbfo ) g_return_if_fail ( imhtml != NULL ); void gtk_imhtml_set_format_functions ( GtkIMHtml * imhtml , GtkIMHtmlButtons buttons ) GObject * object = g_object_ref ( G_OBJECT ( imhtml )); imhtml -> format_functions = buttons ; g_signal_emit ( object , signals [ BUTTONS_UPDATE ], 0 , buttons ); GtkIMHtmlButtons gtk_imhtml_get_format_functions ( GtkIMHtml * imhtml ) return imhtml -> format_functions ; void gtk_imhtml_get_current_format ( GtkIMHtml * imhtml , gboolean * bold , gboolean * italic , gboolean * underline ) ( * bold ) = imhtml -> edit . bold ; ( * italic ) = imhtml -> edit . italic ; ( * underline ) = imhtml -> edit . underline ; gtk_imhtml_get_current_fontface ( GtkIMHtml * imhtml ) return g_strdup ( imhtml -> edit . fontface ); gtk_imhtml_get_current_forecolor ( GtkIMHtml * imhtml ) return g_strdup ( imhtml -> edit . forecolor ); gtk_imhtml_get_current_backcolor ( GtkIMHtml * imhtml ) return g_strdup ( imhtml -> edit . backcolor ); gtk_imhtml_get_current_background ( GtkIMHtml * imhtml ) return g_strdup ( imhtml -> edit . background ); gtk_imhtml_get_current_fontsize ( GtkIMHtml * imhtml ) return imhtml -> edit . fontsize ; gboolean gtk_imhtml_get_editable ( GtkIMHtml * imhtml ) gtk_imhtml_clear_formatting ( GtkIMHtml * imhtml ) object = g_object_ref ( G_OBJECT ( imhtml )); g_signal_emit ( object , signals [ CLEAR_FORMAT ], 0 ); gtk_widget_grab_focus ( GTK_WIDGET ( imhtml )); * I had this crazy idea about changing the text cursor color to reflex the foreground color * of the text about to be entered. This is the place you'd do it, along with the place where * we actually set a new foreground color. * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor * Just in case I do do this, I asked about what to set the secondary text cursor to. * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2 * (12:45:55) ?? ???: understand? * (12:46:14) Tim: yeah. i didn't know there was an exact formula * (12:46:56) ?? ???: u might need to extract separate each color from RGB static void mark_set_cb ( GtkTextBuffer * buffer , GtkTextIter * arg1 , GtkTextMark * mark , if ( mark != gtk_text_buffer_get_insert ( buffer )) if ( ! gtk_text_buffer_get_char_count ( buffer )) imhtml -> edit . bold = imhtml -> edit . italic = imhtml -> edit . underline = imhtml -> edit . strike = FALSE ; g_free ( imhtml -> edit . forecolor ); imhtml -> edit . forecolor = NULL ; g_free ( imhtml -> edit . backcolor ); imhtml -> edit . backcolor = NULL ; g_free ( imhtml -> edit . fontface ); imhtml -> edit . fontface = NULL ; imhtml -> edit . fontsize = 0 ; imhtml -> edit . link = NULL ; gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , mark ); if ( gtk_text_iter_is_end ( & iter )) tags = gtk_text_iter_get_toggled_tags ( & iter , FALSE ); tags = gtk_text_iter_get_tags ( & iter ); for ( l = tags ; l != NULL ; l = l -> next ) { GtkTextTag * tag = GTK_TEXT_TAG ( l -> data ); g_object_get ( G_OBJECT ( tag ), "name" , & name , NULL ); if ( strcmp ( name , "BOLD" ) == 0 ) imhtml -> edit . bold = TRUE ; else if ( strcmp ( name , "ITALICS" ) == 0 ) imhtml -> edit . italic = TRUE ; else if ( strcmp ( name , "UNDERLINE" ) == 0 ) imhtml -> edit . underline = TRUE ; else if ( strcmp ( name , "STRIKE" ) == 0 ) imhtml -> edit . strike = TRUE ; else if ( strncmp ( name , "FORECOLOR " , 10 ) == 0 ) imhtml -> edit . forecolor = g_strdup ( & ( name )[ 10 ]); else if ( strncmp ( name , "BACKCOLOR " , 10 ) == 0 ) imhtml -> edit . backcolor = g_strdup ( & ( name )[ 10 ]); else if ( strncmp ( name , "FONT FACE " , 10 ) == 0 ) imhtml -> edit . fontface = g_strdup ( & ( name )[ 10 ]); else if ( strncmp ( name , "FONT SIZE " , 10 ) == 0 ) imhtml -> edit . fontsize = strtol ( & ( name )[ 10 ], NULL , 10 ); else if (( strncmp ( name , "LINK " , 5 ) == 0 ) && ! gtk_text_iter_is_end ( & iter )) static void imhtml_emit_signal_for_format ( GtkIMHtml * imhtml , GtkIMHtmlButtons button ) g_return_if_fail ( imhtml != NULL ); object = g_object_ref ( G_OBJECT ( imhtml )); g_signal_emit ( object , signals [ TOGGLE_FORMAT ], 0 , button ); static void imhtml_toggle_bold ( GtkIMHtml * imhtml ) imhtml -> edit . bold = ! imhtml -> edit . bold ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "BOLD" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "BOLD" , & start , & end ); void gtk_imhtml_toggle_bold ( GtkIMHtml * imhtml ) imhtml_emit_signal_for_format ( imhtml , GTK_IMHTML_BOLD ); static void imhtml_toggle_italic ( GtkIMHtml * imhtml ) imhtml -> edit . italic = ! imhtml -> edit . italic ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "ITALICS" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "ITALICS" , & start , & end ); void gtk_imhtml_toggle_italic ( GtkIMHtml * imhtml ) imhtml_emit_signal_for_format ( imhtml , GTK_IMHTML_ITALIC ); static void imhtml_toggle_underline ( GtkIMHtml * imhtml ) imhtml -> edit . underline = ! imhtml -> edit . underline ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) if ( imhtml -> edit . underline ) gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "UNDERLINE" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "UNDERLINE" , & start , & end ); void gtk_imhtml_toggle_underline ( GtkIMHtml * imhtml ) imhtml_emit_signal_for_format ( imhtml , GTK_IMHTML_UNDERLINE ); static void imhtml_toggle_strike ( GtkIMHtml * imhtml ) imhtml -> edit . strike = ! imhtml -> edit . strike ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) gtk_text_buffer_apply_tag_by_name ( imhtml -> text_buffer , "STRIKE" , & start , & end ); gtk_text_buffer_remove_tag_by_name ( imhtml -> text_buffer , "STRIKE" , & start , & end ); void gtk_imhtml_toggle_strike ( GtkIMHtml * imhtml ) imhtml_emit_signal_for_format ( imhtml , GTK_IMHTML_STRIKE ); void gtk_imhtml_font_set_size ( GtkIMHtml * imhtml , gint size ) imhtml -> edit . fontsize = size ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) remove_font_size ( imhtml , & start , & end , imhtml -> wbfo ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_size_tag ( imhtml , imhtml -> edit . fontsize ), & start , & end ); object = g_object_ref ( G_OBJECT ( imhtml )); g_signal_emit ( object , signals [ TOGGLE_FORMAT ], 0 , GTK_IMHTML_SHRINK | GTK_IMHTML_GROW ); static void imhtml_font_shrink ( GtkIMHtml * imhtml ) if ( imhtml -> edit . fontsize == 1 ) if ( ! imhtml -> edit . fontsize ) imhtml -> edit . fontsize = 2 ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) remove_font_size ( imhtml , & start , & end , imhtml -> wbfo ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_size_tag ( imhtml , imhtml -> edit . fontsize ), & start , & end ); void gtk_imhtml_font_shrink ( GtkIMHtml * imhtml ) imhtml_emit_signal_for_format ( imhtml , GTK_IMHTML_SHRINK ); static void imhtml_font_grow ( GtkIMHtml * imhtml ) if ( imhtml -> edit . fontsize == MAX_FONT_SIZE ) if ( ! imhtml -> edit . fontsize ) imhtml -> edit . fontsize = 4 ; if ( ! imhtml_get_iter_bounds ( imhtml , & start , & end )) remove_font_size ( imhtml , & start , & end , imhtml -> wbfo ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_font_size_tag ( imhtml , imhtml -> edit . fontsize ), & start , & end ); void gtk_imhtml_font_grow ( GtkIMHtml * imhtml ) imhtml_emit_signal_for_format ( imhtml , GTK_IMHTML_GROW ); static gboolean gtk_imhtml_toggle_str_tag ( GtkIMHtml * imhtml , const char * value , char ** edit_field , void ( * remove_func )( GtkIMHtml * imhtml , GtkTextIter * i , GtkTextIter * e , gboolean homo ), GtkTextTag * ( find_func )( GtkIMHtml * imhtml , gchar * color ), GtkIMHtmlButtons button ) if ( value && strcmp ( value , "" ) != 0 ) * edit_field = g_strdup ( value ); if ( imhtml_get_iter_bounds ( imhtml , & start , & end )) { remove_func ( imhtml , & start , & end , imhtml -> wbfo ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , find_func ( imhtml , * edit_field ), & start , & end ); if ( imhtml_get_iter_bounds ( imhtml , & start , & end )) remove_func ( imhtml , & start , & end , TRUE ); /* 'TRUE' or 'imhtml->wbfo'? */ object = g_object_ref ( G_OBJECT ( imhtml )); g_signal_emit ( object , signals [ TOGGLE_FORMAT ], 0 , button ); return * edit_field != NULL ; gboolean gtk_imhtml_toggle_forecolor ( GtkIMHtml * imhtml , const char * color ) return gtk_imhtml_toggle_str_tag ( imhtml , color , & imhtml -> edit . forecolor , remove_font_forecolor , find_font_forecolor_tag , gboolean gtk_imhtml_toggle_backcolor ( GtkIMHtml * imhtml , const char * color ) return gtk_imhtml_toggle_str_tag ( imhtml , color , & imhtml -> edit . backcolor , remove_font_backcolor , find_font_backcolor_tag , gboolean gtk_imhtml_toggle_background ( GtkIMHtml * imhtml , const char * color ) return gtk_imhtml_toggle_str_tag ( imhtml , color , & imhtml -> edit . background , remove_font_background , find_font_background_tag , gboolean gtk_imhtml_toggle_fontface ( GtkIMHtml * imhtml , const char * face ) return gtk_imhtml_toggle_str_tag ( imhtml , face , & imhtml -> edit . fontface , remove_font_face , find_font_face_tag , void gtk_imhtml_toggle_link ( GtkIMHtml * imhtml , const char * url ) imhtml -> edit . link = NULL ; g_snprintf ( str , sizeof ( str ), "LINK %d" , linkno ++ ); gtk_widget_style_get ( GTK_WIDGET ( imhtml ), "hyperlink-color" , & color , NULL ); imhtml -> edit . link = linktag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , "foreground-gdk" , color , "underline" , PANGO_UNDERLINE_SINGLE , NULL ); imhtml -> edit . link = linktag = gtk_text_buffer_create_tag ( imhtml -> text_buffer , str , "foreground" , "blue" , "underline" , PANGO_UNDERLINE_SINGLE , NULL ); g_object_set_data_full ( G_OBJECT ( linktag ), "link_url" , g_strdup ( url ), g_free ); g_signal_connect ( G_OBJECT ( linktag ), "event" , G_CALLBACK ( tag_event ), NULL ); if ( imhtml -> editable && gtk_text_buffer_get_selection_bounds ( imhtml -> text_buffer , & start , & end )) { remove_font_link ( imhtml , & start , & end , FALSE ); gtk_text_buffer_apply_tag ( imhtml -> text_buffer , linktag , & start , & end ); object = g_object_ref ( G_OBJECT ( imhtml )); g_signal_emit ( object , signals [ TOGGLE_FORMAT ], 0 , GTK_IMHTML_LINK ); void gtk_imhtml_insert_link ( GtkIMHtml * imhtml , GtkTextMark * mark , const char * url , const char * text ) /* Delete any currently selected text */ gtk_text_buffer_delete_selection ( imhtml -> text_buffer , TRUE , TRUE ); gtk_imhtml_toggle_link ( imhtml , url ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , mark ); gtk_text_buffer_insert ( imhtml -> text_buffer , & iter , text , -1 ); gtk_imhtml_toggle_link ( imhtml , NULL ); void gtk_imhtml_insert_smiley ( GtkIMHtml * imhtml , const char * sml , char * smiley ) /* Delete any currently selected text */ gtk_text_buffer_delete_selection ( imhtml -> text_buffer , TRUE , TRUE ); mark = gtk_text_buffer_get_insert ( imhtml -> text_buffer ); gtk_text_buffer_get_iter_at_mark ( imhtml -> text_buffer , & iter , mark ); gtk_text_buffer_begin_user_action ( imhtml -> text_buffer ); gtk_imhtml_insert_smiley_at_iter ( imhtml , sml , smiley , & iter ); gtk_text_buffer_end_user_action ( imhtml -> text_buffer ); /* TODO: I think this can be removed for GTK+ 3.0... */ #if !GTK_CHECK_VERSION(3,0,0) image_expose ( GtkWidget * widget , GdkEventExpose * event , gpointer user_data ) GTK_WIDGET_CLASS ( GTK_WIDGET_GET_CLASS ( widget )) -> expose_event ( widget , event ); /* In case the smiley gets removed from the imhtml before it gets removed from the queue */ static void animated_smiley_destroy_cb ( GtkWidget * widget , GtkIMHtml * imhtml ) GList * l = imhtml -> animations -> head ; if ( l == imhtml -> animations -> tail ) imhtml -> animations -> tail = imhtml -> animations -> tail -> prev ; imhtml -> animations -> head = g_list_delete_link ( imhtml -> animations -> head , l ); imhtml -> num_animations -- ; static gboolean gtk_imhtml_smiley_clicked ( GtkWidget * w , GdkEvent * event , GtkIMHtmlSmiley * smiley ) GdkPixbufAnimation * anim = NULL ; GtkIMHtmlImageSave * save = NULL ; if ( event -> type != GDK_BUTTON_RELEASE || (( GdkEventButton * ) event ) -> button != 3 ) anim = gtk_smiley_get_image ( smiley ); save = g_new0 ( GtkIMHtmlImageSave , 1 ); save -> image = ( GtkIMHtmlScalable * ) gtk_imhtml_animation_new ( anim , smiley -> smile , 0 ); save -> data = smiley -> data ; /* Do not need to memdup here, since the smiley is not destroyed before this GtkIMHtmlImageSave */ save -> datasize = smiley -> datasize ; ret = gtk_imhtml_image_clicked ( w , event , save ); g_object_set_data_full ( G_OBJECT ( w ), "image-data" , save -> image , ( GDestroyNotify ) gtk_imhtml_animation_free ); g_object_set_data_full ( G_OBJECT ( w ), "image-save-data" , save , ( GDestroyNotify ) g_free ); void gtk_imhtml_insert_smiley_at_iter ( GtkIMHtml * imhtml , const char * sml , char * smiley , GtkTextIter * iter ) GdkPixbuf * pixbuf = NULL ; GdkPixbufAnimation * annipixbuf = NULL ; GtkTextChildAnchor * anchor = NULL ; GtkIMHtmlSmiley * imhtml_smiley ; int numsmileys_thismsg , numsmileys_total ; * This GtkIMHtml has the maximum number of smileys allowed, so don't * add any more. We do this for performance reasons, because smileys * are apparently pretty inefficient. Hopefully we can remove this * restriction when we're using a better HTML widget. unescaped = purple_unescape_html ( smiley ); numsmileys_thismsg = GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_thismsg" )); if ( numsmileys_thismsg >= 30 ) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , unescaped , -1 ); numsmileys_total = GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_total" )); if ( numsmileys_total >= 300 ) { gtk_text_buffer_insert ( imhtml -> text_buffer , iter , unescaped , -1 ); imhtml_smiley = gtk_imhtml_smiley_get ( imhtml , sml , unescaped ); if ( imhtml -> format_functions & GTK_IMHTML_SMILEY ) { annipixbuf = imhtml_smiley ? gtk_smiley_get_image ( imhtml_smiley ) : NULL ; if ( gdk_pixbuf_animation_is_static_image ( annipixbuf )) { pixbuf = gdk_pixbuf_animation_get_static_image ( annipixbuf ); icon = gtk_image_new_from_pixbuf ( pixbuf ); icon = gtk_image_new_from_animation ( annipixbuf ); if ( imhtml -> num_animations == 20 ) { GtkImage * image = GTK_IMAGE ( g_queue_pop_head ( imhtml -> animations )); GdkPixbufAnimation * anim = gtk_image_get_animation ( image ); g_signal_handlers_disconnect_matched ( G_OBJECT ( image ), G_SIGNAL_MATCH_FUNC , 0 , 0 , NULL , G_CALLBACK ( animated_smiley_destroy_cb ), NULL ); GdkPixbuf * pb = gdk_pixbuf_animation_get_static_image ( anim ); GdkPixbuf * copy = gdk_pixbuf_copy ( pb ); gtk_image_set_from_pixbuf ( image , copy ); g_object_unref ( G_OBJECT ( copy )); imhtml -> num_animations ++ ; g_signal_connect ( G_OBJECT ( icon ), "destroy" , G_CALLBACK ( animated_smiley_destroy_cb ), imhtml ); g_queue_push_tail ( imhtml -> animations , icon ); if ( imhtml_smiley && imhtml_smiley -> flags & GTK_IMHTML_SMILEY_CUSTOM ) { ebox = gtk_event_box_new (); gtk_event_box_set_visible_window ( GTK_EVENT_BOX ( ebox ), FALSE ); anchor = gtk_text_buffer_create_child_anchor ( imhtml -> text_buffer , iter ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" , g_strdup ( unescaped ), g_free ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_tiptext" , g_strdup ( unescaped ), g_free ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_htmltext" , g_strdup ( smiley ), g_free ); /* This catches the expose events generated by animated * images, and ensures that they are handled by the image * itself, without propagating to the textview and causing /* TODO: I think this should be removed for GTK+ 3.0? */ #if !GTK_CHECK_VERSION(3,0,0) g_signal_connect ( G_OBJECT ( icon ), "expose-event" , G_CALLBACK ( image_expose ), NULL ); gtk_container_add ( GTK_CONTAINER ( ebox ), icon ); gtk_text_view_add_child_at_anchor ( GTK_TEXT_VIEW ( imhtml ), ebox ? ebox : icon , anchor ); g_object_set_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_thismsg" , GINT_TO_POINTER ( numsmileys_thismsg + 1 )); g_object_set_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_total" , GINT_TO_POINTER ( numsmileys_total + 1 )); } else if ( imhtml_smiley != NULL && ( imhtml -> format_functions & GTK_IMHTML_SMILEY )) { anchor = gtk_text_buffer_create_child_anchor ( imhtml -> text_buffer , iter ); imhtml_smiley -> anchors = g_slist_append ( imhtml_smiley -> anchors , g_object_ref ( anchor )); GtkWidget * img = gtk_image_new_from_stock ( GTK_STOCK_MISSING_IMAGE , GTK_ICON_SIZE_MENU ); gtk_container_add ( GTK_CONTAINER ( ebox ), img ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" , g_strdup ( unescaped ), g_free ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_tiptext" , g_strdup ( unescaped ), g_free ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_htmltext" , g_strdup ( smiley ), g_free ); gtk_text_view_add_child_at_anchor ( GTK_TEXT_VIEW ( imhtml ), ebox , anchor ); g_object_set_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_thismsg" , GINT_TO_POINTER ( numsmileys_thismsg + 1 )); g_object_set_data ( G_OBJECT ( imhtml ), "gtkimhtml_numsmileys_total" , GINT_TO_POINTER ( numsmileys_total + 1 )); gtk_text_buffer_insert ( imhtml -> text_buffer , iter , unescaped , -1 ); g_signal_connect ( G_OBJECT ( ebox ), "event" , G_CALLBACK ( gtk_imhtml_smiley_clicked ), imhtml_smiley ); void gtk_imhtml_insert_image_at_iter ( GtkIMHtml * imhtml , int id , GtkTextIter * iter ) GdkPixbufAnimation * anim = NULL ; const char * filename = NULL ; GtkIMHtmlScalable * scalable = NULL ; struct scalable_data * sd ; if ( ! imhtml -> funcs || ! imhtml -> funcs -> image_get || ! imhtml -> funcs -> image_get_size || ! imhtml -> funcs -> image_get_data || ! imhtml -> funcs -> image_get_filename || ! imhtml -> funcs -> image_ref || ! imhtml -> funcs -> image_unref ) image = imhtml -> funcs -> image_get ( id ); data = imhtml -> funcs -> image_get_data ( image ); len = imhtml -> funcs -> image_get_size ( image ); anim = pidgin_pixbuf_anim_from_data ( data , len ); struct im_image_data * t = g_new ( struct im_image_data , 1 ); filename = imhtml -> funcs -> image_get_filename ( image ); imhtml -> funcs -> image_ref ( id ); t -> mark = gtk_text_buffer_create_mark ( imhtml -> text_buffer , NULL , iter , TRUE ); imhtml -> im_images = g_slist_prepend ( imhtml -> im_images , t ); scalable = gtk_imhtml_animation_new ( anim , filename , id ); g_object_unref ( G_OBJECT ( anim )); pixbuf = gtk_widget_render_icon ( GTK_WIDGET ( imhtml ), GTK_STOCK_MISSING_IMAGE , GTK_ICON_SIZE_BUTTON , "gtkimhtml-missing-image" ); scalable = gtk_imhtml_image_new ( pixbuf , filename , id ); g_object_unref ( G_OBJECT ( pixbuf )); sd = g_new ( struct scalable_data , 1 ); sd -> mark = gtk_text_buffer_create_mark ( imhtml -> text_buffer , NULL , iter , TRUE ); gtk_text_view_get_visible_rect ( GTK_TEXT_VIEW ( imhtml ), & rect ); scalable -> add_to ( scalable , imhtml , iter ); minus = gtk_text_view_get_left_margin ( GTK_TEXT_VIEW ( imhtml )) + gtk_text_view_get_right_margin ( GTK_TEXT_VIEW ( imhtml )); scalable -> scale ( scalable , rect . width - minus , rect . height ); imhtml -> scalables = g_list_append ( imhtml -> scalables , sd ); static const gchar * tag_to_html_start ( GtkTextTag * tag ) g_object_get ( G_OBJECT ( tag ), "name" , & name , NULL ); g_return_val_if_fail ( name != NULL , "" ); if ( strcmp ( name , "BOLD" ) == 0 ) { } else if ( strcmp ( name , "ITALICS" ) == 0 ) { } else if ( strcmp ( name , "UNDERLINE" ) == 0 ) { } else if ( strcmp ( name , "STRIKE" ) == 0 ) { } else if ( strncmp ( name , "LINK " , 5 ) == 0 ) { char * tmp = g_object_get_data ( G_OBJECT ( tag ), "link_url" ); gchar * escaped = purple_markup_escape_text ( tmp , -1 ); g_snprintf ( buf , sizeof ( buf ), "<a href= \" %s \" >" , escaped ); buf [ sizeof ( buf ) -1 ] = '\0' ; } else if ( strncmp ( name , "FORECOLOR " , 10 ) == 0 ) { g_snprintf ( buf , sizeof ( buf ), "<font color= \" %s \" >" , & name [ 10 ]); } else if ( strncmp ( name , "BACKCOLOR " , 10 ) == 0 ) { g_snprintf ( buf , sizeof ( buf ), "<font back= \" %s \" >" , & name [ 10 ]); } else if ( strncmp ( name , "BACKGROUND " , 10 ) == 0 ) { g_snprintf ( buf , sizeof ( buf ), "<body bgcolor= \" %s \" >" , & name [ 11 ]); } else if ( strncmp ( name , "FONT FACE " , 10 ) == 0 ) { g_snprintf ( buf , sizeof ( buf ), "<font face= \" %s \" >" , & name [ 10 ]); } else if ( strncmp ( name , "FONT SIZE " , 10 ) == 0 ) { g_snprintf ( buf , sizeof ( buf ), "<font size= \" %s \" >" , & name [ 10 ]); GObject * obj = G_OBJECT ( tag ); str += g_snprintf ( str , sizeof ( buf ) - ( str - buf ), "<span style='" ); g_object_get ( obj , "weight-set" , & isset , "weight" , & ivalue , NULL ); if ( ivalue >= PANGO_WEIGHT_ULTRABOLD ) else if ( ivalue >= PANGO_WEIGHT_BOLD ) else if ( ivalue >= PANGO_WEIGHT_NORMAL ) str += g_snprintf ( str , sizeof ( buf ) - ( str - buf ), "font-weight: %s;" , weight ); g_object_get ( obj , "foreground-set" , & isset , "foreground-gdk" , & color , NULL ); str += g_snprintf ( str , sizeof ( buf ) - ( str - buf ), color -> red >> 8 , color -> green >> 8 , color -> blue >> 8 ); g_object_get ( obj , "background-set" , & isset , "background-gdk" , & color , NULL ); str += g_snprintf ( str , sizeof ( buf ) - ( str - buf ), "background: #%02x%02x%02x;" , color -> red >> 8 , color -> green >> 8 , color -> blue >> 8 ); g_object_get ( obj , "underline-set" , & isset , "underline" , & ivalue , NULL ); case PANGO_UNDERLINE_NONE : case PANGO_UNDERLINE_ERROR : str += g_snprintf ( str , sizeof ( buf ) - ( str - buf ), "text-decoration: underline;" ); g_snprintf ( str , sizeof ( buf ) - ( str - buf ), "'>" ); return ( empty ? "" : buf ); static const gchar * tag_to_html_end ( GtkTextTag * tag ) g_object_get ( G_OBJECT ( tag ), "name" , & name , NULL ); g_return_val_if_fail ( name != NULL , "" ); if ( strcmp ( name , "BOLD" ) == 0 ) { } else if ( strcmp ( name , "ITALICS" ) == 0 ) { } else if ( strcmp ( name , "UNDERLINE" ) == 0 ) { } else if ( strcmp ( name , "STRIKE" ) == 0 ) { } else if ( strncmp ( name , "LINK " , 5 ) == 0 ) { } else if ( strncmp ( name , "FORECOLOR " , 10 ) == 0 ) { } else if ( strncmp ( name , "BACKCOLOR " , 10 ) == 0 ) { } else if ( strncmp ( name , "BACKGROUND " , 10 ) == 0 ) { } else if ( strncmp ( name , "FONT FACE " , 10 ) == 0 ) { } else if ( strncmp ( name , "FONT SIZE " , 10 ) == 0 ) { const char * props [] = { "weight-set" , "foreground-set" , "background-set" , "size-set" , "underline-set" , NULL }; for ( i = 0 ; props [ i ]; i ++ ) { g_object_get ( G_OBJECT ( tag ), props [ i ], & set , NULL ); static PidginTextTagData * text_tag_data_new ( GtkTextTag * tag ) PidginTextTagData * ret = NULL ; start = tag_to_html_start ( tag ); end = tag_to_html_end ( tag ); ret = g_new0 ( PidginTextTagData , 1 ); ret -> start = g_strdup ( start ); ret -> end = g_strdup ( end ); static void text_tag_data_destroy ( PidginTextTagData * data ) static gboolean tag_ends_here ( GtkTextTag * tag , GtkTextIter * iter , GtkTextIter * niter ) return (( gtk_text_iter_has_tag ( iter , GTK_TEXT_TAG ( tag )) && ! gtk_text_iter_has_tag ( niter , GTK_TEXT_TAG ( tag ))) || gtk_text_iter_is_end ( niter )); /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert * the actual text. Then check for tags that are toggled off and insert them, after checking the queue. * Finally, replace <, >, &, and " with their HTML equivalent. char * gtk_imhtml_get_markup_range ( GtkIMHtml * imhtml , GtkTextIter * start , GtkTextIter * end ) GtkTextIter iter , next_iter , non_neutral_iter ; gboolean is_rtl_message = FALSE ; GString * str = g_string_new ( "" ); PidginTextTagData * tagdata ; gtk_text_iter_order ( start , end ); non_neutral_iter = next_iter = iter = * start ; gtk_text_iter_forward_char ( & next_iter ); /* Bi-directional text support */ /* Get to the first non-neutral character */ while (( PANGO_DIRECTION_NEUTRAL == pango_unichar_direction ( gtk_text_iter_get_char ( & non_neutral_iter ))) && gtk_text_iter_forward_char ( & non_neutral_iter )); if ( PANGO_DIRECTION_RTL == pango_unichar_direction ( gtk_text_iter_get_char ( & non_neutral_iter ))) { g_string_append ( str , "<SPAN style= \" direction:rtl;text-align:right; \" >" ); /* First add the tags that are already in progress (we don't care about non-printing tags)*/ tags = gtk_text_iter_get_tags ( start ); for ( sl = tags ; sl ; sl = sl -> next ) { if ( ! gtk_text_iter_toggles_tag ( start , GTK_TEXT_TAG ( tag ))) { PidginTextTagData * data = text_tag_data_new ( tag ); g_string_append ( str , data -> start ); g_queue_push_tail ( q , data ); while (( c = gtk_text_iter_get_char ( & iter )) != 0 && ! gtk_text_iter_equal ( & iter , end )) { tags = gtk_text_iter_get_tags ( & iter ); for ( sl = tags ; sl ; sl = sl -> next ) { if ( gtk_text_iter_begins_tag ( & iter , GTK_TEXT_TAG ( tag ))) { PidginTextTagData * data = text_tag_data_new ( tag ); g_string_append ( str , data -> start ); g_queue_push_tail ( q , data ); GtkTextChildAnchor * anchor = gtk_text_iter_get_child_anchor ( & iter ); char * text = g_object_get_data ( G_OBJECT ( anchor ), "gtkimhtml_htmltext" ); str = g_string_append ( str , text ); str = g_string_append ( str , "<" ); str = g_string_append ( str , ">" ); str = g_string_append ( str , "&" ); str = g_string_append ( str , """ ); str = g_string_append ( str , "<br>" ); str = g_string_append_unichar ( str , c ); tags = g_slist_reverse ( tags ); for ( sl = tags ; sl ; sl = sl -> next ) { /** don't worry about non-printing tags ending */ if ( tag_ends_here ( tag , & iter , & next_iter ) && GQueue * r = g_queue_new (); while (( tmp = g_queue_pop_tail ( q )) && tmp -> tag != tag ) { g_string_append ( str , tmp -> end ); if ( ! tag_ends_here ( tmp -> tag , & iter , & next_iter )) g_queue_push_tail ( r , tmp ); text_tag_data_destroy ( tmp ); g_string_append ( str , tmp -> end ); text_tag_data_destroy ( tmp ); #if 0 /* This can't be allowed to happen because it causes the iters to be invalidated in the debug window imhtml during text copying */ purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n"); while (( tmp = g_queue_pop_head ( r ))) { g_string_append ( str , tmp -> start ); g_queue_push_tail ( q , tmp ); gtk_text_iter_forward_char ( & iter ); gtk_text_iter_forward_char ( & next_iter ); while (( tagdata = g_queue_pop_tail ( q ))) { g_string_append ( str , tagdata -> end ); text_tag_data_destroy ( tagdata ); /* Bi-directional text support - close tags */ g_string_append ( str , "</SPAN>" ); return g_string_free ( str , FALSE ); void gtk_imhtml_close_tags ( GtkIMHtml * imhtml , GtkTextIter * iter ) gtk_imhtml_toggle_bold ( imhtml ); gtk_imhtml_toggle_italic ( imhtml ); if ( imhtml -> edit . underline ) gtk_imhtml_toggle_underline ( imhtml ); gtk_imhtml_toggle_strike ( imhtml ); if ( imhtml -> edit . forecolor ) gtk_imhtml_toggle_forecolor ( imhtml , NULL ); if ( imhtml -> edit . backcolor ) gtk_imhtml_toggle_backcolor ( imhtml , NULL ); if ( imhtml -> edit . fontface ) gtk_imhtml_toggle_fontface ( imhtml , NULL ); imhtml -> edit . fontsize = 0 ; gtk_imhtml_toggle_link ( imhtml , NULL ); char * gtk_imhtml_get_markup ( GtkIMHtml * imhtml ) gtk_text_buffer_get_start_iter ( imhtml -> text_buffer , & start ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & end ); return gtk_imhtml_get_markup_range ( imhtml , & start , & end ); char ** gtk_imhtml_get_markup_lines ( GtkIMHtml * imhtml ) lines = gtk_text_buffer_get_line_count ( imhtml -> text_buffer ); ret = g_new0 ( char * , lines + 1 ); gtk_text_buffer_get_start_iter ( imhtml -> text_buffer , & start ); gtk_text_iter_forward_to_line_end ( & end ); for ( i = 0 , j = 0 ; i < lines ; i ++ ) { if ( gtk_text_iter_get_char ( & start ) != '\n' ) { ret [ j ] = gtk_imhtml_get_markup_range ( imhtml , & start , & end ); gtk_text_iter_forward_line ( & start ); gtk_text_iter_forward_to_line_end ( & end ); char * gtk_imhtml_get_text ( GtkIMHtml * imhtml , GtkTextIter * start , GtkTextIter * stop ) GString * str = g_string_new ( "" ); gtk_text_buffer_get_start_iter ( imhtml -> text_buffer , & iter ); gtk_text_buffer_get_end_iter ( imhtml -> text_buffer , & end ); gtk_text_iter_order ( & iter , & end ); while (( c = gtk_text_iter_get_char ( & iter )) != 0 && ! gtk_text_iter_equal ( & iter , & end )) { GtkTextChildAnchor * anchor ; anchor = gtk_text_iter_get_child_anchor ( & iter ); text = g_object_get_data ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" ); str = g_string_append ( str , text ); g_string_append_unichar ( str , c ); gtk_text_iter_forward_char ( & iter ); return g_string_free ( str , FALSE ); void gtk_imhtml_set_funcs ( GtkIMHtml * imhtml , GtkIMHtmlFuncs * f ) g_return_if_fail ( imhtml != NULL ); void gtk_imhtml_setup_entry ( GtkIMHtml * imhtml , PurpleConnectionFlags flags ) GtkIMHtmlButtons buttons ; if ( flags & PURPLE_CONNECTION_FLAG_HTML ) { GdkColor fg_color , bg_color ; buttons = GTK_IMHTML_ALL ; if ( flags & PURPLE_CONNECTION_FLAG_NO_BGCOLOR ) buttons &= ~ GTK_IMHTML_BACKCOLOR ; if ( flags & PURPLE_CONNECTION_FLAG_NO_FONTSIZE ) buttons &= ~ GTK_IMHTML_GROW ; buttons &= ~ GTK_IMHTML_SHRINK ; if ( flags & PURPLE_CONNECTION_FLAG_NO_URLDESC ) buttons &= ~ GTK_IMHTML_LINKDESC ; gtk_imhtml_set_format_functions ( imhtml , GTK_IMHTML_ALL ); if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/conversations/send_bold" ) != imhtml -> edit . bold ) gtk_imhtml_toggle_bold ( imhtml ); if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/conversations/send_italic" ) != imhtml -> edit . italic ) gtk_imhtml_toggle_italic ( imhtml ); if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/conversations/send_underline" ) != imhtml -> edit . underline ) gtk_imhtml_toggle_underline ( imhtml ); gtk_imhtml_toggle_fontface ( imhtml , purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/conversations/font_face" )); if ( ! ( flags & PURPLE_CONNECTION_FLAG_NO_FONTSIZE )) int size = purple_prefs_get_int ( PIDGIN_PREFS_ROOT "/conversations/font_size" ); gtk_imhtml_font_set_size ( imhtml , size ); if ( strcmp ( purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/conversations/fgcolor" ), "" ) != 0 ) gdk_color_parse ( purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/conversations/fgcolor" ), g_snprintf ( color , sizeof ( color ), "#%02x%02x%02x" , gtk_imhtml_toggle_forecolor ( imhtml , color ); if ( ! ( flags & PURPLE_CONNECTION_FLAG_NO_BGCOLOR ) && strcmp ( purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/conversations/bgcolor" ), "" ) != 0 ) gdk_color_parse ( purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/conversations/bgcolor" ), g_snprintf ( color , sizeof ( color ), "#%02x%02x%02x" , gtk_imhtml_toggle_background ( imhtml , color ); if ( flags & PURPLE_CONNECTION_FLAG_FORMATTING_WBFO ) gtk_imhtml_set_whole_buffer_formatting_only ( imhtml , TRUE ); gtk_imhtml_set_whole_buffer_formatting_only ( imhtml , FALSE ); buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE ; imhtml_clear_formatting ( imhtml ); if ( flags & PURPLE_CONNECTION_FLAG_NO_IMAGES ) buttons &= ~ GTK_IMHTML_IMAGE ; if ( flags & PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY ) buttons |= GTK_IMHTML_CUSTOM_SMILEY ; buttons &= ~ GTK_IMHTML_CUSTOM_SMILEY ; gtk_imhtml_set_format_functions ( imhtml , buttons ); * GtkIMHtmlSmiley functions static void gtk_custom_smiley_allocated ( GdkPixbufLoader * loader , gpointer user_data ) smiley = ( GtkIMHtmlSmiley * ) user_data ; smiley -> icon = gdk_pixbuf_loader_get_animation ( loader ); g_object_ref ( G_OBJECT ( smiley -> icon )); #ifdef DEBUG_CUSTOM_SMILEY purple_debug_info ( "custom-smiley" , "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s' \n " , smiley -> icon , smiley -> smile ); static void gtk_custom_smiley_closed ( GdkPixbufLoader * loader , gpointer user_data ) GtkTextChildAnchor * anchor = NULL ; smiley = ( GtkIMHtmlSmiley * ) user_data ; #ifdef DEBUG_CUSTOM_SMILEY purple_debug_error ( "custom-smiley" , "gtk_custom_smiley_closed(): orphan smiley found: %p \n " , smiley ); g_object_unref ( G_OBJECT ( loader )); for ( current = smiley -> anchors ; current ; current = g_slist_next ( current )) { anchor = GTK_TEXT_CHILD_ANCHOR ( current -> data ); if ( gtk_text_child_anchor_get_deleted ( anchor )) icon = gtk_image_new_from_animation ( smiley -> icon ); #ifdef DEBUG_CUSTOM_SMILEY purple_debug_info ( "custom-smiley" , "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s' \n " , icon , smiley -> icon , smiley -> smile ); wids = gtk_text_child_anchor_get_widgets ( anchor ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_plaintext" , purple_unescape_html ( smiley -> smile ), g_free ); g_object_set_data_full ( G_OBJECT ( anchor ), "gtkimhtml_htmltext" , g_strdup ( smiley -> smile ), g_free ); GList * children = gtk_container_get_children ( GTK_CONTAINER ( wids -> data )); g_list_foreach ( children , ( GFunc ) gtk_widget_destroy , NULL ); gtk_container_add ( GTK_CONTAINER ( wids -> data ), icon ); gtk_text_view_add_child_at_anchor ( GTK_TEXT_VIEW ( smiley -> imhtml ), icon , anchor ); g_slist_free ( smiley -> anchors ); g_object_unref ( G_OBJECT ( loader )); gtk_custom_smiley_size_prepared ( GdkPixbufLoader * loader , gint width , gint height , gpointer data ) if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys" )) { int custom_smileys_size = purple_prefs_get_int ( PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size" ); if ( width <= custom_smileys_size && height <= custom_smileys_size ) height = height * custom_smileys_size / width ; width = custom_smileys_size ; width = width * custom_smileys_size / height ; height = custom_smileys_size ; gdk_pixbuf_loader_set_size ( loader , width , height ); gtk_imhtml_smiley_reload ( GtkIMHtmlSmiley * smiley ) g_object_unref ( smiley -> icon ); g_object_unref ( smiley -> loader ); /* XXX: does this crash? */ /* We do not use the pixbuf loader for a smiley that can be loaded * from a file. (e.g., local custom smileys) smiley -> loader = gdk_pixbuf_loader_new (); g_signal_connect ( smiley -> loader , "area_prepared" , G_CALLBACK ( gtk_custom_smiley_allocated ), smiley ); g_signal_connect ( smiley -> loader , "closed" , G_CALLBACK ( gtk_custom_smiley_closed ), smiley ); g_signal_connect ( smiley -> loader , "size_prepared" , G_CALLBACK ( gtk_custom_smiley_size_prepared ), smiley ); GtkIMHtmlSmiley * gtk_imhtml_smiley_create ( const char * file , const char * shortcut , gboolean hide , GtkIMHtmlSmileyFlags flags ) GtkIMHtmlSmiley * smiley = g_new0 ( GtkIMHtmlSmiley , 1 ); smiley -> file = g_strdup ( file ); smiley -> smile = g_strdup ( shortcut ); gtk_imhtml_smiley_reload ( smiley ); void gtk_imhtml_smiley_destroy ( GtkIMHtmlSmiley * smiley ) gtk_imhtml_disassociate_smiley ( smiley ); g_object_unref ( smiley -> icon ); g_object_unref ( smiley -> loader ); gboolean gtk_imhtml_class_register_protocol ( const char * name , gboolean ( * activate )( GtkIMHtml * imhtml , GtkIMHtmlLink * link ), gboolean ( * context_menu )( GtkIMHtml * imhtml , GtkIMHtmlLink * link , GtkWidget * menu )) GtkIMHtmlProtocol * proto ; g_return_val_if_fail ( name , FALSE ); klass = g_type_class_ref ( GTK_TYPE_IMHTML ); g_return_val_if_fail ( klass , FALSE ); if (( proto = imhtml_find_protocol ( name , TRUE ))) { klass -> protocols = g_list_remove ( klass -> protocols , proto ); proto = g_new0 ( GtkIMHtmlProtocol , 1 ); proto -> name = g_strdup ( name ); proto -> length = strlen ( name ); proto -> activate = activate ; proto -> context_menu = context_menu ; klass -> protocols = g_list_prepend ( klass -> protocols , proto ); gtk_imhtml_activate_tag ( GtkIMHtml * imhtml , GtkTextTag * tag ) /* A link was clicked--we emit the "url_clicked" signal * with the URL as the argument */ g_object_ref ( G_OBJECT ( tag )); g_signal_emit ( imhtml , signals [ URL_CLICKED ], 0 , g_object_get_data ( G_OBJECT ( tag ), "link_url" )); g_object_unref ( G_OBJECT ( tag )); g_object_set_data ( G_OBJECT ( tag ), "visited" , GINT_TO_POINTER ( TRUE )); gtk_imhtml_set_link_color ( GTK_IMHTML ( imhtml ), tag ); gboolean gtk_imhtml_link_activate ( GtkIMHtmlLink * link ) g_return_val_if_fail ( link , FALSE ); gtk_imhtml_activate_tag ( link -> imhtml , link -> tag ); g_signal_emit ( link -> imhtml , signals [ URL_CLICKED ], 0 , link -> url ); const char * gtk_imhtml_link_get_url ( GtkIMHtmlLink * link ) const GtkTextTag * gtk_imhtml_link_get_text_tag ( GtkIMHtmlLink * link ) static gboolean return_add_newline_cb ( GtkWidget * widget , gpointer data ) buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( widget )); /* Delete any currently selected text */ gtk_text_buffer_delete_selection ( buffer , TRUE , TRUE ); /* Insert a newline at the current cursor position */ mark = gtk_text_buffer_get_insert ( buffer ); gtk_text_buffer_get_iter_at_mark ( buffer , & iter , mark ); gtk_imhtml_insert_html_at_iter ( GTK_IMHTML ( widget ), " \n " , 0 , & iter ); * If we just newlined ourselves past the end of the visible area * then scroll down so the cursor is in view. gtk_text_view_scroll_to_mark ( GTK_TEXT_VIEW ( widget ), gtk_text_buffer_get_insert ( buffer ), * It's kind of a pain that we need this function and the above just * to reinstate the default GtkTextView behavior. It might be better * if GtkIMHtml didn't intercept the enter key and just required the * application to deal with it--it's really not much more work than it * is to connect to the current "message_send" signal. void gtk_imhtml_set_return_inserts_newline ( GtkIMHtml * imhtml ) g_signal_connect ( G_OBJECT ( imhtml ), "message_send" , G_CALLBACK ( return_add_newline_cb ), NULL ); void gtk_imhtml_set_populate_primary_clipboard ( GtkIMHtml * imhtml , gboolean populate ) signal_id = g_signal_handler_find ( imhtml -> text_buffer , G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_UNBLOCKED , 0 , 0 , NULL , mark_set_so_update_selection_cb , NULL ); /* We didn't find an unblocked signal handler, which means there is a blocked handler. Now unblock it. This is necessary to avoid a mutex-lock when the debug message saying 'no handler is blocked' is printed in the debug window. g_signal_handlers_unblock_matched ( imhtml -> text_buffer , G_SIGNAL_MATCH_FUNC , 0 , 0 , NULL , mark_set_so_update_selection_cb , NULL ); /* Block only if we found an unblocked handler */ g_signal_handler_block ( imhtml -> text_buffer , signal_id );