Mercurial > pidgin.yaz
changeset 21329:a04a0d3f9b4f
Add tooltips when hovering over rooms in the roomlist so the full channel
topics can be seen more easily. It's mostly copy & paste from gtkblist.c,
so could probably be cleaned up as these tips don't need all the complexity
of the blist tips.
author | Stu Tomlinson <stu@nosnilmot.com> |
---|---|
date | Sun, 11 Nov 2007 18:08:19 +0000 |
parents | daf85e00658b |
children | a97f24eda509 |
files | pidgin/gtkroomlist.c |
diffstat | 1 files changed, 315 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/pidgin/gtkroomlist.c Sun Nov 11 16:30:46 2007 +0000 +++ b/pidgin/gtkroomlist.c Sun Nov 11 18:08:19 2007 +0000 @@ -61,6 +61,15 @@ GtkWidget *tree; GHashTable *cats; /**< Meow. */ gint num_rooms, total_rooms; + GtkWidget *tipwindow; + GdkRectangle tip_rect; + guint timeout; + PangoLayout *tip_layout; + PangoLayout *tip_name_layout; + int tip_height; + int tip_width; + int tip_name_height; + int tip_name_width; } PidginRoomlist; enum { @@ -331,6 +340,310 @@ } } +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) +{ + PidginRoomlist *grl = (PidginRoomlist *)user_data; + GtkStyle *style; + int current_height, max_width; + int max_text_width; + GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET(grl->tree)); + + style = grl->tipwindow->style; + gtk_paint_flat_box(style, grl->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, grl->tipwindow, "tooltip", 0, 0, -1, -1); + + max_text_width = 0; + + max_text_width = MAX(grl->tip_width, grl->tip_name_width); + max_width = TOOLTIP_BORDER + SMALL_SPACE + max_text_width + TOOLTIP_BORDER; + + current_height = 12; + + if (dir == GTK_TEXT_DIR_RTL) { + gtk_paint_layout(style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE, + NULL, grl->tipwindow, "tooltip", + max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000), + current_height, grl->tip_name_layout); + } else { + gtk_paint_layout (style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE, + NULL, grl->tipwindow, "tooltip", + TOOLTIP_BORDER + SMALL_SPACE, current_height, grl->tip_name_layout); + } + if (dir != GTK_TEXT_DIR_RTL) { + gtk_paint_layout (style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE, + NULL, grl->tipwindow, "tooltip", + TOOLTIP_BORDER + SMALL_SPACE, current_height + grl->tip_name_height, grl->tip_layout); + } else { + gtk_paint_layout(style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE, + NULL, grl->tipwindow, "tooltip", + max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000), + current_height + grl->tip_name_height, + grl->tip_layout); + } + +} + +static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list) +{ + PidginRoomlist *grl = list->ui_data; + GtkWidget *tv = grl->tree; + PurpleRoomlistRoom *room; + GtkTreePath *path; + GtkTreeIter iter; + GValue val; + gchar *name, *tmp, *node_name; + GString *tooltip_text = NULL; + GList *l, *k; + gint j; + gboolean first = TRUE; + + 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; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path); + + val.g_type = 0; + gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val); + room = g_value_get_pointer(&val); + + if (!room || !(room->type & PURPLE_ROOMLIST_ROOMTYPE_ROOM)) + return FALSE; + + tooltip_text = g_string_new(""); + gtk_tree_model_get(GTK_TREE_MODEL(grl->model), &iter, NAME_COLUMN, &name, -1); + + for (j = NUM_OF_COLUMNS, l = room->fields, k = list->fields; l && k; j++, l = l->next, k = k->next) { + PurpleRoomlistField *f = k->data; + gchar *label; + if (f->hidden) + continue; + label = g_markup_escape_text(f->label, -1); + switch (f->type) { + case PURPLE_ROOMLIST_FIELD_BOOL: + g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, l->data ? "True" : "False"); + break; + case PURPLE_ROOMLIST_FIELD_INT: + g_string_append_printf(tooltip_text, "%s<b>%s:</b> %d", first ? "" : "\n", label, GPOINTER_TO_INT(l->data)); + break; + case PURPLE_ROOMLIST_FIELD_STRING: + tmp = g_markup_escape_text((char *)l->data, -1); + g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, tmp); + g_free(tmp); + break; + } + first = FALSE; + 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); + + tmp = g_markup_escape_text(name, -1); + g_free(name); + node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp); + g_free(tmp); + + pango_layout_set_markup(grl->tip_layout, tooltip_text->str, -1); + pango_layout_set_wrap(grl->tip_layout, PANGO_WRAP_WORD); + pango_layout_set_width(grl->tip_layout, 600000); + + pango_layout_get_size (grl->tip_layout, &grl->tip_width, &grl->tip_height); + grl->tip_width = PANGO_PIXELS(grl->tip_width); + grl->tip_height = PANGO_PIXELS(grl->tip_height); + + pango_layout_set_markup(grl->tip_name_layout, node_name, -1); + pango_layout_set_wrap(grl->tip_name_layout, PANGO_WRAP_WORD); + pango_layout_set_width(grl->tip_name_layout, 600000); + + pango_layout_get_size (grl->tip_name_layout, &grl->tip_name_width, &grl->tip_name_height); + grl->tip_name_width = PANGO_PIXELS(grl->tip_name_width) + SMALL_SPACE; + grl->tip_name_height = MAX(PANGO_PIXELS(grl->tip_name_height), SMALL_SPACE); + + g_free(node_name); + g_string_free(tooltip_text, TRUE); + + return TRUE; +} + +static void pidgin_roomlist_draw_tooltip(PurpleRoomlist *list, GtkWidget *widget) +{ + 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)) + 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); +} + static gboolean account_filter_func(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); @@ -650,6 +963,8 @@ g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list); g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list); g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list); + 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); /* Enable CTRL+F searching */ gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);