# HG changeset patch # User Sadrul Habib Chowdhury # Date 1196926433 0 # Node ID 6bf73aea6450407aae1c90ffede89638d809f66e # Parent cbf778310bdd3daebfc11e76ba9ece7b099d2980 Some utility functions for showing tooltips. This is used by the buddylist, the roomlist and the infopane in the conversation window. The userlist in chats can also use this, I think. It works OK, but it may be possible to make the code a bit nicer. Any suggestions? diff -r cbf778310bdd -r 6bf73aea6450 pidgin/Makefile.am --- a/pidgin/Makefile.am Thu Dec 06 01:30:38 2007 +0000 +++ b/pidgin/Makefile.am Thu Dec 06 07:33:53 2007 +0000 @@ -119,7 +119,8 @@ gtkthemes.c \ gtkutils.c \ gtkwhiteboard.c \ - minidialog.c + minidialog.c \ + pidgintooltip.c pidgin_headers = \ eggtrayicon.h \ @@ -172,6 +173,7 @@ gtkutils.h \ gtkwhiteboard.h \ minidialog.h \ + pidgintooltip.h \ pidgin.h pidginincludedir=$(includedir)/pidgin diff -r cbf778310bdd -r 6bf73aea6450 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Thu Dec 06 01:30:38 2007 +0000 +++ b/pidgin/gtkblist.c Thu Dec 06 07:33:53 2007 +0000 @@ -59,6 +59,7 @@ #include "gtkscrollbook.h" #include "gtkutils.h" #include "pidgin/minidialog.h" +#include "pidgin/pidgintooltip.h" #include #include @@ -153,6 +154,7 @@ static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full); static const char *item_factory_translate_func (const char *path, gpointer func_data); static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter); +static gboolean buddy_is_displayable(PurpleBuddy *buddy); static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender); static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node); static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded); @@ -2630,7 +2632,7 @@ return td; } -static void pidgin_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, PurpleBlistNode *node) +static gboolean pidgin_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, gpointer data) { GtkStyle *style; int current_height, max_width; @@ -2638,10 +2640,10 @@ int max_avatar_width; GList *l; int prpl_col = 0; - GtkTextDirection dir = gtk_widget_get_direction(widget); + GtkTextDirection dir = gtk_widget_get_direction(widget); if(gtkblist->tooltipdata == NULL) - return; + return FALSE; style = gtkblist->tipwindow->style; gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, @@ -2740,10 +2742,11 @@ current_height += MAX(td->name_height + td->height, td->avatar_height) + TOOLTIP_BORDER; } -} - - -void pidgin_blist_tooltip_destroy() + return FALSE; +} + +static void +pidgin_blist_destroy_tooltip_data() { while(gtkblist->tooltipdata) { struct tooltip_data *td = gtkblist->tooltipdata->data; @@ -2759,12 +2762,62 @@ g_free(td); gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata); } - - if (gtkblist->tipwindow == NULL) - return; - - gtk_widget_destroy(gtkblist->tipwindow); - gtkblist->tipwindow = NULL; +} + +void pidgin_blist_tooltip_destroy() +{ + pidgin_blist_destroy_tooltip_data(); + pidgin_tooltip_destroy(); +} + +static gboolean +pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h) +{ + PurpleBlistNode *node = data; + int width, height; + + gtkblist->tipwindow = widget; + if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) { + struct tooltip_data *td = create_tip_for_node(node, TRUE); + gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); + width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + + MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER; + height = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height)) + + TOOLTIP_BORDER; + } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) { + PurpleBlistNode *child; + PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node); + int max_text_width = 0; + int max_avatar_width = 0; + width = height = 0; + + for(child = node->child; child; child = child->next) + { + if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) { + struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child)); + if (b == (PurpleBuddy *)child) { + gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td); + } else { + gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); + } + max_text_width = MAX(max_text_width, MAX(td->width, td->name_width)); + max_avatar_width = MAX(max_avatar_width, td->avatar_width); + height += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height), + TOOLTIP_BORDER + td->height + td->name_height); + } + } + height += TOOLTIP_BORDER; + width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER; + } else { + return FALSE; + } + + if (w) + *w = width; + if (h) + *h = height; + + return TRUE; } static gboolean pidgin_blist_expand_timeout(GtkWidget *tv) @@ -2826,164 +2879,9 @@ purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline"))); } -static gboolean pidgin_blist_tooltip_timeout(GtkWidget *tv) -{ - GtkTreePath *path; - GtkTreeIter iter; - PurpleBlistNode *node; - GValue val; - gboolean editable = FALSE; - - /* If we're editing a cell (e.g. alias editing), don't show the tooltip */ - g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL); - if (editable) - return FALSE; - - if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y + (gtkblist->tip_rect.height/2), - &path, NULL, NULL, NULL)) - return FALSE; - gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path); - val.g_type = 0; - gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val); - node = g_value_get_pointer(&val); - - pidgin_blist_draw_tooltip(node, gtkblist->window); - - gtk_tree_path_free(path); - return FALSE; -} - void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget) { - int scr_w, scr_h, w, h, x, y; -#if GTK_CHECK_VERSION(2,2,0) - int mon_num; - GdkScreen *screen = NULL; -#endif - gboolean tooltip_top = FALSE; - struct _pidgin_blist_node *gtknode; - GdkRectangle mon_size; - int sig; - const char *name; - - if (node == NULL) - return; - - /* - * Attempt to free the previous tooltip. I have a feeling - * this is never needed... but just in case. - */ - pidgin_blist_tooltip_destroy(); - - gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP); - - if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) { - struct tooltip_data *td = create_tip_for_node(node, TRUE); - gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); - w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + - MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER; - h = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height)) - + TOOLTIP_BORDER; - } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) { - PurpleBlistNode *child; - PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node); - int max_text_width = 0; - int max_avatar_width = 0; - w = h = 0; - - for(child = node->child; child; child = child->next) - { - if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) { - struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child)); - if (b == (PurpleBuddy *)child) { - gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td); - } else { - gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); - } - max_text_width = MAX(max_text_width, MAX(td->width, td->name_width)); - max_avatar_width = MAX(max_avatar_width, td->avatar_width); - h += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height), - TOOLTIP_BORDER + td->height + td->name_height); - } - } - h += TOOLTIP_BORDER; - w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER; - } else { - gtk_widget_destroy(gtkblist->tipwindow); - gtkblist->tipwindow = NULL; - return; - } - - if (gtkblist->tooltipdata == NULL) { - gtk_widget_destroy(gtkblist->tipwindow); - gtkblist->tipwindow = NULL; - return; - } - - gtknode = node->ui_data; - - name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget))); - gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE); - gtk_window_set_title(GTK_WINDOW(gtkblist->tipwindow), name ? name : _("Buddy List")); - gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE); - gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips"); - g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event", - G_CALLBACK(pidgin_blist_paint_tip), NULL); - gtk_widget_ensure_style (gtkblist->tipwindow); - -#if GTK_CHECK_VERSION(2,2,0) - gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL); - mon_num = gdk_screen_get_monitor_at_point(screen, x, y); - gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size); - - scr_w = mon_size.width + mon_size.x; - scr_h = mon_size.height + mon_size.y; -#else - scr_w = gdk_screen_width(); - scr_h = gdk_screen_height(); - gdk_window_get_pointer(NULL, &x, &y, NULL); - mon_size.x = 0; - mon_size.y = 0; -#endif - -#if GTK_CHECK_VERSION(2,2,0) - if (w > mon_size.width) - w = mon_size.width - 10; - - if (h > mon_size.height) - h = mon_size.height - 10; -#endif - - x -= ((w >> 1) + 4); - - if ((y + h + 4) > scr_h || tooltip_top) - y = y - h - 5; - else - y = y + 6; - - if (y < mon_size.y) - y = mon_size.y; - - if (y != mon_size.y) { - if ((x + w) > scr_w) - x -= (x + w + 5) - scr_w; - else if (x < mon_size.x) - x = mon_size.x; - } else { - x -= (w / 2 + 10); - if (x < mon_size.x) - x = mon_size.x; - } - - gtk_widget_set_size_request(gtkblist->tipwindow, w, h); - gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y); - gtk_widget_show(gtkblist->tipwindow); - - /* Hide the tooltip when the widget is destroyed */ - sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_blist_tooltip_destroy), NULL); - g_signal_connect_swapped(G_OBJECT(gtkblist->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig)); - - return; + pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip); } static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context, @@ -3033,31 +2931,34 @@ return FALSE; } -static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null) -{ - GtkTreePath *path; - int delay; - - delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); - - if (delay == 0) +static gboolean +pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path, + gpointer null, int *w, int *h) +{ + GtkTreeIter iter; + PurpleBlistNode *node; + GValue val; + gboolean editable = FALSE; + + /* If we're editing a cell (e.g. alias editing), don't show the tooltip */ + g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL); + if (editable) return FALSE; - if (gtkblist->timeout) { - if ((event->y > gtkblist->tip_rect.y) && ((event->y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y)) - return FALSE; - /* We've left the cell. Remove the timeout and create a new one below */ - pidgin_blist_tooltip_destroy(); - g_source_remove(gtkblist->timeout); - } - - gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL); - gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, >kblist->tip_rect); - - if (path) - gtk_tree_path_free(path); - gtkblist->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_tooltip_timeout, tv); - + if (gtkblist->tooltipdata) { + gtkblist->tipwindow = NULL; + pidgin_blist_destroy_tooltip_data(); + } + + gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path); + val.g_type = 0; + gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val); + node = g_value_get_pointer(&val); + return pidgin_blist_create_tooltip_for_node(widget, node, w, h); +} + +static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null) +{ if (gtkblist->mouseover_contact) { if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) { pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact); @@ -3070,7 +2971,6 @@ static void pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n) { - if (gtkblist->timeout) { g_source_remove(gtkblist->timeout); gtkblist->timeout = 0; @@ -3081,8 +2981,6 @@ gtkblist->drag_timeout = 0; } - pidgin_blist_tooltip_destroy(); - if (gtkblist->mouseover_contact && !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) && (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) { @@ -5152,12 +5050,14 @@ #ifdef _WIN32 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL); #endif - g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL); + g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL); + g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL); /* Tooltips */ - g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL); - g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL); + pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL, + pidgin_blist_create_tooltip, + pidgin_blist_paint_tip); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE); diff -r cbf778310bdd -r 6bf73aea6450 pidgin/gtkroomlist.c --- a/pidgin/gtkroomlist.c Thu Dec 06 01:30:38 2007 +0000 +++ b/pidgin/gtkroomlist.c Thu Dec 06 07:33:53 2007 +0000 @@ -28,6 +28,8 @@ #include "pidgin.h" #include "gtkutils.h" #include "pidginstock.h" +#include "pidgintooltip.h" + #include "debug.h" #include "account.h" #include "connection.h" @@ -340,33 +342,14 @@ } } -static void pidgin_roomlist_tooltip_destroy(PidginRoomlist *grl) -{ - if ((grl == NULL) || (grl->tipwindow == NULL)) - return; - - gtk_widget_destroy(grl->tipwindow); - grl->tipwindow = NULL; -} - -static void pidgin_roomlist_tooltip_destroy_cb(GObject *object, PidginRoomlist *grl) -{ - if ((grl == NULL) || (grl->tipwindow == NULL)) - return; - - if (grl->timeout) - g_source_remove(grl->timeout); - grl->timeout = 0; - - pidgin_roomlist_tooltip_destroy(grl); -} - #define SMALL_SPACE 6 #define TOOLTIP_BORDER 12 -static void pidgin_roomlist_paint_tip(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +static gboolean +pidgin_roomlist_paint_tooltip(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { - PidginRoomlist *grl = (PidginRoomlist *)user_data; + PurpleRoomlist *list = user_data; + PidginRoomlist *grl = list->ui_data; GtkStyle *style; int current_height, max_width; int max_text_width; @@ -404,15 +387,13 @@ current_height + grl->tip_name_height, grl->tip_layout); } - + return FALSE; } -static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list) +static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list, GtkTreePath *path) { PidginRoomlist *grl = list->ui_data; - GtkWidget *tv = grl->tree; PurpleRoomlistRoom *room; - GtkTreePath *path; GtkTreeIter iter; GValue val; gchar *name, *tmp, *node_name; @@ -421,10 +402,11 @@ gint j; gboolean first = TRUE; +#if 0 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2), &path, NULL, NULL, NULL)) return FALSE; - +#endif gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path); val.g_type = 0; @@ -460,8 +442,6 @@ g_free(label); } - gtk_tree_path_free(path); - grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL); grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL); @@ -492,156 +472,22 @@ return TRUE; } -static void pidgin_roomlist_draw_tooltip(PurpleRoomlist *list, GtkWidget *widget) +static gboolean +pidgin_roomlist_create_tooltip(GtkWidget *widget, GtkTreePath *path, + gpointer data, int *w, int *h) { + PurpleRoomlist *list = data; PidginRoomlist *grl = list->ui_data; - int scr_w, scr_h, w, h, x, y; -#if GTK_CHECK_VERSION(2,2,0) - int mon_num; - GdkScreen *screen = NULL; -#endif - GdkRectangle mon_size; - int sig; - const char *name; - - pidgin_roomlist_tooltip_destroy(grl); - grl->tipwindow = gtk_window_new(GTK_WINDOW_POPUP); - gtk_widget_ensure_style (grl->tipwindow); - - if (!pidgin_roomlist_create_tip(list)) { - pidgin_roomlist_tooltip_destroy(grl); - return; - } - - name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget))); - gtk_widget_set_app_paintable(grl->tipwindow, TRUE); - gtk_window_set_title(GTK_WINDOW(grl->tipwindow), name ? name : _("Room List")); - gtk_window_set_resizable(GTK_WINDOW(grl->tipwindow), FALSE); - gtk_widget_set_name(grl->tipwindow, "gtk-tooltips"); - g_signal_connect(G_OBJECT(grl->tipwindow), "expose_event", - G_CALLBACK(pidgin_roomlist_paint_tip), grl); - - w = TOOLTIP_BORDER + SMALL_SPACE + - MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER; - h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height - + TOOLTIP_BORDER; - -#if GTK_CHECK_VERSION(2,2,0) - gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL); - mon_num = gdk_screen_get_monitor_at_point(screen, x, y); - gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size); - - scr_w = mon_size.width + mon_size.x; - scr_h = mon_size.height + mon_size.y; -#else - scr_w = gdk_screen_width(); - scr_h = gdk_screen_height(); - gdk_window_get_pointer(NULL, &x, &y, NULL); - mon_size.x = 0; - mon_size.y = 0; -#endif - -#if GTK_CHECK_VERSION(2,2,0) - if (w > mon_size.width) - w = mon_size.width - 10; - - if (h > mon_size.height) - h = mon_size.height - 10; -#endif - x -= ((w >> 1) + 4); - - if ((y + h + 4) > scr_h) - y = y - h - 5; - else - y = y + 6; - - if (y < mon_size.y) - y = mon_size.y; - - if (y != mon_size.y) { - if ((x + w) > scr_w) - x -= (x + w + 5) - scr_w; - else if (x < mon_size.x) - x = mon_size.x; - } else { - x -= (w / 2 + 10); - if (x < mon_size.x) - x = mon_size.x; - } - - gtk_widget_set_size_request(grl->tipwindow, w, h); - gtk_window_move(GTK_WINDOW(grl->tipwindow), x, y); - gtk_widget_show(grl->tipwindow); - - /* Hide the tooltip when the widget is destroyed */ - sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_roomlist_tooltip_destroy_cb), grl); - g_signal_connect_swapped(G_OBJECT(grl->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig)); -} - -static gboolean pidgin_roomlist_tooltip_timeout(PurpleRoomlist *list) -{ - PidginRoomlist *grl = list->ui_data; - GtkWidget *tv = grl->tree; - GtkTreePath *path; - - pidgin_roomlist_tooltip_destroy(grl); - - if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2), - &path, NULL, NULL, NULL)) + grl->tipwindow = widget; + if (!pidgin_roomlist_create_tip(data, path)) return FALSE; - - pidgin_roomlist_draw_tooltip(list, GTK_WIDGET(grl->tree)); - - return FALSE; -} - -static gboolean row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer user_data) -{ - PurpleRoomlist *list = user_data; - PidginRoomlist *grl = list->ui_data; - GtkTreePath *path; - int delay; - - /* XXX: should this be using the blist delay pref? */ - delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); - - if (delay == 0) - return FALSE; - - if (grl->timeout) { - if ((event->y > grl->tip_rect.y) && ((event->y - grl->tip_rect.height) < grl->tip_rect.y)) - return FALSE; - /* We've left the cell. Remove the timeout and create a new one below */ - pidgin_roomlist_tooltip_destroy(grl); - } - - gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL); - - if (path == NULL) { - pidgin_roomlist_tooltip_destroy(grl); - return FALSE; - } - - gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &grl->tip_rect); - - if (path) - gtk_tree_path_free(path); - grl->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_roomlist_tooltip_timeout, list); - - return FALSE; -} - -static void row_leave_cb(GtkWidget *tv, GdkEventCrossing *e, gpointer user_data) -{ - PurpleRoomlist *list = user_data; - PidginRoomlist *grl = list->ui_data; - - if (grl->timeout) { - g_source_remove(grl->timeout); - grl->timeout = 0; - } - - pidgin_roomlist_tooltip_destroy(grl); + if (w) + *w = TOOLTIP_BORDER + SMALL_SPACE + + MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER; + if (h) + *h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height + + TOOLTIP_BORDER; + return TRUE; } static gboolean account_filter_func(PurpleAccount *account) @@ -967,6 +813,9 @@ g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list); g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list); #endif + pidgin_tooltip_setup_for_treeview(tree, list, + pidgin_roomlist_create_tooltip, + pidgin_roomlist_paint_tooltip); /* Enable CTRL+F searching */ gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN); diff -r cbf778310bdd -r 6bf73aea6450 pidgin/pidgintooltip.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidgintooltip.c Thu Dec 06 07:33:53 2007 +0000 @@ -0,0 +1,268 @@ +/** + * @file pidgintooltip.c Pidgin Tooltip API + * @ingroup pidgin + */ + +/* pidgin + * + * 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 + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it 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 + */ + +#include "internal.h" +#include "prefs.h" +#include "pidgin.h" +#include "pidgintooltip.h" + +struct +{ + GtkWidget *widget; + int timeout; + GdkRectangle tip_rect; + GtkWidget *tipwindow; + PidginTooltipPaint paint_tooltip; +} pidgin_tooltip; + +typedef struct +{ + GtkWidget *widget; + gpointer userdata; + PidginTooltipCreateForTree create_tooltip; + PidginTooltipPaint paint_tooltip; + GtkTreePath *path; +} PidginTooltipData; + +static void +destroy_tooltip_data(PidginTooltipData *data) +{ + gtk_tree_path_free(data->path); + g_free(data); +} + +void pidgin_tooltip_destroy() +{ + if (pidgin_tooltip.timeout > 0) { + g_source_remove(pidgin_tooltip.timeout); + pidgin_tooltip.timeout = 0; + } + if (pidgin_tooltip.tipwindow) { + gtk_widget_destroy(pidgin_tooltip.tipwindow); + pidgin_tooltip.tipwindow = NULL; + } +} + +static void +pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + if (pidgin_tooltip.paint_tooltip) + pidgin_tooltip.paint_tooltip(widget, event, data); +} + +static void +setup_tooltip_window(gpointer data, int w, int h) +{ + const char *name; + int sig; + int scr_w, scr_h, x, y; +#if GTK_CHECK_VERSION(2,2,0) + int mon_num; + GdkScreen *screen = NULL; +#endif + GdkRectangle mon_size; + GtkWidget *tipwindow = pidgin_tooltip.tipwindow; + + name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget)); + gtk_widget_set_app_paintable(tipwindow, TRUE); + gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip")); + gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE); + gtk_widget_set_name(tipwindow, "gtk-tooltips"); + g_signal_connect(G_OBJECT(tipwindow), "expose_event", + G_CALLBACK(pidgin_tooltip_expose_event), data); + +#if GTK_CHECK_VERSION(2,2,0) + gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL); + mon_num = gdk_screen_get_monitor_at_point(screen, x, y); + gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size); + + scr_w = mon_size.width + mon_size.x; + scr_h = mon_size.height + mon_size.y; +#else + scr_w = gdk_screen_width(); + scr_h = gdk_screen_height(); + gdk_window_get_pointer(NULL, &x, &y, NULL); + mon_size.x = 0; + mon_size.y = 0; +#endif + +#if GTK_CHECK_VERSION(2,2,0) + if (w > mon_size.width) + w = mon_size.width - 10; + + if (h > mon_size.height) + h = mon_size.height - 10; +#endif + x -= ((w >> 1) + 4); + + if ((y + h + 4) > scr_h) + y = y - h - 5; + else + y = y + 6; + + if (y < mon_size.y) + y = mon_size.y; + + if (y != mon_size.y) { + if ((x + w) > scr_w) + x -= (x + w + 5) - scr_w; + else if (x < mon_size.x) + x = mon_size.x; + } else { + x -= (w / 2 + 10); + if (x < mon_size.x) + x = mon_size.x; + } + + gtk_widget_set_size_request(tipwindow, w, h); + gtk_window_move(GTK_WINDOW(tipwindow), x, y); + gtk_widget_show(tipwindow); + + /* Hide the tooltip when the widget is destroyed */ + sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL); + g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig)); +} + +void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata, + PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip) +{ + GtkWidget *tipwindow; + int w, h; + + pidgin_tooltip_destroy(); + pidgin_tooltip.tipwindow = tipwindow = gtk_window_new(GTK_WINDOW_POPUP); + pidgin_tooltip.widget = gtk_widget_get_toplevel(widget); + pidgin_tooltip.paint_tooltip = paint_tooltip; + gtk_widget_ensure_style(tipwindow); + + if (!create_tooltip(tipwindow, userdata, &w, &h)) { + pidgin_tooltip_destroy(); + return; + } + setup_tooltip_window(userdata, w, h); +} + +static void +pidgin_tooltip_draw(PidginTooltipData *data) +{ + GtkWidget *tipwindow; + GtkTreePath *path = NULL; + int w, h; + + if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget), + pidgin_tooltip.tip_rect.x, + pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2), + &path, NULL, NULL, NULL)) { + pidgin_tooltip_destroy(); + return; + } + + if (data->path) { + if (gtk_tree_path_compare(data->path, path) == 0) + return; + gtk_tree_path_free(data->path); + data->path = NULL; + } + + pidgin_tooltip.tipwindow = tipwindow = gtk_window_new(GTK_WINDOW_POPUP); + pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget); + pidgin_tooltip.paint_tooltip = data->paint_tooltip; + gtk_widget_ensure_style(tipwindow); + + if (!data->create_tooltip(tipwindow, path, data->userdata, &w, &h)) { + pidgin_tooltip_destroy(); + gtk_tree_path_free(path); + return; + } + + data->path = path; + setup_tooltip_window(data->userdata, w, h); +} + +static gboolean +pidgin_tooltip_timeout(gpointer data) +{ + pidgin_tooltip.timeout = 0; + pidgin_tooltip_draw(data); + return FALSE; +} + +static gboolean +row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata) +{ + GtkTreePath *path; + int delay; + + /* XXX: probably use something more generic? */ + delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); + if (delay == 0) + return FALSE; + + if (pidgin_tooltip.timeout) { + if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y)) + return FALSE; + /* We've left the cell. Remove the timeout and create a new one below */ + pidgin_tooltip_destroy(); + } + + gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL); + + if (path == NULL) { + pidgin_tooltip_destroy(); + return FALSE; + } + + gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect); + + if (path) + gtk_tree_path_free(path); + pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata); + + return FALSE; +} + +static gboolean +row_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata) +{ + pidgin_tooltip_destroy(); + return FALSE; +} + +gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata, + PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip) +{ + PidginTooltipData *tdata = g_new0(PidginTooltipData, 1); + tdata->widget = tree; + tdata->userdata = userdata; + tdata->create_tooltip = create_tooltip; + tdata->paint_tooltip = paint_tooltip; + + g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata); + g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), NULL); + g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata); + return TRUE; +} + diff -r cbf778310bdd -r 6bf73aea6450 pidgin/pidgintooltip.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidgintooltip.h Thu Dec 06 07:33:53 2007 +0000 @@ -0,0 +1,66 @@ +/** + * @file pidgintooltip.h Pidgin Tooltip API + * @ingroup pidgin + */ + +/* pidgin + * + * 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 + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it 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 + */ +#ifndef _PIDGIN_TOOLTIP_H_ +#define _PIDGIN_TOOLTIP_H_ + +#include + +typedef gboolean (*PidginTooltipCreateForTree)(GtkWidget *window, GtkTreePath *path, gpointer userdata, int *w, int *h); +typedef gboolean (*PidginTooltipCreate)(GtkWidget *window, gpointer userdata, int *w, int *h); +typedef gboolean (*PidginTooltipPaint)(GtkWidget *widget, GdkEventExpose *event, gpointer data); + +/** + * Setup tooltip drawing functions for a treeview. + * + * @param tree The treeview + * @param userdata The userdata to send to the callback functions + * @param create_cb Callback function to create the tooltip from the GtkTreePath + * @param paint_cb Callback function to paint the tooltip + * + * @return @c TRUE if the tooltip callbacks were setup correctly. + * @since + */ +gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata, + PidginTooltipCreateForTree create_cb, PidginTooltipPaint paint_cb); + +/** + * Destroy the tooltip. + */ +void pidgin_tooltip_destroy(void); + +/** + * Create and show a tooltip. + * + * @param widget The widget the tooltip is for + * @param userdata The userdata to send to the callback functions + * @param create_cb Callback function to create the tooltip from the GtkTreePath + * @param paint_cb Callback function to paint the tooltip + * + * @since + */ +void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata, + PidginTooltipCreate create_cb, PidginTooltipPaint paint_cb); +#endif