/*
* Copyright © 2000-2003 Marco Pesenti Gritti
* Copyright © 2003, 2004 Christian Persch
*
* 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, 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 02110-1301, USA.
*
*/
#include "config.h"
#include "ephy-dialog.h"
#include "ephy-state.h"
#include "ephy-gui.h"
#include "eel-gconf-extensions.h"
#include "ephy-debug.h"
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
/**
* SECTION:ephy-dialog
* @short_description: A customized #GtkDialog for Epiphany
*
* A customized #GtkDialog for Epiphany.
*/
enum
{
PROP_0,
PROP_PARENT_WINDOW,
PROP_MODAL,
PROP_PERSIST_POSITION,
PROP_DEFAULT_WIDTH,
PROP_DEFAULT_HEIGHT
};
typedef enum
{
PT_TOGGLEBUTTON,
PT_RADIOBUTTON,
PT_SPINBUTTON,
PT_COMBOBOX,
PT_EDITABLE,
PT_UNKNOWN
} WidgetType;
typedef struct
{
const char *id;
EphyDialog *dialog;
char *pref;
EphyDialogApplyType apply_type;
GtkWidget *widget;
WidgetType widget_type;
GType data_type;
GList *string_enum;
int data_col;
gboolean loaded;
gboolean sane_state;
} PropertyInfo;
#define EPHY_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_DIALOG, EphyDialogPrivate))
struct _EphyDialogPrivate
{
char *name;
GHashTable *props;
GtkWidget *parent;
GtkWidget *dialog;
guint modal : 1;
guint has_default_size : 1;
guint disposing : 1;
guint initialized : 1;
guint persist_position : 1;
int default_width;
int default_height;
};
#define SPIN_DELAY 0.20
enum
{
CHANGED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0, };
static void ephy_dialog_class_init (EphyDialogClass *klass);
static void ephy_dialog_init (EphyDialog *window);
static GObjectClass *parent_class = NULL;
GType
ephy_dialog_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0))
{
const GTypeInfo our_info =
{
sizeof (EphyDialogClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) ephy_dialog_class_init,
NULL,
NULL, /* class_data */
sizeof (EphyDialog),
0, /* n_preallocs */
(GInstanceInitFunc) ephy_dialog_init
};
type = g_type_register_static (G_TYPE_OBJECT,
"EphyDialog",
&our_info, 0);
}
return type;
}
static PropertyInfo *
lookup_info (EphyDialog *dialog, const char *id)
{
return g_hash_table_lookup (dialog->priv->props, id);
}
static void
set_sensitivity (PropertyInfo *info, gboolean sensitive)
{
g_return_if_fail (info->widget != NULL);
if (info->widget_type == PT_RADIOBUTTON)
{
GSList *list, *l;
list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (info->widget));
for (l = list; l != NULL; l = l->next)
{
sensitive = gtk_widget_is_sensitive (GTK_WIDGET (l->data)) && sensitive;
gtk_widget_set_sensitive (GTK_WIDGET (l->data), sensitive);
}
}
else if (info->widget_type == PT_EDITABLE)
{
sensitive = gtk_widget_is_sensitive (info->widget) && sensitive;
gtk_editable_set_editable (GTK_EDITABLE (info->widget), sensitive);
}
else
{
sensitive = gtk_widget_is_sensitive (info->widget) && sensitive;
gtk_widget_set_sensitive (info->widget, sensitive);
}
}
static void
set_value_from_pref (PropertyInfo *info, GValue *value)
{
char *text;
switch (info->data_type)
{
case G_TYPE_STRING:
g_value_init (value, G_TYPE_STRING);
text = eel_gconf_get_string (info->pref);
g_value_take_string (value, text);
break;
case G_TYPE_INT:
g_value_init (value, G_TYPE_INT);
g_value_set_int (value, eel_gconf_get_integer (info->pref));
break;
case G_TYPE_FLOAT:
g_value_init (value, G_TYPE_FLOAT);
g_value_set_float (value, eel_gconf_get_float (info->pref));
break;
case G_TYPE_BOOLEAN:
g_value_init (value, G_TYPE_BOOLEAN);
g_value_set_boolean (value, eel_gconf_get_boolean (info->pref));
break;
default:
g_warning ("Unsupported value read from pref %s\n", info->pref);
break;
}
}
static void
set_pref_from_value (PropertyInfo *info, GValue *value)
{
const char *pref = info->pref;
if (!G_VALUE_HOLDS (value, info->data_type))
{
g_warning ("Value type mismatch for id[%s], pref[%s]", info->id, info->pref);
return;
}
switch (info->data_type)
{
case G_TYPE_STRING:
{
const char *string = g_value_get_string (value);
if (string != NULL)
{
eel_gconf_set_string (pref, string);
}
else
{
eel_gconf_unset_key (pref);
}
break;
}
case G_TYPE_INT:
eel_gconf_set_integer (pref, g_value_get_int (value));
break;
case G_TYPE_FLOAT:
eel_gconf_set_float (pref, g_value_get_float (value));
break;
case G_TYPE_BOOLEAN:
eel_gconf_set_boolean (pref, g_value_get_boolean (value));
break;
default:
break;
}
}
static gboolean
set_value_from_editable (PropertyInfo *info, GValue *value)
{
char *text;
gboolean retval = TRUE;
gboolean free_text = TRUE;
g_return_val_if_fail (GTK_IS_EDITABLE (info->widget), FALSE);
text = gtk_editable_get_chars (GTK_EDITABLE (info->widget), 0, -1);
g_value_init (value, info->data_type);
switch (info->data_type)
{
case G_TYPE_STRING:
g_value_take_string (value, text);
free_text = FALSE;
break;
/* FIXME : handle possible errors in the input for int and float */
case G_TYPE_INT:
g_value_set_int (value, atoi (text));
break;
case G_TYPE_FLOAT:
g_value_set_float (value, strtod (text, NULL));
break;
default:
retval = FALSE;
g_value_unset (value);
g_warning ("Unsupported value type for editable %s", info->id);
break;
}
if (free_text)
{
g_free (text);
}
return retval;
}
static gboolean
set_value_from_combobox (PropertyInfo *info, GValue *value)
{
g_return_val_if_fail (GTK_IS_COMBO_BOX (info->widget), FALSE);
if (info->data_col != -1)
{
GtkTreeModel *model;
GtkTreeIter iter;
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (info->widget), &iter))
{
model = gtk_combo_box_get_model (GTK_COMBO_BOX (info->widget));
gtk_tree_model_get_value (model, &iter, info->data_col, value);
return TRUE;
}
}
else if (info->data_type == G_TYPE_INT)
{
int index;
index = gtk_combo_box_get_active (GTK_COMBO_BOX (info->widget));
if (index >= 0)
{
g_value_init (value, G_TYPE_INT);
g_value_set_int (value, index);
return TRUE;
}
}
else
{
g_warning ("Unsupported data type for combo %s\n", info->id);
}
return FALSE;
}
static int
get_radio_button_active_index (GtkWidget *radiobutton)
{
GtkToggleButton *toggle_button;
GSList *list;
int index, i, length;
/* get group list */
list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton));
length = g_slist_length (list);
/* iterate over list to find active button */
for (i = 0; list != NULL; i++, list = list->next)
{
/* get button and text */
toggle_button = GTK_TOGGLE_BUTTON (list->data);
if (gtk_toggle_button_get_active (toggle_button))
{
break;
}
}
/* check we didn't run off end */
g_assert (list != NULL);
/* return index (reverse order!) */
return index = (length - 1) - i;
}
static gboolean
set_value_from_radiobuttongroup (PropertyInfo *info, GValue *value)
{
gboolean retval = TRUE;
int index;
g_return_val_if_fail (GTK_IS_RADIO_BUTTON (info->widget), FALSE);
index = get_radio_button_active_index (info->widget);
g_return_val_if_fail (index >= 0, FALSE);
if (info->data_type == G_TYPE_STRING)
{
g_return_val_if_fail (info->string_enum != NULL, FALSE);
g_return_val_if_fail (g_list_nth_data (info->string_enum, index) != NULL, FALSE);
g_value_init (value, G_TYPE_STRING);
g_value_set_string (value, (char*) g_list_nth_data (info->string_enum, index));
}
else if (info->data_type == G_TYPE_INT)
{
g_value_init (value, G_TYPE_INT);
g_value_set_int (value, index);
}
else
{
retval = FALSE;
g_warning ("unsupported data type for radio button %s\n", info->id);
}
return retval;
}
static gboolean
set_value_from_spin_button (PropertyInfo *info, GValue *value)
{
gboolean retval = TRUE;
gdouble f;
gboolean is_int;
g_return_val_if_fail (GTK_IS_SPIN_BUTTON (info->widget), FALSE);
f = gtk_spin_button_get_value (GTK_SPIN_BUTTON (info->widget));
is_int = (gtk_spin_button_get_digits (GTK_SPIN_BUTTON(info->widget)) == 0);
if (info->data_type == G_TYPE_INT && is_int)
{
g_value_init (value, G_TYPE_INT);
g_value_set_int (value, (int) f);
}
else if (info->data_type == G_TYPE_FLOAT)
{
g_value_init (value, G_TYPE_FLOAT);
g_value_set_float (value, f);
}
else
{
retval = FALSE;
g_warning ("Unsupported data type for spin button %s\n", info->id);
}
return retval;
}
static gboolean
set_value_from_togglebutton (PropertyInfo *info, GValue *value)
{
gboolean retval = TRUE;
gboolean active;
g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (info->widget), FALSE);
active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (info->widget));
if (info->apply_type & PT_INVERTED)
{
active = !active;
}
if (info->data_type == G_TYPE_BOOLEAN)
{
g_value_init (value, info->data_type);
g_value_set_boolean (value, active);
}
else
{
retval = FALSE;
g_warning ("Unsupported data type for toggle button %s\n", info->id);
}
return retval;
}
static gboolean
set_value_from_info (PropertyInfo *info, GValue *value)
{
gboolean retval;
if (info->sane_state == FALSE)
{
return FALSE;
}
switch (info->widget_type)
{
case PT_SPINBUTTON:
retval = set_value_from_spin_button (info, value);
break;
case PT_RADIOBUTTON:
retval = set_value_from_radiobuttongroup (info, value);
break;
case PT_TOGGLEBUTTON:
retval = set_value_from_togglebutton (info, value);
break;
case PT_EDITABLE:
retval = set_value_from_editable (info, value);
break;
case PT_COMBOBOX:
retval = set_value_from_combobox (info, value);
break;
default:
retval = FALSE;
g_warning ("Unsupported widget type\n");
break;
}
return retval;
}
static void
set_editable_from_value (PropertyInfo *info, const GValue *value)
{
char *text = NULL;
int pos = 0; /* insertion position */
g_return_if_fail (GTK_IS_EDITABLE (info->widget));
switch (info->data_type)
{
case G_TYPE_STRING:
text = g_value_dup_string (value);
break;
case G_TYPE_INT:
text = g_strdup_printf ("%d", g_value_get_int (value));
break;
case G_TYPE_FLOAT:
text = g_strdup_printf ("%.2f", g_value_get_float (value));
break;
default:
break;
}
if (text == NULL)
{
text = g_strdup ("");
}
info->sane_state = TRUE;
gtk_editable_delete_text (GTK_EDITABLE (info->widget), 0, -1);
gtk_editable_insert_text (GTK_EDITABLE (info->widget), text, strlen (text), &pos);
g_free (text);
}
static int
strcmp_with_null (const char *key1,
const char *key2)
{
if (key1 == NULL && key2 == NULL)
{
return 0;
}
if (key1 == NULL)
{
return -1;
}
if (key2 == NULL)
{
return 1;
}
return strcmp (key1, key2);
}
static int
get_index_from_value (const GValue *value, GList *string_enum)
{
int index = -1;
const char *val;
GList *s = NULL;
if (string_enum)
{
val = g_value_get_string (value);
s = g_list_find_custom (string_enum, val, (GCompareFunc) strcmp_with_null);
if (s)
{
index = g_list_position (string_enum, s);
}
}
else
{
index = g_value_get_int (value);
}
return index;
}
static gboolean
compare_values (const GValue *a, const GValue *b)
{
if (G_VALUE_HOLDS (a, G_TYPE_STRING))
{
const char *ta, *tb;
ta = g_value_get_string (a);
tb = g_value_get_string (b);
return (strcmp_with_null (ta, tb) == 0);
}
else if (G_VALUE_HOLDS (a, G_TYPE_INT))
{
return g_value_get_int (a) == g_value_get_int (b);
}
else if (G_VALUE_HOLDS (a, G_TYPE_FLOAT))
{
return g_value_get_float (a) == g_value_get_float (b);
}
else if (G_VALUE_HOLDS (a, G_TYPE_BOOLEAN))
{
return g_value_get_boolean (a) == g_value_get_boolean (b);
}
return FALSE;
}
static void
set_combo_box_from_value (PropertyInfo *info, const GValue *value)
{
g_return_if_fail (GTK_IS_COMBO_BOX (info->widget));
if (info->data_col != -1)
{
GValue data = { 0, };
GtkTreeModel *model;
GtkTreeIter iter;
gboolean valid, found = FALSE;
model = gtk_combo_box_get_model (GTK_COMBO_BOX (info->widget));
valid = gtk_tree_model_get_iter_first (model, &iter);
while (valid)
{
gtk_tree_model_get_value (model, &iter, info->data_col, &data);
found = compare_values (&data, value);
g_value_unset (&data);
if (found) break;
valid = gtk_tree_model_iter_next (model, &iter);
}
if (found)
{
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (info->widget), &iter);
}
else
{
gtk_combo_box_set_active (GTK_COMBO_BOX (info->widget), 0);
}
info->sane_state = found;
}
else if (info->data_type == G_TYPE_INT)
{
int index;
index = g_value_get_int (value);
info->sane_state = index >= 0;
g_return_if_fail (index >= -1);
gtk_combo_box_set_active (GTK_COMBO_BOX (info->widget), index);
}
else
{
g_warning ("Unsupported data type for combo box %s\n", info->id);
}
}
static void
set_radiobuttongroup_from_value (PropertyInfo *info, const GValue *value)
{
GtkToggleButton *button;
GSList *list;
gint length;
int index;
g_return_if_fail (GTK_IS_RADIO_BUTTON (info->widget));
list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (info->widget));
length = g_slist_length (list);
index = get_index_from_value (value, info->string_enum);
/* new buttons are *prepended* to the list, so button added as first
* has last position in the list */
index = (length - 1) - index;
if (index < 0 || index >= length)
{
info->sane_state = FALSE;
g_return_if_fail (index >= 0 && index < length);
return;
}
button = GTK_TOGGLE_BUTTON (g_slist_nth_data (list, index));
g_return_if_fail (button != NULL);
info->sane_state = TRUE;
if (gtk_toggle_button_get_active (button) == FALSE)
{
gtk_toggle_button_set_active (button, TRUE);
}
}
static void
set_spin_button_from_value (PropertyInfo *info, const GValue *value)
{
gdouble f = 0.0;
gboolean is_int;
g_return_if_fail (GTK_IS_SPIN_BUTTON (info->widget));
is_int = (gtk_spin_button_get_digits (GTK_SPIN_BUTTON (info->widget)) == 0);
if (info->data_type == G_TYPE_INT && is_int)
{
f = (float) g_value_get_int (value);
}
else if (info->data_type == G_TYPE_FLOAT)
{
f = g_value_get_float (value);
}
else
{
info->sane_state = FALSE;
g_warning ("Unsupported data type for spin button %s\n", info->id);
return;
}
info->sane_state = TRUE;
gtk_spin_button_set_value (GTK_SPIN_BUTTON (info->widget), f);
}
static void
set_togglebutton_from_value (PropertyInfo *info, const GValue *value)
{
gboolean active;
g_return_if_fail (GTK_IS_TOGGLE_BUTTON (info->widget));
g_return_if_fail (info->data_type == G_TYPE_BOOLEAN);
active = g_value_get_boolean (value);
if (info->apply_type & PT_INVERTED)
{
active = !active;
}
info->sane_state = TRUE;
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (info->widget), active);
}
static void
set_info_from_value (PropertyInfo *info, const GValue *value)
{
if (!G_VALUE_HOLDS (value, info->data_type))
{
g_warning ("Incompatible value types for id %s\n", info->id);
return;
}
switch (info->widget_type)
{
case PT_SPINBUTTON:
set_spin_button_from_value (info, value);
break;
case PT_RADIOBUTTON:
set_radiobuttongroup_from_value (info, value);
break;
case PT_TOGGLEBUTTON:
set_togglebutton_from_value (info, value);
break;
case PT_EDITABLE:
set_editable_from_value (info, value);
break;
case PT_COMBOBOX:
set_combo_box_from_value (info, value);
break;
default:
g_warning ("Unknown widget type\n");
break;
}
}
/* widget changed callbacks */
static void
set_pref_from_info_and_emit (PropertyInfo *info)
{
GValue value = { 0, };
if (!set_value_from_info (info, &value))
{
return;
}
g_signal_emit (info->dialog, signals[CHANGED], g_quark_from_string (info->id), &value);
if ((info->apply_type & PT_AUTOAPPLY) && info->pref != NULL)
{
set_pref_from_value (info, &value);
}
g_value_unset (&value);
}
static void
togglebutton_clicked_cb (GtkWidget *widget, PropertyInfo *info)
{
info->sane_state = TRUE;
set_pref_from_info_and_emit (info);
}
static void
radiobutton_clicked_cb (GtkWidget *widget, PropertyInfo *info)
{
info->sane_state = TRUE;
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)))
{
return;
}
set_pref_from_info_and_emit (info);
}
static gboolean
spinbutton_timeout_cb (PropertyInfo *info)
{
GTimer *spin_timer;
spin_timer = (GTimer *) g_object_get_data (G_OBJECT (info->widget), "timer");
/* timer still valid? */
if (spin_timer == NULL)
{
/* don't call me again */
return FALSE;
}
/* okay, we're ready to set */
if (g_timer_elapsed (spin_timer, NULL) >= SPIN_DELAY)
{
/* kill off the timer */
g_timer_destroy (spin_timer);
g_object_set_data (G_OBJECT (info->widget), "timer", NULL);
/* HACK update the spinbutton here so that the
* changes made directly in the entry are accepted
* and set in the pref. Otherwise the old value is used */
gtk_spin_button_update (GTK_SPIN_BUTTON (info->widget));
info->sane_state = TRUE;
set_pref_from_info_and_emit (info);
/* done, don't run again */
return FALSE;
}
/* not elapsed yet, call me again */
return TRUE;
}
static void
spinbutton_changed_cb (GtkWidget *widget, PropertyInfo *info)
{
GTimer *spin_timer;
if ((info->apply_type & PT_AUTOAPPLY) == 0) return;
spin_timer = g_object_get_data (G_OBJECT (info->widget), "timer");
/* destroy an existing timer */
if (spin_timer != NULL)
{
g_timer_destroy (spin_timer);
}
/* start tnew timer */
spin_timer = g_timer_new();
g_timer_start (spin_timer);
g_object_set_data (G_OBJECT (info->widget), "timer", spin_timer);
g_timeout_add (50, (GSourceFunc) spinbutton_timeout_cb, info);
}
static void
changed_cb (GtkWidget *widget, PropertyInfo *info)
{
info->sane_state = TRUE;
set_pref_from_info_and_emit (info);
}
static void
connect_signals (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
GSList *list;
g_return_if_fail (info->widget != NULL);
switch (info->widget_type)
{
case PT_TOGGLEBUTTON:
g_signal_connect (G_OBJECT (info->widget), "clicked",
G_CALLBACK (togglebutton_clicked_cb),
(gpointer)info);
break;
case PT_RADIOBUTTON:
list = gtk_radio_button_get_group
(GTK_RADIO_BUTTON (info->widget));
for (; list != NULL; list = list->next)
{
g_signal_connect
(G_OBJECT (list->data), "clicked",
G_CALLBACK (radiobutton_clicked_cb),
info);
}
break;
case PT_SPINBUTTON:
g_signal_connect (G_OBJECT (info->widget), "changed",
G_CALLBACK (spinbutton_changed_cb),
info);
break;
case PT_COMBOBOX:
g_signal_connect (G_OBJECT (info->widget), "changed",
G_CALLBACK (changed_cb), info);
break;
case PT_EDITABLE:
g_signal_connect (G_OBJECT (info->widget), "changed",
G_CALLBACK (changed_cb), info);
break;
case PT_UNKNOWN:
break;
}
}
static void
disconnect_signals (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
g_return_if_fail (info->widget != NULL);
g_signal_handlers_disconnect_matched (info->widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, info);
}
static void
init_props (EphyDialog *dialog, const EphyDialogProperty *properties, GtkBuilder *builder)
{
int i;
for (i = 0 ; properties[i].id != NULL; i++)
{
PropertyInfo *info = g_new0 (PropertyInfo, 1);
info->id = properties[i].id;
info->dialog = dialog;
info->pref = g_strdup (properties[i].pref);
info->apply_type = properties[i].apply_type;
info->string_enum = NULL;
info->data_col = -1;
info->widget = (GtkWidget*)gtk_builder_get_object (builder, info->id);
if (GTK_IS_COMBO_BOX (info->widget))
{
info->widget_type = PT_COMBOBOX;
info->data_type = G_TYPE_INT;
}
else if (GTK_IS_SPIN_BUTTON (info->widget))
{
info->widget_type = PT_SPINBUTTON;
info->data_type = G_TYPE_INT;
}
else if (GTK_IS_RADIO_BUTTON (info->widget))
{
info->widget_type = PT_RADIOBUTTON;
info->data_type = G_TYPE_INT;
}
else if (GTK_IS_TOGGLE_BUTTON (info->widget))
{
info->widget_type = PT_TOGGLEBUTTON;
info->data_type = G_TYPE_BOOLEAN;
}
else if (GTK_IS_EDITABLE (info->widget))
{
info->widget_type = PT_EDITABLE;
info->data_type = G_TYPE_STRING;
}
else
{
info->widget_type = PT_UNKNOWN;
info->data_type = G_TYPE_INVALID;
}
if (properties[i].data_type != 0)
{
info->data_type = properties[i].data_type;
}
info->loaded = FALSE;
info->sane_state = FALSE;
g_hash_table_insert (dialog->priv->props, (char *) info->id, info);
}
}
static void
load_info (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
GValue value = { 0, };
g_return_if_fail (info->widget != NULL);
if (info->pref != NULL)
{
set_value_from_pref (info, &value);
set_info_from_value (info, &value);
g_signal_emit (info->dialog, signals[CHANGED], g_quark_from_string (info->id), &value);
g_value_unset (&value);
set_sensitivity (info, eel_gconf_key_is_writable (info->pref));
}
info->loaded = TRUE;
}
static void
save_info (gpointer key, PropertyInfo *info, EphyDialog *dialog)
{
GValue value = { 0, };
if (info->pref == NULL || (info->apply_type & PT_NORMAL) == 0)
{
return;
}
if (!info->sane_state)
{
g_warning ("Not persisting insane state of id[%s]", info->id);
return;
}
if (set_value_from_info (info, &value))
{
set_pref_from_value (info, &value);
g_value_unset (&value);
}
}
static void
setup_default_size (EphyDialog *dialog)
{
if (dialog->priv->has_default_size == FALSE)
{
EphyStateWindowFlags flags;
flags = EPHY_STATE_WINDOW_SAVE_SIZE;
if (dialog->priv->persist_position)
{
flags |= EPHY_STATE_WINDOW_SAVE_POSITION;
}
ephy_state_add_window (dialog->priv->dialog,
dialog->priv->name,
dialog->priv->default_width,
dialog->priv->default_height,
FALSE, flags);
dialog->priv->has_default_size = TRUE;
}
}
static void
dialog_destroy_cb (GtkWidget *widget, EphyDialog *dialog)
{
g_hash_table_foreach (dialog->priv->props, (GHFunc) save_info, dialog);
if (dialog->priv->disposing == FALSE)
{
g_object_unref (dialog);
}
}
static void
impl_construct (EphyDialog *dialog,
const EphyDialogProperty *properties,
const char *file,
const char *name,
const char *domain)
{
EphyDialogPrivate *priv = dialog->priv;
GtkBuilder *builder;
GError *error = NULL;
builder = gtk_builder_new ();
gtk_builder_set_translation_domain (builder, domain);
gtk_builder_add_from_file (builder, file, &error);
if (error)
{
g_warning ("Unable to load UI file %s: %s", file, error->message);
g_error_free (error);
return;
}
priv->dialog = (GtkWidget*)gtk_builder_get_object (builder, name);
g_return_if_fail (priv->dialog != NULL);
if (priv->name == NULL)
{
priv->name = g_strdup (name);
}
if (properties)
{
init_props (dialog, properties, builder);
}
g_signal_connect_object (dialog->priv->dialog, "destroy",
G_CALLBACK(dialog_destroy_cb), dialog, 0);
g_object_unref (builder);
}
static void
impl_show (EphyDialog *dialog)
{
if (dialog->priv->initialized == FALSE)
{
dialog->priv->initialized = TRUE;
g_hash_table_foreach (dialog->priv->props, (GHFunc) load_info, dialog);
g_hash_table_foreach (dialog->priv->props, (GHFunc) connect_signals, dialog);
}
setup_default_size (dialog);
if (dialog->priv->parent != NULL)
{
/* make the dialog transient again, because it seems to get
* forgotten after gtk_widget_hide
*/
gtk_window_set_transient_for (GTK_WINDOW (dialog->priv->dialog),
GTK_WINDOW (dialog->priv->parent));
}
gtk_window_present (GTK_WINDOW (dialog->priv->dialog));
}
/**
* ephy_dialog_set_modal:
* @dialog: an #EphyDialog
* @is_modal: %TRUE to make @dialog modal
*
* Sets @dialog to be modal or not.
**/
void
ephy_dialog_set_modal (EphyDialog *dialog,
gboolean is_modal)
{
g_return_if_fail (EPHY_IS_DIALOG (dialog));
dialog->priv->modal = is_modal != FALSE;
gtk_window_set_modal (GTK_WINDOW(dialog->priv->dialog), is_modal);
}
/**
* ephy_dialog_add_enum:
* @dialog: an #EphyDialog
* @property_id: string identifier of the property to modify
* @n_items: length of @items array
* @items: array of items to add to @property_id
*
* Modifies the property identified by @property_id in @dialog to have its
* string_enum member set to a #GList constructed with the elements given as
* @items.
**/
void
ephy_dialog_add_enum (EphyDialog *dialog,
const char *property_id,
guint n_items,
const char *const *items)
{
PropertyInfo *info;
int i = 0;
GList *l = NULL;
g_return_if_fail (EPHY_IS_DIALOG (dialog));
info = lookup_info (dialog, property_id);
g_return_if_fail (info != NULL);
for (i = 0; i < n_items; i++)
{
l = g_list_prepend (l, g_strdup (items[i]));
}
info->string_enum = g_list_reverse (l);
}
/**
* ephy_dialog_set_data_column:
* @dialog: an #EphyDialog
* @property_id: string identifier of the property to modify
* @column: value for the data_col member of @property_id
*
* Sets the data_col member of the property identified by @property_id in @dialog
* to @column.
**/
void
ephy_dialog_set_data_column (EphyDialog *dialog,
const char *property_id,
int column)
{
PropertyInfo *info;
g_return_if_fail (EPHY_IS_DIALOG (dialog));
info = lookup_info (dialog, property_id);
g_return_if_fail (info != NULL);
info->data_col = column;
}
/**
* ephy_dialog_set_pref:
* @dialog: an #EphyDialog
* @property_id: string identifier of the property to modify
* @pref: preference value of the property identified by @property_id
*
* Sets the pref member of the property of @dialog identified by @property_id
* to @pref.
**/
void
ephy_dialog_set_pref (EphyDialog *dialog,
const char *property_id,
const char *pref)
{
PropertyInfo *info;
g_return_if_fail (EPHY_IS_DIALOG (dialog));
info = lookup_info (dialog, property_id);
g_return_if_fail (info != NULL);
disconnect_signals (NULL, info, dialog);
info->loaded = FALSE;
info->sane_state = FALSE;
g_free (info->pref);
info->pref = g_strdup (pref);
if (dialog->priv->initialized)
{
/* dialog is already initialised, so initialise this here */
load_info (NULL, info, dialog);
connect_signals (NULL, info, dialog);
}
}
/**
* ephy_dialog_set_size_group:
* @dialog: an #EphyDialog
* @first_id: id of a widget in @dialog
* @Varargs: a %NULL-terminated list of widget ids
*
* Put @first_id and @Varargs widgets into the same #GtkSizeGroup.
* Note that this are all widgets inside @dialog.
**/
void
ephy_dialog_set_size_group (EphyDialog *dialog,
const char *first_id,
...)
{
GtkSizeGroup *size_group;
va_list vl;
g_return_if_fail (EPHY_IS_DIALOG (dialog));
size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
va_start (vl, first_id);
while (first_id != NULL)
{
PropertyInfo *info;
info = lookup_info (dialog, first_id);
g_return_if_fail (info != NULL);
g_return_if_fail (info->widget != NULL);
gtk_size_group_add_widget (size_group, info->widget);
first_id = va_arg (vl, const char*);
}
va_end (vl);
g_object_unref (size_group);
}
/**
* ephy_dialog_construct:
* @dialog: an #EphyDialog
* @properties: an array of #EphyDialogProperty elements
* @file: the path to a #GtkBuilder file
* @name: name of the widget to use for @dialog, found in @file
* @domain: translation domain to set for @dialog
*
* Constructs the widget part of @dialog using the widget identified by @name
* in the #GtkBuilder file found at @file. Fills the dialog properties with
* @properties and sets translation domain to @domain.
**/
void
ephy_dialog_construct (EphyDialog *dialog,
const EphyDialogProperty *properties,
const char *file,
const char *name,
const char *domain)
{
EphyDialogClass *klass = EPHY_DIALOG_GET_CLASS (dialog);
klass->construct (dialog, properties, file, name, domain);
}
/**
* ephy_dialog_show:
* @dialog: an #EphyDialog
*
* Shows @dialog on screen.
**/
void
ephy_dialog_show (EphyDialog *dialog)
{
EphyDialogClass *klass;
g_return_if_fail (EPHY_IS_DIALOG (dialog));
klass = EPHY_DIALOG_GET_CLASS (dialog);
klass->show (dialog);
}
/**
* ephy_dialog_hide:
* @dialog: an #EphyDialog
*
* Calls gtk_widget_hide on @dialog.
**/
void
ephy_dialog_hide (EphyDialog *dialog)
{
g_return_if_fail (EPHY_IS_DIALOG (dialog));
g_return_if_fail (dialog->priv->dialog != NULL);
gtk_widget_hide (dialog->priv->dialog);
}
/**
* ephy_dialog_run:
* @dialog: an #EphyDialog
*
* Runs gtk_dialog_run on @dialog and waits for a response.
*
* Returns: the user response to gtk_dialog_run or 0 if @dialog is not valid
**/
int
ephy_dialog_run (EphyDialog *dialog)
{
g_return_val_if_fail (EPHY_IS_DIALOG (dialog), 0);
ephy_dialog_show (dialog);
gtk_window_group_add_window (ephy_gui_ensure_window_group (GTK_WINDOW (dialog->priv->parent)),
GTK_WINDOW (dialog->priv->dialog));
return gtk_dialog_run (GTK_DIALOG (dialog->priv->dialog));
}
/**
* ephy_dialog_get_control:
* @dialog: an #EphyDialog
* @property_id: the string identifier of the requested control
*
* Gets the internal widget corresponding to @property_id from @dialog.
* Return value: (transfer none): the #GtkWidget corresponding to @property_id
* or %NULL
**/
GtkWidget *
ephy_dialog_get_control (EphyDialog *dialog,
const char *property_id)
{
PropertyInfo *info;
g_return_val_if_fail (EPHY_IS_DIALOG (dialog), NULL);
info = lookup_info (dialog, property_id);
g_return_val_if_fail (info != NULL, NULL);
return info->widget;
}
/**
* ephy_dialog_get_controls:
* @dialog: an #EphyDialog
* @first_id: identifier of the requested control
* @Varargs: a %NULL terminated list of extra pairs of properties as const char
* and store locations as #GtkWidgets.
*
* Gets the requested controls according to given property-store_location pairs.
* Properties are given as strings (const char *), controls are returned as
* #GtkWidget elements.
* Rename to: ephy_dialog_get_controls
**/
void
ephy_dialog_get_controls (EphyDialog *dialog,
const char *first_id,
...)
{
PropertyInfo *info;
GtkWidget **wptr;
va_list varargs;
va_start (varargs, first_id);
while (first_id != NULL)
{
info = lookup_info (dialog, first_id);
g_return_if_fail (info != NULL);
wptr = va_arg (varargs, GtkWidget **);
*wptr = info->widget;
first_id = va_arg (varargs, const char *);
}
va_end (varargs);
}
/**
* ephy_dialog_get_value:
* @dialog: an #EphyDialog
* @property_id: property name
* @value: (out): location to store the value of @property_id
*
* Gets the value of @property_id and stores it in @value.
*
* Returns: %TRUE if the operation was successful
*/
gboolean
ephy_dialog_get_value (EphyDialog *dialog,
const char *property_id,
GValue *value)
{
PropertyInfo *info;
g_return_val_if_fail (EPHY_IS_DIALOG (dialog), FALSE);
info = lookup_info (dialog, property_id);
g_return_val_if_fail (info != NULL, FALSE);
return set_value_from_info (info, value);
}
/**
* ephy_dialog_set_value:
* @dialog: an #EphyDialog
* @property_id: @dialog property to set
* @value: value to set @property_id to
*
* Sets the property identified by @property_id to @value in @dialog.
**/
void
ephy_dialog_set_value (EphyDialog *dialog,
const char *property_id,
const GValue *value)
{
PropertyInfo *info;
g_return_if_fail (EPHY_IS_DIALOG (dialog));
info = lookup_info (dialog, property_id);
g_return_if_fail (info != NULL);
set_info_from_value (info, value);
}
static void
free_prop_info (PropertyInfo *info)
{
if (info->string_enum)
{
g_list_foreach (info->string_enum, (GFunc)g_free, NULL);
g_list_free (info->string_enum);
}
g_free (info->pref);
g_free (info);
}
static void
ephy_dialog_init (EphyDialog *dialog)
{
dialog->priv = EPHY_DIALOG_GET_PRIVATE (dialog);
dialog->priv->default_width = -1;
dialog->priv->default_height = -1;
dialog->priv->props = g_hash_table_new_full
(g_str_hash, g_str_equal, NULL, (GDestroyNotify) free_prop_info);
}
static void
ephy_dialog_dispose (GObject *object)
{
EphyDialog *dialog = EPHY_DIALOG (object);
if (dialog->priv->dialog)
{
dialog->priv->disposing = TRUE;
gtk_widget_destroy (dialog->priv->dialog);
dialog->priv->dialog = NULL;
}
parent_class->dispose (object);
}
static void
ephy_dialog_finalize (GObject *object)
{
EphyDialog *dialog = EPHY_DIALOG (object);
g_hash_table_destroy (dialog->priv->props);
g_free (dialog->priv->name);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* ephy_dialog_get_parent:
* @dialog: an #EphyDialog
*
* Gets @dialog's parent-window.
*
* Returns: (transfer none): the parent-window of @dialog
**/
GtkWidget *
ephy_dialog_get_parent (EphyDialog *dialog)
{
g_return_val_if_fail (EPHY_IS_DIALOG (dialog), NULL);
return dialog->priv->parent;
}
/**
* ephy_dialog_set_parent:
* @dialog: an #EphyDialog
* @parent: new parent for @dialog
*
* Sets @parent as the parent-window of @dialog.
**/
void
ephy_dialog_set_parent (EphyDialog *dialog,
GtkWidget *parent)
{
g_return_if_fail (EPHY_IS_DIALOG (dialog));
dialog->priv->parent = parent;
g_object_notify (G_OBJECT (dialog), "parent-window");
}
static void
ephy_dialog_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EphyDialog *dialog = EPHY_DIALOG (object);
switch (prop_id)
{
case PROP_PARENT_WINDOW:
ephy_dialog_set_parent (dialog, g_value_get_object (value));
break;
case PROP_MODAL:
ephy_dialog_set_modal (dialog, g_value_get_boolean (value));
break;
case PROP_PERSIST_POSITION:
dialog->priv->persist_position = g_value_get_boolean (value);
break;
case PROP_DEFAULT_WIDTH:
dialog->priv->default_width = g_value_get_int (value);
break;
case PROP_DEFAULT_HEIGHT:
dialog->priv->default_height = g_value_get_int (value);
break;
}
}
static void
ephy_dialog_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EphyDialog *dialog = EPHY_DIALOG (object);
switch (prop_id)
{
case PROP_PARENT_WINDOW:
g_value_set_object (value, dialog->priv->parent);
break;
case PROP_MODAL:
g_value_set_boolean (value, dialog->priv->modal);
break;
case PROP_PERSIST_POSITION:
g_value_set_boolean (value, dialog->priv->persist_position);
break;
case PROP_DEFAULT_WIDTH:
g_value_set_int (value, dialog->priv->default_width);
break;
case PROP_DEFAULT_HEIGHT:
g_value_set_int (value, dialog->priv->default_height);
break;
}
}
static void
ephy_dialog_class_init (EphyDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
object_class->finalize = ephy_dialog_finalize;
object_class->dispose = ephy_dialog_dispose;
object_class->set_property = ephy_dialog_set_property;
object_class->get_property = ephy_dialog_get_property;
klass->construct = impl_construct;
klass->show = impl_show;
/**
* EphyDialog::changed:
* @dialog: the object on which the signal is emitted
* @value: new value of the modified widget, as a #GValue
*
* Emitted everytime a child widget of the dialog has its changed or
* clicked signal emitted.
*/
signals[CHANGED] =
g_signal_new ("changed",
EPHY_TYPE_DIALOG,
G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
G_STRUCT_OFFSET (EphyDialogClass, changed),
NULL, NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE,
1,
G_TYPE_POINTER);
/**
* EphyDialog:parent-window:
*
* Dialog's parent window.
*/
g_object_class_install_property (object_class,
PROP_PARENT_WINDOW,
g_param_spec_object ("parent-window",
"Parent window",
"Parent window",
GTK_TYPE_WINDOW,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
/**
* EphyDialog:modal:
*
* Whether the dialog is or not modal.
*/
g_object_class_install_property (object_class,
PROP_MODAL,
g_param_spec_boolean ("modal",
"Modal",
"Modal dialog",
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
/**
* EphyDialog:persist-position:
*
* If dialog position should be persistent.
*/
g_object_class_install_property (object_class,
PROP_PERSIST_POSITION,
g_param_spec_boolean ("persist-position",
"Persist position",
"Persist dialog position",
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
G_PARAM_CONSTRUCT_ONLY));
/**
* EphyDialog:default-width:
*
* The dialog default width.
*/
g_object_class_install_property (object_class,
PROP_DEFAULT_WIDTH,
g_param_spec_int ("default-width",
"Default width",
"Default dialog width",
-1,
G_MAXINT,
-1,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
G_PARAM_CONSTRUCT_ONLY));
/**
* EphyDialog:default-height:
*
* The dialog default height.
*/
g_object_class_install_property (object_class,
PROP_DEFAULT_HEIGHT,
g_param_spec_int ("default-height",
"Default height",
"Default dialog height",
-1,
G_MAXINT,
-1,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (object_class, sizeof (EphyDialogPrivate));
}
/**
* ephy_dialog_new:
*
* Creates a new #EphyDialog.
*
* Returns: a new #EphyDialog
**/
EphyDialog *
ephy_dialog_new (void)
{
return EPHY_DIALOG (g_object_new (EPHY_TYPE_DIALOG, NULL));
}
/**
* ephy_dialog_new_with_parent:
* @parent_window: a window to be parent of the new dialog
*
* Creates a new #EphyDialog with @parent_window as its parent.
*
* Returns: a new #EphyDialog
**/
EphyDialog *
ephy_dialog_new_with_parent (GtkWidget *parent_window)
{
return EPHY_DIALOG (g_object_new (EPHY_TYPE_DIALOG,
"parent-window", parent_window,
NULL));
}