* @file pidgintooltip.c Pidgin Tooltip API * 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 * 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 "pidgintooltip.h" static gboolean enable_tooltips; static int tooltip_delay = -1; PidginTooltipPaint paint_tooltip; PidginTooltipPaint paint_tooltip; PidginTooltipCreateForTree create_tooltip; PidginTooltipCreate create_tooltip; initialize_tooltip_delay() settings = gtk_settings_get_default(); g_object_get(settings, "gtk-enable-tooltips", &enable_tooltips, NULL); g_object_get(settings, "gtk-tooltip-timeout", &tooltip_delay, NULL); destroy_tooltip_data(PidginTooltipData *data) if (data->common.treeview.path) gtk_tree_path_free(data->common.treeview.path); pidgin_tooltip_destroy(); 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; #if GTK_CHECK_VERSION(3,0,0) pidgin_tooltip_draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data) GtkAllocation allocation; gtk_widget_get_allocation(widget, &allocation); if (pidgin_tooltip.paint_tooltip) { GtkStyleContext *context = gtk_widget_get_style_context(widget); gtk_style_context_add_class(context, GTK_STYLE_CLASS_TOOLTIP); gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height); pidgin_tooltip.paint_tooltip(widget, cr, data); pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) if (pidgin_tooltip.paint_tooltip) { cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget))); gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, widget, "tooltip", 0, 0, -1, -1); pidgin_tooltip.paint_tooltip(widget, cr, data); setup_tooltip_window(void) tipwindow = gtk_window_new(GTK_WINDOW_POPUP); name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget)); gtk_window_set_type_hint(GTK_WINDOW(tipwindow), GDK_WINDOW_TYPE_HINT_TOOLTIP); 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"); gtk_widget_ensure_style(tipwindow); gtk_widget_realize(tipwindow); setup_tooltip_window_position(gpointer data, int w, int h) int scr_w, scr_h, x, y, dy; int preserved_x, preserved_y; GdkScreen *screen = NULL; GtkWidget *tipwindow = pidgin_tooltip.tipwindow; 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; dy = gdk_display_get_default_cursor_size(gdk_display_get_default()) / 2; h = mon_size.height - 10; x -= (x + w + 5) - scr_w; /* If the mouse covered by the tipwindow, move the tipwindow * to the righ side of the it */ if ((preserved_x >= x) && (preserved_x <= (x + w)) && (preserved_y >= y) && (preserved_y <= (y + h))) gtk_widget_set_size_request(tipwindow, w, h); gtk_window_move(GTK_WINDOW(tipwindow), x, y); gtk_widget_show(tipwindow); #if GTK_CHECK_VERSION(3,0,0) g_signal_connect(G_OBJECT(tipwindow), "draw", G_CALLBACK(pidgin_tooltip_draw_cb), data); g_signal_connect(G_OBJECT(tipwindow), "expose_event", G_CALLBACK(pidgin_tooltip_expose_event), data); /* 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) pidgin_tooltip_destroy(); pidgin_tooltip.widget = gtk_widget_get_toplevel(widget); pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window(); pidgin_tooltip.paint_tooltip = paint_tooltip; if (!create_tooltip(tipwindow, userdata, &w, &h)) { pidgin_tooltip_destroy(); setup_tooltip_window_position(userdata, w, h); reset_data_treepath(PidginTooltipData *data) gtk_tree_path_free(data->common.treeview.path); data->common.treeview.path = NULL; pidgin_tooltip_draw(PidginTooltipData *data) pidgin_tooltip_destroy(); pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget); pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window(); pidgin_tooltip.paint_tooltip = data->paint_tooltip; if (!data->common.widget.create_tooltip(tipwindow, data->userdata, &w, &h)) { if (tipwindow == pidgin_tooltip.tipwindow) pidgin_tooltip_destroy(); setup_tooltip_window_position(data->userdata, w, h); pidgin_tooltip_draw_tree(PidginTooltipData *data) GtkTreePath *path = NULL; 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(); if (data->common.treeview.path) { if (gtk_tree_path_compare(data->common.treeview.path, path) == 0) { gtk_tree_path_free(path); gtk_tree_path_free(data->common.treeview.path); data->common.treeview.path = NULL; pidgin_tooltip_destroy(); pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget); pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window(); pidgin_tooltip.paint_tooltip = data->paint_tooltip; if (!data->common.treeview.create_tooltip(tipwindow, path, data->userdata, &w, &h)) { if (tipwindow == pidgin_tooltip.tipwindow) pidgin_tooltip_destroy(); gtk_tree_path_free(path); setup_tooltip_window_position(data->userdata, w, h); data->common.treeview.path = path; g_signal_connect_swapped(G_OBJECT(pidgin_tooltip.tipwindow), "destroy", G_CALLBACK(reset_data_treepath), data); pidgin_tooltip_timeout(gpointer data) PidginTooltipData *tdata = data; pidgin_tooltip.timeout = 0; if (GTK_IS_TREE_VIEW(tdata->widget)) pidgin_tooltip_draw_tree(data); pidgin_tooltip_draw(data); row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata) if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tv))) return FALSE; /* The cursor is probably on the TreeView's header. */ initialize_tooltip_delay(); if (pidgin_tooltip.timeout) { if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y)) /* 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); pidgin_tooltip_destroy(); gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect); gtk_tree_path_free(path); pidgin_tooltip.timeout = g_timeout_add(tooltip_delay, (GSourceFunc)pidgin_tooltip_timeout, userdata); widget_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata) pidgin_tooltip_destroy(); gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata, PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip) PidginTooltipData *tdata = g_new0(PidginTooltipData, 1); tdata->userdata = userdata; tdata->common.treeview.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(widget_leave_cb), NULL); g_signal_connect(G_OBJECT(tree), "scroll-event", G_CALLBACK(widget_leave_cb), NULL); g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata); widget_motion_cb(GtkWidget *widget, GdkEvent *event, gpointer data) initialize_tooltip_delay(); pidgin_tooltip_destroy(); pidgin_tooltip.timeout = g_timeout_add(tooltip_delay, (GSourceFunc)pidgin_tooltip_timeout, data); gboolean pidgin_tooltip_setup_for_widget(GtkWidget *widget, gpointer userdata, PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip) PidginTooltipData *wdata = g_new0(PidginTooltipData, 1); wdata->userdata = userdata; wdata->common.widget.create_tooltip = create_tooltip; wdata->paint_tooltip = paint_tooltip; g_signal_connect(G_OBJECT(widget), "motion-notify-event", G_CALLBACK(widget_motion_cb), wdata); g_signal_connect(G_OBJECT(widget), "leave-notify-event", G_CALLBACK(widget_leave_cb), NULL); g_signal_connect(G_OBJECT(widget), "scroll-event", G_CALLBACK(widget_leave_cb), NULL); g_signal_connect_swapped(G_OBJECT(widget), "destroy", G_CALLBACK(destroy_tooltip_data), wdata);