# HG changeset patch # User Sean Egan # Date 1047469051 0 # Node ID b9e7ccf21f9c44375d3a5e13bae2a0cc03e7d2b5 # Parent 39068784aa0871d3dd5e74847b0b0d99867f797f [gaim-migrate @ 5037] I stayed up all night and readded tooltips. It feels great. GTK 2 provides no easy way to add a tooltip to a row of a GtkTreeView so I had to do it all by hand. In doing so, I took some liberties and did some stuff most tooltips can't do... my tooltips have Markup and Pixbufs in them =-). Tomorrow, KingAnt gets back. I'll let him readd the AIM-specific tooltip stuff. Capabilities and logged on time used to live in the buddy struct, but were removed for being too aim-centric. KingAnt will put them back, I'm sure. committer: Tailor Script diff -r 39068784aa08 -r b9e7ccf21f9c src/buddy.c --- a/src/buddy.c Wed Mar 12 07:09:27 2003 +0000 +++ b/src/buddy.c Wed Mar 12 11:37:31 2003 +0000 @@ -60,6 +60,8 @@ static gboolean gaim_gtk_blist_obscured = FALSE; static void gaim_gtk_blist_update(struct gaim_buddy_list *list, GaimBlistNode *node); +static char *gaim_get_tooltip_text(struct buddy *b); +static GdkPixbuf *gaim_gtk_blist_get_status_icon(struct buddy *b, GaimStatusIconSize size); /*************************************************** * Callbacks * @@ -336,6 +338,134 @@ } } +static void gaim_gtk_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, struct buddy *b) +{ + int x,y,scr_w,scr_h, w, h; + GtkStyle *style; + GdkPixbuf *pixbuf = gaim_gtk_blist_get_status_icon(b, GAIM_STATUS_ICON_LARGE); + PangoLayout *layout; + char *tooltiptext = gaim_get_tooltip_text(b); + + layout = gtk_widget_create_pango_layout (gtkblist->tipwindow, NULL); + pango_layout_set_markup(layout, tooltiptext, strlen(tooltiptext)); + style = gtkblist->tipwindow->style; + + gtk_paint_flat_box (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, gtkblist->tipwindow, "tooltip", 0, 0, -1, -1); + + gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, pixbuf, + 0, 0, 4, 4, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0); + + gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, TRUE, + NULL, gtkblist->tipwindow, "tooltip", 38, 4, layout); + + g_object_unref (pixbuf); + g_object_unref (layout); + g_free(tooltiptext); + return; +} + +static gboolean gaim_gtk_blist_tooltip_timeout(GtkWidget *tv) +{ + GtkTreePath *path; + GtkTreeIter iter; + GaimBlistNode *node; + char *tooltiptext; + GValue val = {0}; + + if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->rect.x, gtkblist->rect.y, &path, NULL, NULL, NULL)) + return FALSE; + gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path); + gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val); + node = g_value_get_pointer(&val); + + if (GAIM_BLIST_NODE_IS_BUDDY(node)) { + int scr_w,scr_h, w, h, x, y; + PangoLayout *layout; + struct buddy *buddy = (struct buddy*)node; + char *tooltiptext = gaim_get_tooltip_text(buddy); + gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE); + gtk_window_set_policy(GTK_WINDOW(gtkblist->tipwindow), FALSE, FALSE, TRUE); + gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips"); + g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event", + G_CALLBACK(gaim_gtk_blist_paint_tip), buddy); + gtk_widget_ensure_style (gtkblist->tipwindow); + + layout = gtk_widget_create_pango_layout (gtkblist->tipwindow, NULL); + pango_layout_set_markup(layout, tooltiptext, strlen(tooltiptext)); + scr_w = gdk_screen_width(); + scr_h = gdk_screen_height(); + pango_layout_get_size (layout, &w, &h); + w = PANGO_PIXELS(w) + 8; + h = PANGO_PIXELS(h) + 8; + + /* 38 is the size of a large status icon plus 4 pixels padding on each side. + I should #define this or something */ + w = w + 38; + h = MAX(h, 38); + + gdk_window_get_pointer(NULL, &x, &y, NULL); + if (GTK_WIDGET_NO_WINDOW(gtkblist->window)) + y+=gtkblist->window->allocation.y; + + x -= ((w >> 1) + 4); + + if ((x + w) > scr_w) + x -= (x + w) - scr_w; + else if (x < 0) + x = 0; + + if ((y + h + 4) > scr_h) + y = y - h; + else + y = y + 6; + g_object_unref (layout); + g_free(tooltiptext); + gtk_widget_set_size_request(gtkblist->tipwindow, w, h); + gtk_widget_set_uposition(gtkblist->tipwindow, x, y); + gtk_widget_show(gtkblist->tipwindow); + } + + gtk_tree_path_free(path); + return FALSE; +} + +static void gaim_gtk_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null) +{ + GtkTreePath *path; + + if (gtkblist->timeout) { + if ((event->y > gtkblist->rect.y) && ((event->y - gtkblist->rect.height) < gtkblist->rect.y)) + return; + /* We've left the cell. Remove the timeout and create a new one below */ + if (gtkblist->tipwindow) { + gtk_widget_destroy(gtkblist->tipwindow); + gtkblist->tipwindow = NULL; + } + + 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->rect); + if (path) + gtk_tree_path_free(path); + gtkblist->timeout = g_timeout_add(500, (GSourceFunc)gaim_gtk_blist_tooltip_timeout, tv); +} + +static void gaim_gtk_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n) +{ + if (gtkblist->timeout == 0) + return; + if (gtkblist->tipwindow) { + gtk_widget_destroy(gtkblist->tipwindow); + gtkblist->tipwindow = NULL; + } + g_source_remove(gtkblist->timeout); + gtkblist->timeout = 0; +} + /*************************************************** * Crap * ***************************************************/ @@ -385,7 +515,54 @@ * Private Utility functions * *********************************************************/ -static GdkPixbuf *gaim_gtk_blist_get_status_icon(struct buddy *b) +static char *gaim_get_tooltip_text(struct buddy *b) +{ + char *text = NULL; + struct prpl* prpl = find_prpl(b->account->protocol); + char *statustext = NULL; + char *tooltiptext = NULL; + char *warning = NULL, *idletime = NULL; + + if (prpl->tooltip_text) { + char *tmp = prpl->tooltip_text(b); + if (tmp) { + statustext = g_markup_escape_text(tmp, strlen(tmp)); + g_free(tmp); + } + } + + if (b->idle) { + int ihrs, imin; + time_t t; + time(&t); + ihrs = (t - b->idle) / 3600; + imin = ((t - b->idle) / 60) % 60; + if (ihrs) + idletime = g_strdup_printf(_("Idle %dh%02dm"), ihrs, imin); + else + idletime = g_strdup_printf(_("Idle %dm"), imin); + } + + if (b->evil > 0) + warning = g_strdup_printf(_("Warned %d%%"), b->evil); + + text = g_strdup_printf("%s" + "%s %s %s" /* Alias */ + "%s %s %s" /* Nickname */ + "%s %s" /* Idle */ + "%s %s" /* Warning */ + "%s %s", /* Status */ + b->name, + b->alias && b->alias[0] ? "\n" : "", b->alias && b->alias[0] ? _("Alias ") : "", b->alias ? b->alias : "", + b->server_alias ? "\n" : "", b->server_alias ? _("Nickname ") : "", b->server_alias ? b->server_alias : "", + b->idle ? "\n" : "", b->idle ? idletime : "", + b->evil ? "\n" : "", b->evil ? warning : "", + statustext ? "\n" : "", statustext ? statustext : ""); + return text; + +} + +static GdkPixbuf *gaim_gtk_blist_get_status_icon(struct buddy *b, GaimStatusIconSize size) { GdkPixbuf *status = NULL; GdkPixbuf *scale = NULL; @@ -403,7 +580,7 @@ if (prpl->list_emblems) prpl->list_emblems(b, &se, &sw, &nw, &ne); - if (!(blist_options & OPT_BLIST_SHOW_ICONS)) { + if (size == GAIM_STATUS_ICON_SMALL) { scalesize = 15; sw = nw = ne = NULL; /* So that only the se icon will composite */ } @@ -449,7 +626,7 @@ emblem = gdk_pixbuf_new_from_file(filename,NULL); g_free(filename); if (emblem) { - if (blist_options & OPT_BLIST_SHOW_ICONS) + if (size == GAIM_STATUS_ICON_LARGE) gdk_pixbuf_composite (emblem, scale, 15, 15, 15, 15, @@ -550,8 +727,7 @@ /* XXX Clean up this crap */ int ihrs, imin; - char *idletime = NULL, *warning = NULL; - const char *statustext = NULL; + char *idletime = NULL, *warning = NULL, *statustext = NULL; time_t t; if (!(blist_options & OPT_BLIST_SHOW_ICONS)) { @@ -570,7 +746,7 @@ imin = ((t - b->idle) / 60) % 60; if (prpl->status_text) { - char *tmp = prpl->status_text(b); + const char *tmp = prpl->status_text(b); if (tmp) statustext = g_markup_escape_text(tmp, strlen(tmp)); } @@ -681,6 +857,9 @@ g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(gaim_gtk_blist_drag_data_rcv_cb), NULL); g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(gaim_gtk_blist_drag_data_get_cb), NULL); + /* Tooltips */ + g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(gaim_gtk_blist_motion_cb), NULL); + g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(gaim_gtk_blist_leave_cb), NULL); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE); @@ -903,7 +1082,8 @@ char *mark; char *warning = NULL, *idle = NULL; - status = gaim_gtk_blist_get_status_icon((struct buddy*)node); + status = gaim_gtk_blist_get_status_icon((struct buddy*)node, + blist_options & OPT_BLIST_SHOW_ICONS ? GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL); avatar = gaim_gtk_blist_get_buddy_icon((struct buddy*)node); mark = gaim_gtk_blist_get_name_markup((struct buddy*)node); diff -r 39068784aa08 -r b9e7ccf21f9c src/gtklist.h --- a/src/gtklist.h Wed Mar 12 07:09:27 2003 +0000 +++ b/src/gtklist.h Wed Mar 12 11:37:31 2003 +0000 @@ -34,6 +34,10 @@ BLIST_COLUMNS }; +typedef enum { + GAIM_STATUS_ICON_LARGE, + GAIM_STATUS_ICON_SMALL +} GaimStatusIconSize; /************************************************************************** * @name Structures **************************************************************************/ @@ -49,6 +53,12 @@ GtkTreeStore *treemodel; /**< This is the treemodel. */ GtkWidget *bbox; /**< A Button Box. */ + + guint timeout; /**< The timeout for the tooltip. */ + GdkRectangle rect; /**< This is the bounding rectangle of the + cell we're currently hovering over. This is + used for tooltips. */ + GtkWidget *tipwindow; /**< The window used by the tooltip */ }; /** diff -r 39068784aa08 -r b9e7ccf21f9c src/protocols/msn/msn.c --- a/src/protocols/msn/msn.c Wed Mar 12 07:09:27 2003 +0000 +++ b/src/protocols/msn/msn.c Wed Mar 12 11:37:31 2003 +0000 @@ -1643,6 +1643,10 @@ return NULL; } +static const char *msn_tooltip_text(struct buddy *b) { + return g_strdup(msn_get_away_text(b->uc >> 1)); +} + static GList *msn_buddy_menu(struct gaim_connection *gc, char *who) { GList *m = NULL; @@ -2007,6 +2011,7 @@ ret->send_typing = msn_send_typing; ret->away_states = msn_away_states; ret->status_text = msn_status_text; + ret->tooltip_text = msn_tooltip_text; ret->set_away = msn_set_away; ret->set_idle = msn_set_idle; ret->add_buddy = msn_add_buddy; diff -r 39068784aa08 -r b9e7ccf21f9c src/protocols/yahoo/yahoo.c --- a/src/protocols/yahoo/yahoo.c Wed Mar 12 07:09:27 2003 +0000 +++ b/src/protocols/yahoo/yahoo.c Wed Mar 12 11:37:31 2003 +0000 @@ -1076,6 +1076,17 @@ } } +static char *yahoo_tooltip_text(struct buddy *b) +{ + struct yahoo_data *yd = (struct yahoo_data*)b->account->gc->proto_data; + if (b->uc & UC_UNAVAILABLE && b->uc >> 2 != YAHOO_STATUS_IDLE) { + if ((b->uc >> 2) != YAHOO_STATUS_CUSTOM) + return g_strdup(yahoo_get_status_string(b->uc >> 2)); + else + return g_strdup(g_hash_table_lookup(yd->hash, b->name)); + } +} + static GList *yahoo_buddy_menu(struct gaim_connection *gc, char *who) { GList *m = NULL; @@ -1347,6 +1358,7 @@ ret->buddy_menu = yahoo_buddy_menu; ret->list_icon = yahoo_list_icon; ret->status_text = yahoo_status_text; + ret->tooltip_text = yahoo_tooltip_text; ret->actions = yahoo_actions; ret->send_im = yahoo_send_im; ret->away_states = yahoo_away_states; diff -r 39068784aa08 -r b9e7ccf21f9c src/prpl.h --- a/src/prpl.h Wed Mar 12 07:09:27 2003 +0000 +++ b/src/prpl.h Wed Mar 12 11:37:31 2003 +0000 @@ -197,6 +197,11 @@ */ const char *(* status_text)(struct buddy *buddy); + /** + * Gets a string to put in the buddy list tooltip. + */ + char *(* tooltip_text)(struct buddy *buddy); + GList *(* away_states)(struct gaim_connection *gc); GList *(* actions)(struct gaim_connection *gc); /* user_opts is a GList* of g_malloc'd struct proto_user_opts */