/* * panel-menu.c: A GtkMenu with an ugly stripe down the side * * Copyright (C) 2003 Sun Microsystems, Inc. * * 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Authors: * Mark McLoughlin * Erwann Chenede * * The hack in size_allocate() is based on the menu stripe patch * with XD2 which is Copyright (C) 2002 Ximian, Inc. and written * by Thomas and Michael Meeks. */ #include #include "panel-menu.h" #include "panel-globals.h" #include #define PANEL_MENU_DEFAULT_IMAGE "gnome-panel-menu-stripe" #define PANEL_MENU_DEFAULT_GRADIENT_TOP { 0, 0xffff, 0xffff, 0xffff } #define PANEL_MENU_DEFAULT_GRADIENT_BOTTOM { 0, 0x0000, 0x0000, 0xffff } struct _PanelMenuPrivate { gboolean stripe_enabled; GdkPixbuf *stripe_image; GdkPixbuf *stripe_scaled; }; static GObjectClass *parent_class; enum { PROP_0, PROP_STRIPE_ENABLED }; static void panel_menu_unset_stripe_image (PanelMenu *menu) { if (menu->priv->stripe_image) g_object_unref (menu->priv->stripe_image); menu->priv->stripe_image = NULL; if (menu->priv->stripe_scaled) g_object_unref (menu->priv->stripe_scaled); menu->priv->stripe_scaled = NULL; } static gboolean panel_menu_ensure_stripe_image (PanelMenu *menu) { GError *error = NULL; char *name = NULL; char *full_path; g_return_val_if_fail (menu->priv->stripe_enabled != FALSE, FALSE); if (menu->priv->stripe_image) return TRUE; gtk_widget_ensure_style (GTK_WIDGET (menu)); gtk_widget_style_get (GTK_WIDGET (menu), "stripe-image", &name, NULL); if (!name) goto out_no_name; full_path = gnome_icon_theme_lookup_icon (panel_icon_theme, name, 48, NULL, NULL); if (!full_path) { g_warning ("Unable to lookup stripe image '%s' against the current icon theme\n", name); goto out_no_path; } menu->priv->stripe_image = gdk_pixbuf_new_from_file (full_path, &error); if (error) { g_warning ("Unable to load image '%s': %s", full_path, error->message); g_error_free (error); } g_free (full_path); out_no_path: g_free (name); out_no_name: return menu->priv->stripe_image != NULL; } static void panel_menu_prepare_stripe_image (PanelMenu *menu, int height) { g_return_if_fail (menu->priv->stripe_image != NULL); if (menu->priv->stripe_scaled && height != gdk_pixbuf_get_height (menu->priv->stripe_scaled)) { g_object_unref (menu->priv->stripe_scaled); menu->priv->stripe_scaled = NULL; } if (gdk_pixbuf_get_height (menu->priv->stripe_image) > height) menu->priv->stripe_scaled = gdk_pixbuf_scale_simple (menu->priv->stripe_image, gdk_pixbuf_get_width (menu->priv->stripe_image), height, GDK_INTERP_HYPER); } static void panel_menu_size_request (GtkWidget *widget, GtkRequisition *requisition) { PanelMenu *menu = PANEL_MENU (widget); if (GTK_WIDGET_CLASS (parent_class)->size_request) GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition); if (menu->priv->stripe_enabled && panel_menu_ensure_stripe_image (menu)) requisition->width += gdk_pixbuf_get_width (menu->priv->stripe_image) + widget->style->xthickness; } static void panel_menu_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { PanelMenu *menu = PANEL_MENU (widget); GtkMenuShell *shell = GTK_MENU_SHELL (widget); GList *l; int stripe_width; int stripe_height = 0; if (GTK_WIDGET_CLASS (parent_class)->size_allocate) GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); if (!menu->priv->stripe_enabled || !panel_menu_ensure_stripe_image (menu)) return; stripe_width = gdk_pixbuf_get_width (menu->priv->stripe_image) + widget->style->xthickness; for (l = shell->children; l; l = l->next) { GtkWidget *child = l->data; GtkAllocation challoc = child->allocation; if (!GTK_WIDGET_VISIBLE (child)) continue; challoc.x += stripe_width; if (!GTK_IS_SEPARATOR_MENU_ITEM (child)) challoc.width -= stripe_width; gtk_widget_size_allocate (child, &challoc); if (GTK_WIDGET_REALIZED (child)) gdk_window_move_resize (GTK_MENU_ITEM (child)->event_window, 0, challoc.y, allocation->width, challoc.height); stripe_height += challoc.height; } panel_menu_prepare_stripe_image (menu, stripe_height); } static void panel_menu_draw_hgradient (GdkDrawable *drawable, GdkGC *gc, GdkColormap *colormap, int x, int y, int width, int height, GdkColor *top_color, GdkColor *bottom_color, GdkRegion *clip) { GdkGCValues old_values; GdkColor col; int dr, dg, db; int i; col = *top_color; dr = (bottom_color->red - top_color->red) / height; dg = (bottom_color->green - top_color->green) / height; db = (bottom_color->blue - top_color->blue) / height; gdk_gc_get_values (gc, &old_values); gdk_gc_set_clip_region (gc, clip); for (i = 0; i < height; i++) { gdk_rgb_find_color (colormap, &col); gdk_gc_set_foreground (gc, &col); gdk_draw_line (drawable, gc, x, y + i, x + width - 1, y + i); col.red += dr; col.green += dg; col.blue += db; } gdk_gc_set_foreground (gc, &old_values.foreground); gdk_gc_set_clip_region (gc, NULL); } static void panel_menu_draw_stripe (PanelMenu *menu, GdkEventExpose *event) { GtkWidget *widget = GTK_WIDGET (menu); GdkDrawable *drawable; GdkPixbuf *stripe_image; GdkColor default_top_color = PANEL_MENU_DEFAULT_GRADIENT_TOP; GdkColor default_bottom_color = PANEL_MENU_DEFAULT_GRADIENT_BOTTOM; GdkColor *top_color = NULL; GdkColor *bottom_color = NULL; gboolean stripe_at_top = FALSE; GdkRectangle area; GdkRectangle exposed_area; int height; int stripe_width; int stripe_height; if (!menu->priv->stripe_image) return; gtk_widget_style_get (widget, "stripe-at-top", &stripe_at_top, "stripe-gradient-top", &top_color, "stripe-gradient-bottom", &bottom_color, NULL); drawable = GTK_MENU (menu)->bin_window; gdk_window_get_geometry (drawable, NULL, NULL, NULL, &height, NULL); stripe_image = menu->priv->stripe_scaled ? menu->priv->stripe_scaled : menu->priv->stripe_image; stripe_width = gdk_pixbuf_get_width (stripe_image); stripe_height = gdk_pixbuf_get_height (stripe_image); panel_menu_draw_hgradient (drawable, widget->style->fg_gc [GTK_WIDGET_STATE (widget)], widget->style->colormap, 0, 0, stripe_width, height, top_color ? top_color : &default_top_color, bottom_color ? bottom_color : &default_bottom_color, event->region); if (top_color) gdk_color_free (top_color); if (bottom_color) gdk_color_free (bottom_color); area.x = 0; area.y = stripe_at_top ? 0 : height - stripe_height; area.width = stripe_width; area.height = stripe_height; if (!gdk_rectangle_intersect (&event->area, &area, &exposed_area)) return; gdk_draw_pixbuf (drawable, widget->style->fg_gc [GTK_WIDGET_STATE (widget)], stripe_image, exposed_area.x, exposed_area.y - area.y, exposed_area.x, exposed_area.y, exposed_area.width, exposed_area.height, GDK_RGB_DITHER_NORMAL, 0, 0); } static gboolean panel_menu_expose (GtkWidget *widget, GdkEventExpose *event) { PanelMenu *menu = (PanelMenu *) widget; gboolean retval = FALSE; if (!GTK_WIDGET_DRAWABLE (widget)) return retval; if (GTK_WIDGET_CLASS (parent_class)->expose_event) retval = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); panel_menu_draw_stripe (menu, event); return retval; } static void panel_menu_finalize (GObject *object) { PanelMenu *menu = PANEL_MENU (object); panel_menu_unset_stripe_image (menu); g_free (menu->priv); menu->priv = NULL; if (parent_class->finalize) parent_class->finalize (object); } static void panel_menu_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PanelMenu *menu; g_return_if_fail (PANEL_IS_MENU (object)); menu = PANEL_MENU (object); switch (prop_id) { case PROP_STRIPE_ENABLED: panel_menu_set_stripe_enabled (menu, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void panel_menu_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PanelMenu *menu; g_return_if_fail (PANEL_IS_MENU (object)); menu = PANEL_MENU (object); switch (prop_id) { case PROP_STRIPE_ENABLED: g_value_set_boolean (value, menu->priv->stripe_enabled); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void panel_menu_class_init (PanelMenuClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; parent_class = g_type_class_peek_parent (klass); gobject_class->finalize = panel_menu_finalize; gobject_class->set_property = panel_menu_set_property; gobject_class->get_property = panel_menu_get_property; widget_class->size_request = panel_menu_size_request; widget_class->size_allocate = panel_menu_size_allocate; widget_class->expose_event = panel_menu_expose; g_object_class_install_property ( gobject_class, PROP_STRIPE_ENABLED, g_param_spec_boolean ( "stripe-enabled", _("Stripe Enabled"), _("Whether the stripe image should be drawn beside the menu"), FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); gtk_widget_class_install_style_property (widget_class, g_param_spec_string ("stripe-image", _("Stripe Image Filename"), _("Name of the image with which to draw the menu stripe"), PANEL_MENU_DEFAULT_IMAGE, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_string ("stripe-at-top", _("Stripe at Top"), _("If true, place the stripe image at the top of the menu, otherwise place it at the bottom of the menu"), FALSE, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("stripe-gradient-top", _("Stripe Gradient Top Color"), _("The top color of the gradient on the menu stripe"), GDK_TYPE_COLOR, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("stripe-gradient-bottom", _("Stripe Gradient Bottom Color"), _("The bottom color of the gradient on the menu stripe"), GDK_TYPE_COLOR, G_PARAM_READABLE)); } static void panel_menu_instance_init (PanelMenu *menu, PanelMenuClass *klass) { menu->priv = g_new0 (PanelMenuPrivate, 1); menu->priv->stripe_enabled = FALSE; menu->priv->stripe_image = NULL; menu->priv->stripe_scaled = NULL; gtk_widget_add_events (GTK_WIDGET (menu), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK); } GType panel_menu_get_type (void) { static GType type = 0; if (!type) { static const GTypeInfo info = { sizeof (PanelMenuClass), NULL, NULL, (GClassInitFunc) panel_menu_class_init, NULL, NULL, sizeof (PanelMenu), 0, (GInstanceInitFunc) panel_menu_instance_init, NULL }; type = g_type_register_static (GTK_TYPE_MENU, "PanelMenu", &info, 0); } return type; } GtkWidget * panel_menu_new (gboolean stripe_enabled) { return g_object_new (PANEL_TYPE_MENU, "stripe-enabled", stripe_enabled, NULL); } void panel_menu_set_stripe_enabled (PanelMenu *menu, gboolean stripe_enabled) { g_return_if_fail (PANEL_IS_MENU (menu)); stripe_enabled = stripe_enabled != FALSE; if (menu->priv->stripe_enabled == stripe_enabled) return; menu->priv->stripe_enabled = stripe_enabled; gtk_widget_queue_resize (GTK_WIDGET (menu)); g_object_notify (G_OBJECT (menu), "stripe-enabled"); } gboolean panel_menu_get_stripe_enabled (PanelMenu *menu) { g_return_val_if_fail (PANEL_IS_MENU (menu), FALSE); return menu->priv->stripe_enabled; }