diff options
Diffstat (limited to 'eel/eel-gtk-extensions.c')
-rw-r--r-- | eel/eel-gtk-extensions.c | 1247 |
1 files changed, 1247 insertions, 0 deletions
diff --git a/eel/eel-gtk-extensions.c b/eel/eel-gtk-extensions.c new file mode 100644 index 00000000..da78a532 --- /dev/null +++ b/eel/eel-gtk-extensions.c @@ -0,0 +1,1247 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* eel-gtk-extensions.c - implementation of new functions that operate on + gtk classes. Perhaps some of these should be + rolled into gtk someday. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: John Sullivan <[email protected]> + Ramiro Estrugo <[email protected]> + Darin Adler <[email protected]> +*/ + +#include <config.h> +#include "eel-gtk-extensions.h" + +#include "eel-gdk-pixbuf-extensions.h" +#include "eel-glib-extensions.h" +#include "eel-mate-extensions.h" +#include "eel-pango-extensions.h" +#include "eel-string.h" +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <gdk/gdk.h> +#include <gdk/gdkprivate.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <math.h> +#include "eel-marshal.h" +#include "eel-marshal.c" + +/* This number is fairly arbitrary. Long enough to show a pretty long + * menu title, but not so long to make a menu grotesquely wide. + */ +#define MAXIMUM_MENU_TITLE_LENGTH 48 + +/* Used for window position & size sanity-checking. The sizes are big enough to prevent + * at least normal-sized mate panels from obscuring the window at the screen edges. + */ +#define MINIMUM_ON_SCREEN_WIDTH 100 +#define MINIMUM_ON_SCREEN_HEIGHT 100 + + +/** + * eel_gtk_window_get_geometry_string: + * @window: a #GtkWindow + * + * Obtains the geometry string for this window, suitable for + * set_geometry_string(); assumes the window has NorthWest gravity + * + * Return value: geometry string, must be freed + **/ +char* +eel_gtk_window_get_geometry_string (GtkWindow *window) +{ + char *str; + int w, h, x, y; + + g_return_val_if_fail (GTK_IS_WINDOW (window), NULL); + g_return_val_if_fail (gtk_window_get_gravity (window) == + GDK_GRAVITY_NORTH_WEST, NULL); + + gtk_window_get_position (window, &x, &y); + gtk_window_get_size (window, &w, &h); + + str = g_strdup_printf ("%dx%d+%d+%d", w, h, x, y); + + return str; +} + +static void +send_delete_event (GtkWindow *window) +{ + /* Synthesize delete_event to close window. */ + + GdkEvent event; + GtkWidget *widget; + + widget = GTK_WIDGET (window); + + event.any.type = GDK_DELETE; + event.any.window = gtk_widget_get_window (widget); + event.any.send_event = TRUE; + + g_object_ref (event.any.window); + gtk_main_do_event (&event); + g_object_unref (event.any.window); +} + +static int +handle_standard_close_accelerator (GtkWindow *window, + GdkEventKey *event, + gpointer user_data) +{ + g_assert (GTK_IS_WINDOW (window)); + g_assert (event != NULL); + g_assert (user_data == NULL); + + if (eel_gtk_window_event_is_close_accelerator (window, event)) + { + send_delete_event (window); + g_signal_stop_emission_by_name ( + G_OBJECT (window), "key_press_event"); + return TRUE; + } + + return FALSE; +} + +/** + * eel_gtk_window_event_is_close_accelerator: + * + * Tests whether a key event is a standard window close accelerator. + * Not needed for clients that use eel_gtk_window_set_up_close_accelerator; + * use only if you must set up your own key_event handler for your own reasons. + **/ +gboolean +eel_gtk_window_event_is_close_accelerator (GtkWindow *window, GdkEventKey *event) +{ + g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (event->state & GDK_CONTROL_MASK) + { + /* Note: menu item equivalents are case-sensitive, so we will + * be case-sensitive here too. + */ + if (event->keyval == EEL_STANDARD_CLOSE_WINDOW_CONTROL_KEY) + { + return TRUE; + } + } + + + return FALSE; +} + +/** + * eel_gtk_window_set_up_close_accelerator: + * + * Sets up the standard keyboard equivalent to close the window. + * Call this for windows that don't set up a keyboard equivalent to + * close the window some other way, e.g. via a menu item accelerator. + * + * NOTE: do not use for GtkDialog, it already sets up the right + * stuff here. + * + * @window: The GtkWindow that should be hidden when the standard + * keyboard equivalent is typed. + **/ +void +eel_gtk_window_set_up_close_accelerator (GtkWindow *window) +{ + g_return_if_fail (GTK_IS_WINDOW (window)); + + if (GTK_IS_DIALOG (window)) + { + g_warning ("eel_gtk_window_set_up_close_accelerator: Should not mess with close accelerator on GtkDialogs"); + return; + } + + g_signal_connect (window, + "key_press_event", + G_CALLBACK (handle_standard_close_accelerator), + NULL); +} + +static void +sanity_check_window_position (int *left, int *top) +{ + g_assert (left != NULL); + g_assert (top != NULL); + + /* Make sure the top of the window is on screen, for + * draggability (might not be necessary with all window managers, + * but seems reasonable anyway). Make sure the top of the window + * isn't off the bottom of the screen, or so close to the bottom + * that it might be obscured by the panel. + */ + *top = CLAMP (*top, 0, gdk_screen_height() - MINIMUM_ON_SCREEN_HEIGHT); + + /* FIXME bugzilla.eazel.com 669: + * If window has negative left coordinate, set_uposition sends it + * somewhere else entirely. Not sure what level contains this bug (XWindows?). + * Hacked around by pinning the left edge to zero, which just means you + * can't set a window to be partly off the left of the screen using + * this routine. + */ + /* Make sure the left edge of the window isn't off the right edge of + * the screen, or so close to the right edge that it might be + * obscured by the panel. + */ + *left = CLAMP (*left, 0, gdk_screen_width() - MINIMUM_ON_SCREEN_WIDTH); +} + +static void +sanity_check_window_dimensions (guint *width, guint *height) +{ + g_assert (width != NULL); + g_assert (height != NULL); + + /* Pin the size of the window to the screen, so we don't end up in + * a state where the window is so big essential parts of it can't + * be reached (might not be necessary with all window managers, + * but seems reasonable anyway). + */ + *width = MIN (*width, gdk_screen_width()); + *height = MIN (*height, gdk_screen_height()); +} + +/** + * eel_gtk_window_set_initial_geometry: + * + * Sets the position and size of a GtkWindow before the + * GtkWindow is shown. It is an error to call this on a window that + * is already on-screen. Takes into account screen size, and does + * some sanity-checking on the passed-in values. + * + * @window: A non-visible GtkWindow + * @geometry_flags: A EelGdkGeometryFlags value defining which of + * the following parameters have defined values + * @left: pixel coordinate for left of window + * @top: pixel coordinate for top of window + * @width: width of window in pixels + * @height: height of window in pixels + */ +void +eel_gtk_window_set_initial_geometry (GtkWindow *window, + EelGdkGeometryFlags geometry_flags, + int left, + int top, + guint width, + guint height) +{ + GdkScreen *screen; + int real_left, real_top; + int screen_width, screen_height; + + g_return_if_fail (GTK_IS_WINDOW (window)); + + /* Setting the default size doesn't work when the window is already showing. + * Someday we could make this move an already-showing window, but we don't + * need that functionality yet. + */ + g_return_if_fail (!gtk_widget_get_visible (GTK_WIDGET (window))); + + if ((geometry_flags & EEL_GDK_X_VALUE) && (geometry_flags & EEL_GDK_Y_VALUE)) + { + real_left = left; + real_top = top; + + screen = gtk_window_get_screen (window); + screen_width = gdk_screen_get_width (screen); + screen_height = gdk_screen_get_height (screen); + + /* This is sub-optimal. GDK doesn't allow us to set win_gravity + * to South/East types, which should be done if using negative + * positions (so that the right or bottom edge of the window + * appears at the specified position, not the left or top). + * However it does seem to be consistent with other MATE apps. + */ + if (geometry_flags & EEL_GDK_X_NEGATIVE) + { + real_left = screen_width - real_left; + } + if (geometry_flags & EEL_GDK_Y_NEGATIVE) + { + real_top = screen_height - real_top; + } + + sanity_check_window_position (&real_left, &real_top); + gtk_window_move (window, real_left, real_top); + } + + if ((geometry_flags & EEL_GDK_WIDTH_VALUE) && (geometry_flags & EEL_GDK_HEIGHT_VALUE)) + { + sanity_check_window_dimensions (&width, &height); + gtk_window_set_default_size (GTK_WINDOW (window), (int)width, (int)height); + } +} + +/** + * eel_gtk_window_set_initial_geometry_from_string: + * + * Sets the position and size of a GtkWindow before the + * GtkWindow is shown. The geometry is passed in as a string. + * It is an error to call this on a window that + * is already on-screen. Takes into account screen size, and does + * some sanity-checking on the passed-in values. + * + * @window: A non-visible GtkWindow + * @geometry_string: A string suitable for use with eel_gdk_parse_geometry + * @minimum_width: If the width from the string is smaller than this, + * use this for the width. + * @minimum_height: If the height from the string is smaller than this, + * use this for the height. + * @ignore_position: If true position data from string will be ignored. + */ +void +eel_gtk_window_set_initial_geometry_from_string (GtkWindow *window, + const char *geometry_string, + guint minimum_width, + guint minimum_height, + gboolean ignore_position) +{ + int left, top; + guint width, height; + EelGdkGeometryFlags geometry_flags; + + g_return_if_fail (GTK_IS_WINDOW (window)); + g_return_if_fail (geometry_string != NULL); + + /* Setting the default size doesn't work when the window is already showing. + * Someday we could make this move an already-showing window, but we don't + * need that functionality yet. + */ + g_return_if_fail (!gtk_widget_get_visible (GTK_WIDGET (window))); + + geometry_flags = eel_gdk_parse_geometry (geometry_string, &left, &top, &width, &height); + + /* Make sure the window isn't smaller than makes sense for this window. + * Other sanity checks are performed in set_initial_geometry. + */ + if (geometry_flags & EEL_GDK_WIDTH_VALUE) + { + width = MAX (width, minimum_width); + } + if (geometry_flags & EEL_GDK_HEIGHT_VALUE) + { + height = MAX (height, minimum_height); + } + + /* Ignore saved window position if requested. */ + if (ignore_position) + { + geometry_flags &= ~(EEL_GDK_X_VALUE | EEL_GDK_Y_VALUE); + } + + eel_gtk_window_set_initial_geometry (window, geometry_flags, left, top, width, height); +} + +/** + * eel_pop_up_context_menu: + * + * Pop up a context menu under the mouse. + * The menu is sunk after use, so it will be destroyed unless the + * caller first ref'ed it. + * + * This function is more of a helper function than a gtk extension, + * so perhaps it belongs in a different file. + * + * @menu: The menu to pop up under the mouse. + * @offset_x: Number of pixels to displace the popup menu vertically + * @offset_y: Number of pixels to displace the popup menu horizontally + * @event: The event that invoked this popup menu. + **/ +void +eel_pop_up_context_menu (GtkMenu *menu, + gint16 offset_x, + gint16 offset_y, + GdkEventButton *event) +{ + GdkPoint offset; + int button; + + g_return_if_fail (GTK_IS_MENU (menu)); + + offset.x = offset_x; + offset.y = offset_y; + + /* The event button needs to be 0 if we're popping up this menu from + * a button release, else a 2nd click outside the menu with any button + * other than the one that invoked the menu will be ignored (instead + * of dismissing the menu). This is a subtle fragility of the GTK menu code. + */ + + if (event) + { + button = event->type == GDK_BUTTON_RELEASE + ? 0 + : event->button; + } + else + { + button = 0; + } + + gtk_menu_popup (menu, /* menu */ + NULL, /* parent_menu_shell */ + NULL, /* parent_menu_item */ + NULL, + &offset, /* data */ + button, /* button */ + event ? event->time : GDK_CURRENT_TIME); /* activate_time */ + + g_object_ref_sink (menu); + g_object_unref (menu); +} + +GtkMenuItem * +eel_gtk_menu_append_separator (GtkMenu *menu) +{ + return eel_gtk_menu_insert_separator (menu, -1); +} + +GtkMenuItem * +eel_gtk_menu_insert_separator (GtkMenu *menu, int index) +{ + GtkWidget *menu_item; + + menu_item = gtk_separator_menu_item_new (); + gtk_widget_show (menu_item); + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, index); + + return GTK_MENU_ITEM (menu_item); +} + +void +eel_gtk_menu_set_item_visibility (GtkMenu *menu, int index, gboolean visible) +{ + GList *children; + GtkWidget *menu_item; + + g_return_if_fail (GTK_IS_MENU (menu)); + + children = gtk_container_get_children (GTK_CONTAINER (menu)); + g_return_if_fail (index >= 0 && index < (int) g_list_length (children)); + + menu_item = GTK_WIDGET (g_list_nth_data (children, index)); + if (visible) + { + gtk_widget_show (menu_item); + } + else + { + gtk_widget_hide (menu_item); + } + + g_list_free (children); +} + +GtkWidget * +eel_gtk_menu_tool_button_get_button (GtkMenuToolButton *tool_button) +{ + GtkContainer *container; + GList *children; + GtkWidget *button; + + g_return_val_if_fail (GTK_IS_MENU_TOOL_BUTTON (tool_button), NULL); + + /* The menu tool button's button is the first child + * of the child hbox. */ + container = GTK_CONTAINER (gtk_bin_get_child (GTK_BIN (tool_button))); + children = gtk_container_get_children (container); + button = GTK_WIDGET (children->data); + + g_list_free (children); + + return button; +} + +gboolean +eel_point_in_allocation (const GtkAllocation *allocation, + int x, int y) +{ + g_return_val_if_fail (allocation != NULL, FALSE); + return x >= allocation->x + && y >= allocation->y + && x < allocation->x + allocation->width + && y < allocation->y + allocation->height; +} + +/* FIXME this function is dangerous, because gtk_widget_get_window (widget) coords (or + * other window-belonging-to-widget coords) do not need to be in the + * same coordinate system as widget->allocation. + * If you use this function, be aware of that. Someone should probably + * audit all uses, too. + */ +gboolean +eel_point_in_widget (GtkWidget *widget, + int x, int y) +{ + GtkAllocation allocation; + if (widget == NULL) + { + return FALSE; + } + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + gtk_widget_get_allocation (widget, &allocation); + return eel_point_in_allocation (&allocation, x, y); +} + +/** + * eel_gtk_widget_set_shown + * + * Show or hide a widget. + * @widget: The widget. + * @shown: Boolean value indicating whether the widget should be shown or hidden. + **/ +void +eel_gtk_widget_set_shown (GtkWidget *widget, gboolean shown) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (shown) + { + gtk_widget_show (widget); + } + else + { + gtk_widget_hide (widget); + } +} + +/* This stuff is stolen from Gtk. */ + +typedef struct DisconnectInfo +{ + GtkObject *object1; + guint disconnect_handler1; + guint signal_handler; + GtkObject *object2; + guint disconnect_handler2; +} DisconnectInfo; + +static void +alive_disconnecter (GtkObject *object, DisconnectInfo *info) +{ + g_assert (info != NULL); + g_assert (GTK_IS_OBJECT (info->object1)); + g_assert (info->disconnect_handler1 != 0); + g_assert (info->signal_handler != 0); + g_assert (GTK_IS_OBJECT (info->object2)); + g_assert (info->disconnect_handler2 != 0); + g_assert (object == info->object1 || object == info->object2); + + g_signal_handler_disconnect (info->object1, info->disconnect_handler1); + g_signal_handler_disconnect (info->object1, info->signal_handler); + g_signal_handler_disconnect (info->object2, info->disconnect_handler2); + + g_free (info); +} + +/** + * eel_gtk_signal_connect_full_while_alive + * + * Like gtk_signal_connect_while_alive, but works with full parameters. + **/ +void +eel_gtk_signal_connect_full_while_alive (GtkObject *object, + const gchar *name, + GCallback func, + GtkCallbackMarshal marshal, + gpointer data, + GDestroyNotify destroy_func, + gboolean object_signal, + gboolean after, + GtkObject *alive_object) +{ + DisconnectInfo *info; + + g_return_if_fail (GTK_IS_OBJECT (object)); + g_return_if_fail (name != NULL); + g_return_if_fail (func != NULL || marshal != NULL); + g_return_if_fail (object_signal == FALSE || object_signal == TRUE); + g_return_if_fail (after == FALSE || after == TRUE); + g_return_if_fail (GTK_IS_OBJECT (alive_object)); + + info = g_new (DisconnectInfo, 1); + info->object1 = object; + info->object2 = alive_object; + + + info->signal_handler = g_signal_connect_closure ( + object, name, + (object_signal + ? g_cclosure_new_swap + : g_cclosure_new) (func, data, (GClosureNotify) destroy_func), + after); + + info->disconnect_handler1 = g_signal_connect (G_OBJECT (object), + "destroy", + G_CALLBACK (alive_disconnecter), + info); + info->disconnect_handler2 = g_signal_connect (G_OBJECT (alive_object), + "destroy", + G_CALLBACK (alive_disconnecter), + info); +} + +typedef struct +{ + GtkObject *object; + guint object_destroy_handler; + + GtkWidget *realized_widget; + guint realized_widget_destroy_handler; + guint realized_widget_unrealized_handler; + + guint signal_handler; +} RealizeDisconnectInfo; + +static void +while_realized_disconnecter (GtkObject *object, + RealizeDisconnectInfo *info) +{ + g_assert (GTK_IS_OBJECT (object)); + g_assert (info != NULL); + g_assert (GTK_IS_OBJECT (info->object)); + g_assert (info->object_destroy_handler != 0); + g_assert (info->object_destroy_handler != 0); + g_assert (info->realized_widget_destroy_handler != 0); + g_assert (info->realized_widget_unrealized_handler != 0); + + g_signal_handler_disconnect (info->object, info->object_destroy_handler); + g_signal_handler_disconnect (info->object, info->signal_handler); + g_signal_handler_disconnect (info->realized_widget, info->realized_widget_destroy_handler); + g_signal_handler_disconnect (info->realized_widget, info->realized_widget_unrealized_handler); + g_free (info); +} + +/** + * eel_gtk_signal_connect_while_realized: + * + * @object: Object to connect to. + * @name: Name of signal to connect to. + * @callback: Caller's callback. + * @callback_data: Caller's callback_data. + * @realized_widget: Widget to monitor for realized state. Signal is connected + * while this wigget is realized. + * + * Connect to a signal of an object while another widget is realized. This is + * useful for non windowed widgets that need to monitor events in their ancestored + * windowed widget. The signal is automatically disconnected when &widget is + * unrealized. Also, the signal is automatically disconnected when either &object + * or &widget are destroyed. + **/ +void +eel_gtk_signal_connect_while_realized (GtkObject *object, + const char *name, + GCallback callback, + gpointer callback_data, + GtkWidget *realized_widget) +{ + RealizeDisconnectInfo *info; + + g_return_if_fail (GTK_IS_OBJECT (object)); + g_return_if_fail (name != NULL); + g_return_if_fail (name[0] != '\0'); + g_return_if_fail (callback != NULL); + g_return_if_fail (GTK_IS_WIDGET (realized_widget)); + g_return_if_fail (gtk_widget_get_realized (realized_widget)); + + info = g_new0 (RealizeDisconnectInfo, 1); + + info->object = object; + info->object_destroy_handler = + g_signal_connect (G_OBJECT (info->object), + "destroy", + G_CALLBACK (while_realized_disconnecter), + info); + + info->realized_widget = realized_widget; + info->realized_widget_destroy_handler = + g_signal_connect (G_OBJECT (info->realized_widget), + "destroy", + G_CALLBACK (while_realized_disconnecter), + info); + info->realized_widget_unrealized_handler = + g_signal_connect_after (G_OBJECT (info->realized_widget), + "unrealize", + G_CALLBACK (while_realized_disconnecter), + info); + + info->signal_handler = g_signal_connect (G_OBJECT (info->object), + name, callback, callback_data); +} + +/** + * eel_gtk_container_get_first_child. + * + * Returns the first child of a container. + * @container: The container. + **/ + +static void +get_first_callback (GtkWidget *widget, gpointer callback_data) +{ + GtkWidget **first_child_slot; + + g_assert (GTK_IS_WIDGET (widget)); + g_assert (callback_data != NULL); + + first_child_slot = callback_data; + + if (*first_child_slot == NULL) + { + *first_child_slot = widget; + /* We'd stop the iterating now if we could. */ + } + else + { + g_assert (GTK_IS_WIDGET (*first_child_slot)); + } +} + +GtkWidget * +eel_gtk_container_get_first_child (GtkContainer *container) +{ + GtkWidget *first_child; + + g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL); + + first_child = NULL; + gtk_container_foreach (container, get_first_callback, &first_child); + g_assert (first_child == NULL || GTK_IS_WIDGET (first_child)); + return first_child; +} + +typedef struct +{ + GtkCallback callback; + gpointer callback_data; +} container_foreach_deep_callback_data; + +static void +container_foreach_deep_callback (GtkWidget *child, gpointer data) +{ + container_foreach_deep_callback_data *deep_data; + + deep_data = (container_foreach_deep_callback_data *) data; + + deep_data->callback (child, deep_data->callback_data); + + if (GTK_IS_CONTAINER (child)) + { + gtk_container_foreach (GTK_CONTAINER (child), container_foreach_deep_callback, data); + } +} + +void +eel_gtk_container_foreach_deep (GtkContainer *container, + GtkCallback callback, + gpointer callback_data) +{ + container_foreach_deep_callback_data deep_data; + deep_data.callback = callback; + deep_data.callback_data = callback_data; + gtk_container_foreach (container, container_foreach_deep_callback, &deep_data); +} + +/* The standard gtk_adjustment_set_value ignores page size, which + * disagrees with the logic used by scroll bars, for example. + */ +void +eel_gtk_adjustment_set_value (GtkAdjustment *adjustment, + float value) +{ + float upper_page_start, clamped_value; + + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + upper_page_start = MAX (gtk_adjustment_get_upper (adjustment) - + gtk_adjustment_get_page_size (adjustment), + gtk_adjustment_get_lower (adjustment)); + clamped_value = CLAMP (value, gtk_adjustment_get_lower (adjustment), upper_page_start); + if (clamped_value != gtk_adjustment_get_value (adjustment)) + { + gtk_adjustment_set_value (adjustment, clamped_value); + gtk_adjustment_value_changed (adjustment); + } +} + +/* Clamp a value if the minimum or maximum has changed. */ +void +eel_gtk_adjustment_clamp_value (GtkAdjustment *adjustment) +{ + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + eel_gtk_adjustment_set_value (adjustment, + gtk_adjustment_get_value (adjustment)); +} + +/** + * eel_gtk_label_make_bold. + * + * Switches the font of label to a bold equivalent. + * @label: The label. + **/ +void +eel_gtk_label_make_bold (GtkLabel *label) +{ + PangoFontDescription *font_desc; + + font_desc = pango_font_description_new (); + + pango_font_description_set_weight (font_desc, + PANGO_WEIGHT_BOLD); + + /* This will only affect the weight of the font, the rest is + * from the current state of the widget, which comes from the + * theme or user prefs, since the font desc only has the + * weight flag turned on. + */ + gtk_widget_modify_font (GTK_WIDGET (label), font_desc); + + pango_font_description_free (font_desc); +} + +/** + * eel_gtk_label_set_scale: + * @label: + * @num_steps: + * + * Function is broken, see eel_gtk_label_make_larger() for explanation + * + **/ +void +eel_gtk_label_set_scale (GtkLabel *label, + double scale_factor) +{ + PangoAttrList *old_attr_list; + PangoAttrList *attr_list; + + g_return_if_fail (GTK_IS_LABEL (label)); + g_return_if_fail (scale_factor > 0); + + old_attr_list = gtk_label_get_attributes (label); + attr_list = eel_pango_attr_list_apply_global_attribute (old_attr_list, + pango_attr_scale_new (scale_factor)); + gtk_label_set_attributes (label, attr_list); + pango_attr_list_unref (attr_list); +} + +static void +get_layout_location (GtkLabel *label, + gint *xp, + gint *yp) +{ + GtkMisc *misc; + GtkWidget *widget; + float xalign, yalign; + int x, y, xpad, ypad; + int shadow_offset; + GtkAllocation allocation; + GtkRequisition req; + + shadow_offset = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), + "eel-label-shadow-offset")); + + misc = GTK_MISC (label); + widget = GTK_WIDGET (label); + gtk_misc_get_alignment (misc, &xalign, &yalign); + gtk_misc_get_padding (misc, &xpad, &ypad); + + if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR) + xalign = 1.0 - xalign; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_get_requisition (widget, &req); + x = floor (allocation.x + xpad + + ((allocation.width - req.width - shadow_offset) * xalign) + + 0.5); + + y = floor (allocation.y + ypad + + ((allocation.height - req.height - shadow_offset) * yalign) + + 0.5); + + + if (xp) + *xp = x; + + if (yp) + *yp = y; +} + +static gboolean +eel_gtk_label_expose_event (GtkLabel *label, GdkEventExpose *event, gpointer user_data) +{ + int x, y; + GdkColor color; + GtkWidget *widget; + GdkGC *gc; + guint32 shadow_color; + int shadow_offset; + + shadow_color = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), + "eel-label-shadow-color")); + shadow_offset = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), + "eel-label-shadow-offset")); + + color = eel_gdk_rgb_to_color (shadow_color); + + get_layout_location (label, &x, &y); + + widget = GTK_WIDGET (label); + if (shadow_offset > 0) + { + gc = gdk_gc_new (gtk_widget_get_window (widget)); + gdk_gc_set_rgb_fg_color (gc, &color); + gdk_gc_set_clip_rectangle (gc, &event->area); + + gdk_draw_layout (gtk_widget_get_window (widget), + gc, + x + shadow_offset, y + shadow_offset, + gtk_label_get_layout (label)); + g_object_unref (gc); + } + + gtk_paint_layout (gtk_widget_get_style (widget), + gtk_widget_get_window (widget), + gtk_widget_get_state (widget), + FALSE, + &event->area, + widget, + "label", + x, y, + gtk_label_get_layout (label)); + + return TRUE; +} + +static void +eel_gtk_label_size_request (GtkLabel *label, GtkRequisition *requisition, gpointer user_data) +{ + gint shadow_offset; + + shadow_offset = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), + "eel-label-shadow-offset")); + + requisition->width += shadow_offset; + requisition->height += shadow_offset; +} + +static void +set_up_label (GtkLabel *label) +{ + + if (g_object_get_data (G_OBJECT (label), "eel-label-set-up") != NULL) + { + return; + } + + g_signal_connect (label, "expose_event", + G_CALLBACK (eel_gtk_label_expose_event), NULL); + g_signal_connect_after (label, "size_request", + G_CALLBACK (eel_gtk_label_size_request), NULL); + + g_object_set_data (G_OBJECT (label), "eel-label-set-up", "eel-label-set-up"); +} + +void +eel_gtk_label_set_drop_shadow_color (GtkLabel *label, + guint32 color) +{ + set_up_label (label); + + g_object_set_data (G_OBJECT (label), "eel-label-shadow-color", + GINT_TO_POINTER (color)); + + gtk_widget_queue_draw (GTK_WIDGET (label)); +} + +void +eel_gtk_label_set_drop_shadow_offset (GtkLabel *label, + gint offset) +{ + set_up_label (label); + + g_object_set_data (G_OBJECT (label), "eel-label-shadow-offset", + GINT_TO_POINTER (offset)); + + gtk_widget_queue_draw (GTK_WIDGET (label)); +} + +void +eel_gtk_widget_set_background_color (GtkWidget *widget, + const char *color_spec) +{ + GdkColor color; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + eel_gdk_color_parse_with_white_default (color_spec, &color); + + gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, &color); + gtk_widget_modify_base (widget, GTK_STATE_NORMAL, &color); + gtk_widget_modify_bg (widget, GTK_STATE_ACTIVE, &color); + gtk_widget_modify_base (widget, GTK_STATE_ACTIVE, &color); +} + +void +eel_gtk_widget_set_foreground_color (GtkWidget *widget, + const char *color_spec) +{ + GdkColor color; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + eel_gdk_color_parse_with_white_default (color_spec, &color); + + gtk_widget_modify_fg (widget, GTK_STATE_NORMAL, &color); + gtk_widget_modify_text (widget, GTK_STATE_NORMAL, &color); + gtk_widget_modify_fg (widget, GTK_STATE_ACTIVE, &color); + gtk_widget_modify_text (widget, GTK_STATE_ACTIVE, &color); +} + +GtkWidget * +eel_gtk_widget_find_windowed_ancestor (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + while (widget && !gtk_widget_get_has_window (widget)) + { + widget = gtk_widget_get_parent (widget); + } + + return widget; +} + +/* eel_gtk_get_system_font: + * + * Return the system font as selected in the control center. Need to + * g_object_unref() the result when done with it. + * + * Perhaps there is a better way to figure out what that font is, but + * the following is simple enough and it works. + */ +PangoFontDescription * +eel_gtk_get_system_font (void) +{ + GtkWidget *label; + PangoFontDescription *font; + + label = gtk_label_new (""); + + gtk_widget_ensure_style (label); + + font = pango_font_description_copy (gtk_widget_get_style (label)->font_desc); + + g_object_ref_sink (label); + g_object_unref (label); + + return font; +} + +void +eel_gtk_widget_get_button_event_location (GtkWidget *widget, + const GdkEventButton *event, + int *x, + int *y) +{ + int window_x, window_y; + GtkAllocation allocation; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (event != NULL); + + gdk_window_get_position (event->window, &window_x, &window_y); + gtk_widget_get_allocation (widget, &allocation); + if (x != NULL) + { + *x = event->x + window_x - allocation.x; + } + if (y != NULL) + { + *y = event->y + window_y - allocation.y; + } +} + +void +eel_gtk_widget_get_motion_event_location (GtkWidget *widget, + const GdkEventMotion *event, + int *x, + int *y) +{ + eel_gtk_widget_get_button_event_location (widget, (const GdkEventButton *) event, x, y); +} + +static gboolean +tree_view_button_press_callback (GtkWidget *tree_view, + GdkEventButton *event, + gpointer data) +{ + GtkTreePath *path; + GtkTreeViewColumn *column; + + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) + { + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree_view), + event->x, event->y, + &path, + &column, + NULL, + NULL)) + { + gtk_tree_view_row_activated + (GTK_TREE_VIEW (tree_view), path, column); + } + } + + return FALSE; +} + +void +eel_gtk_tree_view_set_activate_on_single_click (GtkTreeView *tree_view, + gboolean should_activate) +{ + guint button_press_id; + + button_press_id = GPOINTER_TO_UINT + (g_object_get_data (G_OBJECT (tree_view), + "eel-tree-view-activate")); + + if (button_press_id && !should_activate) + { + g_signal_handler_disconnect (tree_view, button_press_id); + g_object_set_data (G_OBJECT (tree_view), + "eel-tree-view-activate", + NULL); + } + else if (!button_press_id && should_activate) + { + button_press_id = g_signal_connect + (tree_view, + "button_press_event", + G_CALLBACK (tree_view_button_press_callback), + NULL); + g_object_set_data (G_OBJECT (tree_view), + "eel-tree-view-activate", + GUINT_TO_POINTER (button_press_id)); + } +} + +gboolean +eel_gtk_viewport_get_visible_rect (GtkViewport *viewport, + GdkRectangle *rect) +{ + GdkRectangle viewport_rect; + GdkRectangle child_rect; + gboolean return_val; + + g_return_val_if_fail (GTK_IS_VIEWPORT (viewport), FALSE); + g_return_val_if_fail (rect != NULL, FALSE); + + if (gtk_widget_get_realized (GTK_WIDGET (viewport))) + { + viewport_rect.x = 0; + viewport_rect.y = 0; + +#if GTK_CHECK_VERSION(3, 0, 0) + viewport_rect.width = gdk_window_get_width(GDK_WINDOW(gtk_viewport_get_view_window(viewport))); + viewport_rect.height = gdk_window_get_height(GDK_WINDOW(gtk_viewport_get_view_window(viewport))); +#else + gdk_drawable_get_size(gtk_viewport_get_view_window(viewport), &viewport_rect.width, &viewport_rect.height); +#endif + + gdk_window_get_position (gtk_viewport_get_bin_window (viewport), + &child_rect.x, + &child_rect.y); + +#if GTK_CHECK_VERSION(3, 0, 0) + child_rect.width = gdk_window_get_width(GDK_WINDOW(gtk_viewport_get_view_window(viewport))); + child_rect.height = gdk_window_get_height(GDK_WINDOW(gtk_viewport_get_view_window(viewport))); +#else + gdk_drawable_get_size(gtk_viewport_get_bin_window(viewport), &child_rect.width, &child_rect.height); +#endif + + return_val = gdk_rectangle_intersect (&viewport_rect, + &child_rect, + rect); + rect->x -= child_rect.x; + rect->y -= child_rect.y; + + return return_val; + } + + rect->x = rect->y = rect->width = rect->height = 0; + return FALSE; +} + +void +eel_gtk_viewport_scroll_to_rect (GtkViewport *viewport, + GdkRectangle *rect) +{ + GdkRectangle visible_rect; + int scroll_x; + int scroll_y; + GtkAdjustment *adjustment; + + g_return_if_fail (GTK_IS_VIEWPORT (viewport)); + g_return_if_fail (rect != NULL); + + if (eel_gtk_viewport_get_visible_rect (viewport, &visible_rect)) + { + scroll_x = -1; + scroll_y = -1; + + if (rect->x + rect->width > visible_rect.x + visible_rect.width) + { + scroll_x = rect->x - (visible_rect.width - rect->width); + } + if (rect->y + rect->height > visible_rect.y + visible_rect.height) + { + scroll_y = rect->y - (visible_rect.height - rect->height); + } + + if (rect->x < visible_rect.x) + { + scroll_x = rect->x; + } + + if (rect->y < visible_rect.y) + { + scroll_y = rect->y; + } + + adjustment = gtk_viewport_get_hadjustment (viewport); + if (adjustment && scroll_x != -1) + { + eel_gtk_adjustment_set_value (adjustment, + (double)scroll_x); + } + + adjustment = gtk_viewport_get_vadjustment (viewport); + if (adjustment && scroll_y != -1) + { + eel_gtk_adjustment_set_value (adjustment, + (double)scroll_y); + } + } +} |