Mercurial > pidgin.yaz
changeset 21877:6bf73aea6450
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?
author | Sadrul Habib Chowdhury <imadil@gmail.com> |
---|---|
date | Thu, 06 Dec 2007 07:33:53 +0000 |
parents | cbf778310bdd |
children | 9d1002929512 |
files | pidgin/Makefile.am pidgin/gtkblist.c pidgin/gtkroomlist.c pidgin/pidgintooltip.c pidgin/pidgintooltip.h |
diffstat | 5 files changed, 461 insertions(+), 376 deletions(-) [+] |
line wrap: on
line diff
--- 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
--- 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 <gdk/gdkkeysyms.h> #include <gtk/gtk.h> @@ -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);
--- 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);
--- /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; +} +
--- /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 <gtk/gtk.h> + +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