519 lines
18 KiB
C
519 lines
18 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 gchar*
|
|
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);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
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);
|
|
return FALSE;
|
|
}
|
|
|
|
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 gboolean
|
|
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);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gchar*
|
|
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);
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Any other values for @hint are silently ignored.
|
|
*
|
|
* Return value: a new widget
|
|
**/
|
|
GtkWidget*
|
|
katze_property_proxy (gpointer object,
|
|
const gchar* property,
|
|
const gchar* hint)
|
|
{
|
|
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
|
|
GObjectClass* class = G_OBJECT_GET_CLASS (object);
|
|
GParamSpec* 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);
|
|
}
|
|
GType type = G_PARAM_SPEC_TYPE (pspec);
|
|
const gchar* nick = g_param_spec_get_nick (pspec);
|
|
const gchar* _hint = g_intern_string (hint);
|
|
if (_hint == g_intern_string ("blurb"))
|
|
nick = g_param_spec_get_blurb (pspec);
|
|
GtkWidget* widget;
|
|
gchar* string = NULL;
|
|
if (type == G_TYPE_PARAM_BOOLEAN)
|
|
{
|
|
widget = gtk_check_button_new_with_label (gettext (nick));
|
|
gboolean toggled;
|
|
g_object_get (object, property, &toggled, NULL);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), toggled);
|
|
g_signal_connect (widget, "toggled",
|
|
G_CALLBACK (proxy_toggle_button_toggled_cb), object);
|
|
}
|
|
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("file"))
|
|
{
|
|
widget = gtk_file_chooser_button_new (_("Choose file"),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN);
|
|
g_object_get (object, property, &string, NULL);
|
|
if (!string)
|
|
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
|
|
gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget),
|
|
string ? string : "");
|
|
#if GTK_CHECK_VERSION (2, 12, 0)
|
|
g_signal_connect (widget, "file-set",
|
|
G_CALLBACK (proxy_file_file_set_cb), object);
|
|
#else
|
|
if (pspec->flags & G_PARAM_WRITABLE)
|
|
g_signal_connect (widget, "selection-changed",
|
|
G_CALLBACK (proxy_file_file_set_cb), object);
|
|
#endif
|
|
}
|
|
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("folder"))
|
|
{
|
|
widget = gtk_file_chooser_button_new (_("Choose folder"),
|
|
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
|
|
g_object_get (object, property, &string, NULL);
|
|
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 GTK_CHECK_VERSION (2, 12, 0)
|
|
g_signal_connect (widget, "file-set",
|
|
G_CALLBACK (proxy_folder_file_set_cb), object);
|
|
#else
|
|
if (pspec->flags & G_PARAM_WRITABLE)
|
|
g_signal_connect (widget, "selection-changed",
|
|
G_CALLBACK (proxy_folder_file_set_cb), object);
|
|
#endif
|
|
}
|
|
else if (type == G_TYPE_PARAM_STRING && _hint == g_intern_string ("uri"))
|
|
{
|
|
widget = gtk_file_chooser_button_new (_("Choose file"),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN);
|
|
g_object_get (object, property, &string, NULL);
|
|
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"))
|
|
{
|
|
widget = gtk_combo_box_new_text ();
|
|
PangoContext* context = gtk_widget_get_pango_context (widget);
|
|
PangoFontFamily** families;
|
|
int n_families;
|
|
pango_context_list_families (context, &families, &n_families);
|
|
g_object_get (object, property, &string, NULL);
|
|
if (!string)
|
|
string = g_strdup (G_PARAM_SPEC_STRING (pspec)->default_value);
|
|
gint i = 0;
|
|
while (i < n_families)
|
|
{
|
|
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);
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
else if (type == G_TYPE_PARAM_FLOAT)
|
|
{
|
|
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);
|
|
gfloat value;
|
|
g_object_get (object, property, &value, NULL);
|
|
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)
|
|
{
|
|
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);
|
|
gint value;
|
|
g_object_get (object, property, &value, NULL);
|
|
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)
|
|
{
|
|
GEnumClass* enum_class = G_ENUM_CLASS (
|
|
g_type_class_ref (pspec->value_type));
|
|
widget = gtk_combo_box_new_text ();
|
|
gint i = 0;
|
|
while (i < enum_class->n_values)
|
|
{
|
|
const gchar* label = gettext (enum_class->values[i].value_nick);
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (widget), label);
|
|
i++;
|
|
}
|
|
gint value;
|
|
g_object_get (object, property, &value, NULL);
|
|
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_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)
|
|
{
|
|
g_return_val_if_fail (G_IS_OBJECT (object), NULL);
|
|
GObjectClass* class = G_OBJECT_GET_CLASS (object);
|
|
GParamSpec* 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);
|
|
}
|
|
const gchar* nick = g_param_spec_get_nick (pspec);
|
|
GtkWidget* 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;
|
|
}
|