midori/katze/katze-utils.c
Enrico Tröger c182e4f788 Disconnect boolean and string property proxy callbacks on destroy
This fixes critical warnings seen when opening and closing
multiple windows, which led notification callbacks to be invoked
on invalid widgets.
2009-06-01 03:43:36 +02:00

781 lines
24 KiB
C

/*
Copyright (C) 2007-2008 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#include "katze-utils.h"
#include <glib/gi18n.h>
#include <string.h>
static void
proxy_toggle_button_toggled_cb (GtkToggleButton* button,
GObject* object)
{
gboolean toggled = gtk_toggle_button_get_active (button);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
g_object_set (object, property, toggled, NULL);
}
static void
proxy_file_file_set_cb (GtkFileChooser* button,
GObject* object)
{
const gchar* file = gtk_file_chooser_get_filename (button);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
g_object_set (object, property, file, NULL);
}
static void
proxy_folder_file_set_cb (GtkFileChooser* button,
GObject* object)
{
const gchar* file = gtk_file_chooser_get_current_folder (button);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
g_object_set (object, property, file, NULL);
}
static void
proxy_uri_file_set_cb (GtkFileChooser* button,
GObject* object)
{
const gchar* file = gtk_file_chooser_get_uri (button);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
g_object_set (object, property, file, NULL);
}
static void
proxy_combo_box_text_changed_cb (GtkComboBox* button,
GObject* object)
{
gchar* text = gtk_combo_box_get_active_text (button);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
g_object_set (object, property, text, NULL);
}
static void
proxy_entry_activate_cb (GtkEntry* entry,
GObject* object)
{
const gchar* text = gtk_entry_get_text (entry);
const gchar* property = g_object_get_data (G_OBJECT (entry), "property");
g_object_set (object, property, text, NULL);
}
static gboolean
proxy_entry_focus_out_event_cb (GtkEntry* entry,
GdkEventFocus* event,
GObject* object)
{
const gchar* text = gtk_entry_get_text (entry);
const gchar* property = g_object_get_data (G_OBJECT (entry), "property");
g_object_set (object, property, text, NULL);
return FALSE;
}
static void
proxy_spin_button_changed_cb (GtkSpinButton* button,
GObject* object)
{
GObjectClass* class = G_OBJECT_GET_CLASS (object);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
GParamSpec* pspec = g_object_class_find_property (class, property);
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT)
{
gint value = gtk_spin_button_get_value_as_int (button);
g_object_set (object, property, value, NULL);
}
else
{
gdouble value = gtk_spin_button_get_value (button);
g_object_set (object, property, value, NULL);
}
}
static void
proxy_combo_box_changed_cb (GtkComboBox* button,
GObject* object)
{
gint value = gtk_combo_box_get_active (button);
const gchar* property = g_object_get_data (G_OBJECT (button), "property");
g_object_set (object, property, value, NULL);
}
static void
proxy_object_notify_boolean_cb (GObject* object,
GParamSpec* pspec,
GtkWidget* proxy)
{
const gchar* property = g_object_get_data (G_OBJECT (proxy), "property");
gboolean value = katze_object_get_boolean (object, property);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (proxy), value);
}
static void
proxy_object_notify_string_cb (GObject* object,
GParamSpec* pspec,
GtkWidget* proxy)
{
const gchar* property = g_object_get_data (G_OBJECT (proxy), "property");
gchar* value = katze_object_get_string (object, property);
gtk_entry_set_text (GTK_ENTRY (proxy), value);
g_free (value);
}
static void
proxy_widget_boolean_destroy_cb (GtkWidget* proxy,
GObject* object)
{
g_signal_handlers_disconnect_by_func (object,
proxy_object_notify_boolean_cb, proxy);
}
static void
proxy_widget_string_destroy_cb (GtkWidget* proxy,
GObject* object)
{
g_signal_handlers_disconnect_by_func (object,
proxy_object_notify_string_cb, proxy);
}
/**
* katze_property_proxy:
* @object: a #GObject
* @property: the name of a property
* @hint: a special hint
*
* Create a widget of an appropriate type to represent the specified
* object's property. If the property is writable changes of the value
* through the widget will be reflected in the value of the property.
*
* Supported values for @hint are as follows:
* "blurb": the blurb of the property will be used to provide a kind
* of label, instead of the name.
* "file": the widget created will be particularly suitable for
* choosing an existing filename.
* "folder": the widget created will be particularly suitable for
* choosing an existing folder.
* "uri": the widget created will be particularly suitable for
* choosing an existing filename, encoded as an URI.
* "font": the widget created will be particularly suitable for
* choosing a font from installed fonts.
* Since 0.1.6 the following hints are also supported:
* "toggle": the widget created will be an empty toggle button. This
* is only supported with boolean properties.
* Since 0.1.8 "toggle" creates GtkCheckButton widgets without checkmarks.
*
* Any other values for @hint are silently ignored.
*
* Since 0.1.2 strings without hints and booleans are truly synchronous
* including property changes causing the proxy to be updated.
*
* Return value: a new widget
**/
GtkWidget*
katze_property_proxy (gpointer object,
const gchar* property,
const gchar* hint)
{
GObjectClass* class;
GParamSpec* pspec;
GType type;
const gchar* nick;
const gchar* _hint;
GtkWidget* widget;
gchar* string;
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
class = G_OBJECT_GET_CLASS (object);
pspec = g_object_class_find_property (class, property);
if (!pspec)
{
g_warning (_("Property '%s' is invalid for %s"),
property, G_OBJECT_CLASS_NAME (class));
return gtk_label_new (property);
}
type = G_PARAM_SPEC_TYPE (pspec);
nick = g_param_spec_get_nick (pspec);
_hint = g_intern_string (hint);
if (_hint == g_intern_string ("blurb"))
nick = g_param_spec_get_blurb (pspec);
string = NULL;
if (type == G_TYPE_PARAM_BOOLEAN)
{
gchar* notify_property;
gboolean toggled = katze_object_get_boolean (object, property);
widget = gtk_check_button_new ();
if (_hint == g_intern_string ("toggle"))
gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (widget), FALSE);
else
gtk_button_set_label (GTK_BUTTON (widget), gettext (nick));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), toggled);
g_signal_connect (widget, "toggled",
G_CALLBACK (proxy_toggle_button_toggled_cb), object);
notify_property = g_strdup_printf ("notify::%s", property);
g_signal_connect (object, notify_property,
G_CALLBACK (proxy_object_notify_boolean_cb), widget);
g_signal_connect (widget, "destroy",
G_CALLBACK (proxy_widget_boolean_destroy_cb), object);
g_free (notify_property);
}
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("file"))
{
string = katze_object_get_string (object, property);
widget = gtk_file_chooser_button_new (_("Choose file"),
GTK_FILE_CHOOSER_ACTION_OPEN);
if (!string)
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget),
string ? string : "");
if (pspec->flags & G_PARAM_WRITABLE)
g_signal_connect (widget, "selection-changed",
G_CALLBACK (proxy_file_file_set_cb), object);
}
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("folder"))
{
string = katze_object_get_string (object, property);
widget = gtk_file_chooser_button_new (_("Choose folder"),
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
if (!string)
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
string ? string : "");
if (pspec->flags & G_PARAM_WRITABLE)
g_signal_connect (widget, "selection-changed",
G_CALLBACK (proxy_folder_file_set_cb), object);
}
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("uri"))
{
string = katze_object_get_string (object, property);
widget = gtk_file_chooser_button_new (_("Choose file"),
GTK_FILE_CHOOSER_ACTION_OPEN);
if (!string)
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (widget),
string ? string : "");
#if GTK_CHECK_VERSION (2, 12, 0)
g_signal_connect (widget, "file-set",
G_CALLBACK (proxy_uri_file_set_cb), object);
#else
if (pspec->flags & G_PARAM_WRITABLE)
g_signal_connect (widget, "selection-changed",
G_CALLBACK (proxy_uri_file_set_cb), object);
#endif
}
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("font"))
{
int n_families, i;
PangoContext* context;
PangoFontFamily** families;
string = katze_object_get_string (object, property);
widget = gtk_combo_box_new_text ();
context = gtk_widget_get_pango_context (widget);
pango_context_list_families (context, &families, &n_families);
if (!string)
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
for (i = 0; i < n_families; i++)
{
const gchar* font = pango_font_family_get_name (families[i]);
gtk_combo_box_append_text (GTK_COMBO_BOX (widget), font);
if (string && !strcmp (font, string))
gtk_combo_box_set_active (GTK_COMBO_BOX (widget), i);
}
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (
gtk_combo_box_get_model (GTK_COMBO_BOX (widget))),
0, GTK_SORT_ASCENDING);
g_signal_connect (widget, "changed",
G_CALLBACK (proxy_combo_box_text_changed_cb), object);
g_free (families);
}
else if (type == G_TYPE_PARAM_STRING)
{
gchar* notify_property;
widget = gtk_entry_new ();
g_object_get (object, property, &string, NULL);
if (!string)
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
gtk_entry_set_text (GTK_ENTRY (widget), string ? string : "");
g_signal_connect (widget, "activate",
G_CALLBACK (proxy_entry_activate_cb), object);
g_signal_connect (widget, "focus-out-event",
G_CALLBACK (proxy_entry_focus_out_event_cb), object);
notify_property = g_strdup_printf ("notify::%s", property);
g_signal_connect (object, notify_property,
G_CALLBACK (proxy_object_notify_string_cb), widget);
g_signal_connect (widget, "destroy",
G_CALLBACK (proxy_widget_string_destroy_cb), object);
g_free (notify_property);
}
else if (type == G_TYPE_PARAM_FLOAT)
{
gfloat value = katze_object_get_float (object, property);
widget = gtk_spin_button_new_with_range (
G_PARAM_SPEC_FLOAT (pspec)->minimum,
G_PARAM_SPEC_FLOAT (pspec)->maximum, 1);
/* Keep it narrow, 5 + 2 digits are usually fine */
gtk_entry_set_width_chars (GTK_ENTRY (widget), 5 + 2);
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 2);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
g_signal_connect (widget, "value-changed",
G_CALLBACK (proxy_spin_button_changed_cb), object);
}
else if (type == G_TYPE_PARAM_INT)
{
gint value = katze_object_get_int (object, property);
widget = gtk_spin_button_new_with_range (
G_PARAM_SPEC_INT (pspec)->minimum,
G_PARAM_SPEC_INT (pspec)->maximum, 1);
/* Keep it narrow, 5 digits are usually fine */
gtk_entry_set_width_chars (GTK_ENTRY (widget), 5);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
g_signal_connect (widget, "value-changed",
G_CALLBACK (proxy_spin_button_changed_cb), object);
}
else if (type == G_TYPE_PARAM_ENUM)
{
guint i;
GEnumClass* enum_class = G_ENUM_CLASS (
g_type_class_ref (pspec->value_type));
gint value = katze_object_get_enum (object, property);
widget = gtk_combo_box_new_text ();
for (i = 0; i < enum_class->n_values; i++)
{
const gchar* label = gettext (enum_class->values[i].value_nick);
gtk_combo_box_append_text (GTK_COMBO_BOX (widget), label);
}
gtk_combo_box_set_active (GTK_COMBO_BOX (widget), value);
g_signal_connect (widget, "changed",
G_CALLBACK (proxy_combo_box_changed_cb), object);
g_type_class_unref (enum_class);
}
else
widget = gtk_label_new (gettext (nick));
g_free (string);
gtk_widget_set_tooltip_text (widget, g_param_spec_get_blurb (pspec));
gtk_widget_set_sensitive (widget, pspec->flags & G_PARAM_WRITABLE);
g_object_set_data_full (G_OBJECT (widget), "property",
g_strdup (property), g_free);
return widget;
}
/**
* katze_property_label:
* @object: a #GObject
* @property: the name of a property
*
* Create a label widget displaying the name of the specified object's property.
*
* Return value: a new label widget
**/
GtkWidget*
katze_property_label (gpointer object,
const gchar* property)
{
GObjectClass* class;
GParamSpec* pspec;
const gchar* nick;
GtkWidget* widget;
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
class = G_OBJECT_GET_CLASS (object);
pspec = g_object_class_find_property (class, property);
if (!pspec)
{
g_warning (_("Property '%s' is invalid for %s"),
property, G_OBJECT_CLASS_NAME (class));
return gtk_label_new (property);
}
nick = g_param_spec_get_nick (pspec);
widget = gtk_label_new (nick);
return widget;
}
typedef struct
{
GtkWidget* widget;
KatzeMenuPos position;
} KatzePopupInfo;
static void
katze_widget_popup_position_menu (GtkMenu* menu,
gint* x,
gint* y,
gboolean* push_in,
gpointer user_data)
{
gint wx, wy;
gint menu_width;
GtkRequisition menu_req;
GtkRequisition widget_req;
KatzePopupInfo* info = user_data;
GtkWidget* widget = info->widget;
gint widget_height;
/* Retrieve size and position of both widget and menu */
if (GTK_WIDGET_NO_WINDOW (widget))
{
gdk_window_get_position (widget->window, &wx, &wy);
wx += widget->allocation.x;
wy += widget->allocation.y;
}
else
gdk_window_get_origin (widget->window, &wx, &wy);
gtk_widget_size_request (GTK_WIDGET (menu), &menu_req);
gtk_widget_size_request (widget, &widget_req);
menu_width = menu_req.width;
widget_height = widget_req.height; /* Better than allocation.height */
/* Calculate menu position */
if (info->position == KATZE_MENU_POSITION_CURSOR)
; /* Do nothing? */
else if (info->position == KATZE_MENU_POSITION_RIGHT)
{
*x = wx + widget->allocation.width - menu_width;
*y = wy + widget_height;
} else if (info->position == KATZE_MENU_POSITION_LEFT)
{
*x = wx;
*y = wy + widget_height;
}
*push_in = TRUE;
}
/**
* katze_widget_popup:
* @widget: a widget
* @menu: the menu to popup
* @event: a button event, or %NULL
* @pos: the preferred positioning
*
* Pops up the given menu relative to @widget. Use this
* instead of writing custom positioning functions.
*
* Return value: a new label widget
**/
void
katze_widget_popup (GtkWidget* widget,
GtkMenu* menu,
GdkEventButton* event,
KatzeMenuPos pos)
{
int button, event_time;
if (event)
{
button = event->button;
event_time = event->time;
}
else
{
button = 0;
event_time = gtk_get_current_event_time ();
}
if (!gtk_menu_get_attach_widget (menu))
gtk_menu_attach_to_widget (menu, widget, NULL);
if (widget)
{
KatzePopupInfo info = { widget, pos };
gtk_menu_popup (menu, NULL, NULL,
katze_widget_popup_position_menu, &info,
button, event_time);
}
else
gtk_menu_popup (menu, NULL, NULL, NULL, NULL, button, event_time);
}
/**
* katze_image_menu_item_new_ellipsized:
* @label: a label or %NULL
*
* Creates an image menu item where the label is
* reasonably ellipsized for you.
*
* Return value: a new label widget
**/
GtkWidget*
katze_image_menu_item_new_ellipsized (const gchar* label)
{
GtkWidget* menuitem;
GtkWidget* label_widget;
menuitem = gtk_image_menu_item_new ();
label_widget = gtk_label_new (label);
/* FIXME: Should text direction be respected here? */
gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.0);
gtk_label_set_max_width_chars (GTK_LABEL (label_widget), 50);
gtk_label_set_ellipsize (GTK_LABEL (label_widget), PANGO_ELLIPSIZE_MIDDLE);
gtk_widget_show (label_widget);
gtk_container_add (GTK_CONTAINER (menuitem), label_widget);
return menuitem;
}
/**
* katze_pixbuf_new_from_buffer:
* @buffer: Buffer with image data
* @length: Length of the buffer
* @mime_type: a MIME type, or %NULL
* @error: return location for a #GError, or %NULL
*
* Creates a new #GdkPixbuf out of the specified buffer.
*
* You can specify a MIME type if looking at the buffer
* is not enough to determine the right type.
*
* Return value: A newly-allocated #GdkPixbuf
**/
GdkPixbuf*
katze_pixbuf_new_from_buffer (const guchar* buffer,
gsize length,
const gchar* mime_type,
GError** error)
{
/* Proposed for inclusion in GdkPixbuf
See http://bugzilla.gnome.org/show_bug.cgi?id=74291 */
GdkPixbufLoader* loader;
GdkPixbuf* pixbuf;
g_return_val_if_fail (buffer != NULL, NULL);
g_return_val_if_fail (length > 0, NULL);
if (mime_type)
{
loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, error);
if (!loader)
return NULL;
}
else
loader = gdk_pixbuf_loader_new ();
if (!gdk_pixbuf_loader_write (loader, buffer, length, error))
{
g_object_unref (loader);
return NULL;
}
if (!gdk_pixbuf_loader_close (loader, error))
{
g_object_unref (loader);
return NULL;
}
pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
g_object_ref (pixbuf);
g_object_unref (loader);
return pixbuf;
}
/**
* katze_tree_view_get_selected_iter:
* @treeview: a #GtkTreeView
* @model: a pointer to store the model, or %NULL
* @iter: a pointer to store the iter, or %NULL
*
* Determines whether there is a selection in @treeview
* and sets the @iter to the current selection.
*
* If there is a selection and @model is not %NULL, it is
* set to the model, mainly for convenience.
*
* Either @model or @iter or both can be %NULL in which case
* no value will be assigned in any case.
*
* Return value: %TRUE if there is a selection
*
* Since: 0.1.3
**/
gboolean
katze_tree_view_get_selected_iter (GtkTreeView* treeview,
GtkTreeModel** model,
GtkTreeIter* iter)
{
GtkTreeSelection* selection;
g_return_val_if_fail (GTK_IS_TREE_VIEW (treeview), FALSE);
if ((selection = gtk_tree_view_get_selection (treeview)))
if (gtk_tree_selection_get_selected (selection, model, iter))
return TRUE;
return FALSE;
}
/**
* katze_object_has_property:
* @object: a #GObject
* @property: the name of the property
*
* Determine if @object has a property with the specified name.
*
* Return value: a boolean
*
* Since: 0.1.2
**/
gboolean
katze_object_has_property (gpointer object,
const gchar* property)
{
GObjectClass* class;
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
class = G_OBJECT_GET_CLASS (object);
return g_object_class_find_property (class, property) != NULL;
}
/**
* katze_object_get_boolean:
* @object: a #GObject
* @property: the name of the property to get
*
* Retrieve the boolean value of the specified property.
*
* Return value: a boolean
**/
gboolean
katze_object_get_boolean (gpointer object,
const gchar* property)
{
gboolean value = FALSE;
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
/**
* katze_object_get_int:
* @object: a #GObject
* @property: the name of the property to get
*
* Retrieve the integer value of the specified property.
*
* Return value: an integer
**/
gint
katze_object_get_int (gpointer object,
const gchar* property)
{
gint value = -1;
g_return_val_if_fail (G_IS_OBJECT (object), -1);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
/**
* katze_object_get_float:
* @object: a #GObject
* @property: the name of the property to get
*
* Retrieve the float value of the specified property.
*
* Return value: a float
**/
gfloat
katze_object_get_float (gpointer object,
const gchar* property)
{
gfloat value = -1.0f;
g_return_val_if_fail (G_IS_OBJECT (object), -1.0f);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
/**
* katze_object_get_enum:
* @object: a #GObject
* @property: the name of the property to get
*
* Retrieve the enum value of the specified property.
*
* Return value: an enumeration
**/
gint
katze_object_get_enum (gpointer object,
const gchar* property)
{
gint value = -1;
g_return_val_if_fail (G_IS_OBJECT (object), -1);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
/**
* katze_object_get_string:
* @object: a #GObject
* @property: the name of the property to get
*
* Retrieve the string value of the specified property.
*
* Return value: a newly allocated string
**/
gchar*
katze_object_get_string (gpointer object,
const gchar* property)
{
gchar* value = NULL;
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
/**
* katze_object_get_object:
* @object: a #GObject
* @property: the name of the property to get
*
* Retrieve the object value of the specified property.
*
* Return value: an object
**/
gpointer
katze_object_get_object (gpointer object,
const gchar* property)
{
GObject* value = NULL;
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}