/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* GConf
* Copyright (C) 1999 - 2001 Red Hat Inc.
*
* This 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.
*
* This 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 this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* This program demonstrates how to use GConf. The key thing is that
* the main window and the prefs dialog have NO KNOWLEDGE of one
* another as far as configuration values are concerned; they don't
* even have to be in the same process. That is, the GConfClient acts
* as the data "model" for configuration information; the main
* application is a "view" of the model; and the prefs dialog is a
* "controller."
*
* You can tell if your application has done this correctly by
* using "gconftool" instead of your preferences dialog to set
* preferences. For example:
*
* gconftool --type=string --set /apps/basic-gconf-app/foo "My string"
*
* If that doesn't work every bit as well as setting the value
* via the prefs dialog, then you aren't doing things right. ;-)
*
*
* If you really want to be mean to your app, make it survive
* this:
*
* gconftool --break-key /apps/basic-gconf-app/foo
*
* Remember, the GConf database is just like an external file or
* the network - it may have bogus values in it. GConf admin
* tools will let people put in whatever they can think of.
*
* GConf does guarantee that string values will be valid UTF-8, for
* convenience.
*
*/
/* Throughout, this program is letting GConfClient use its default
* error handlers rather than checking for errors or attaching custom
* handlers to the "unreturned_error" signal. Thus the last arg to
* GConfClient functions is NULL.
*/
/* Special mention of an idiom often used in GTK+ apps that does
* not work right with GConf but may appear to at first:
*
* gboolean i_am_changing_value;
*
* i_am_changing_value = TRUE;
* change_value (value);
* i_am_changing_value = FALSE;
*
* This breaks for several reasons: notification of changes
* may be asynchronous, you may get notifications that are not
* caused by change_value () while change_value () is running,
* since GConf will enter the main loop, and also if you need
* this code to work you are probably going to have issues
* when someone other than yourself sets the value.
*
* A robust solution in this case is often to compare the old
* and new values to see if they've really changed, thus avoiding
* whatever loop you were trying to avoid.
*
*/
/* Be clean and pure */
#define GTK_DISABLE_DEPRECATED
#define G_DISABLE_DEPRECATED
#include <gconf/gconf-client.h>
#include <gtk/gtk.h>
static GtkWidget* create_main_window (GConfClient *client);
static GtkWidget* create_prefs_dialog (GtkWidget *parent,
GConfClient *client);
int
main (int argc, char** argv)
{
GConfClient *client;
GtkWidget *main_window;
gtk_init (&argc, &argv);
/* Get the default client */
client = gconf_client_get_default ();
/* Tell GConfClient that we're interested in the given directory.
* This means GConfClient will receive notification of changes
* to this directory, and cache keys under this directory.
* So _don't_ add "/" or something silly like that or you'll end
* up with a copy of the whole GConf database. ;-)
*
* We pass NULL for the error to use the default error handler;
* and use PRELOAD_NONE to avoid loading all config keys on
* startup. If your app pretty much reads all config keys
* on startup, then preloading the cache may make sense.
*/
gconf_client_add_dir (client, "/apps/basic-gconf-app",
GCONF_CLIENT_PRELOAD_NONE, NULL);
main_window = create_main_window (client);
gtk_widget_show_all (main_window);
gtk_main ();
/* This ensures we cleanly detach from the GConf server (assuming
* we hold the last reference). It's purely a bit of cleanliness,
* the server does survive fine if we crash.
*/
g_object_unref (G_OBJECT (client));
return 0;
}
/* Quit app when window is destroyed */
static void
destroy_callback (GtkWidget *window,
gpointer data)
{
gtk_main_quit ();
}
/* Remove the notification callback when the widget monitoring
* notifications is destroyed
*/
static void
configurable_widget_destroy_callback (GtkWidget *widget,
gpointer data)
{
guint notify_id;
GConfClient *client;
client = g_object_get_data (G_OBJECT (widget), "client");
notify_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "notify_id"));
if (notify_id != 0)
gconf_client_notify_remove (client, notify_id);
}
/* Notification callback for our label widgets that
* monitor the current value of a gconf key. i.e.
* we are conceptually "configuring" the label widgets
*/
static void
configurable_widget_config_notify (GConfClient *client,
guint cnxn_id,
GConfEntry *entry,
gpointer user_data)
{
GtkWidget *label = user_data;
g_return_if_fail (GTK_IS_LABEL (label));
/* Note that value can be NULL (unset) or it can have
* the wrong type! Need to check that to survive
* gconftool --break-key
*/
if (gconf_entry_get_value (entry) == NULL)
{
gtk_label_set_text (GTK_LABEL (label), "");
}
else if (gconf_entry_get_value (entry)->type == GCONF_VALUE_STRING)
{
gtk_label_set_text (GTK_LABEL (label),
gconf_value_get_string (gconf_entry_get_value (entry)));
}
else
{
/* A real app would probably fall back to a reasonable default
* in this case, instead of putting funky stuff in the GUI.
*/
gtk_label_set_text (GTK_LABEL (label), "!type error!");
}
}
/* Create a GtkLabel inside a frame, that we can "configure"
* (the label displays the value of the config key).
*/
static GtkWidget*
create_configurable_widget (GConfClient *client,
const gchar *config_key)
{
GtkWidget *frame;
GtkWidget *label;
guint notify_id;
gchar *str;
str = g_strdup_printf ("Value of \"%s\"", config_key);
frame = gtk_frame_new (str);
g_free (str);
label = gtk_label_new ("");
gtk_container_add (GTK_CONTAINER (frame), label);
str = gconf_client_get_string (client, config_key, NULL);
if (str != NULL)
{
gtk_label_set_text (GTK_LABEL (label), str);
g_free (str);
}
notify_id = gconf_client_notify_add (client,
config_key,
configurable_widget_config_notify,
label,
NULL, NULL);
/* Note that notify_id will be 0 if there was an error,
* so we handle that in our destroy callback.
*/
g_object_set_data (G_OBJECT (label), "notify_id", GUINT_TO_POINTER (notify_id));
g_object_set_data (G_OBJECT (label), "client", client);
g_signal_connect (G_OBJECT (label), "destroy",
G_CALLBACK (configurable_widget_destroy_callback),
NULL);
return frame;
}
static void
prefs_dialog_destroyed (GtkWidget *dialog,
gpointer main_window)
{
g_object_set_data (G_OBJECT (main_window), "prefs", NULL);
}
/* prefs button clicked */
static void
prefs_clicked (GtkWidget *button,
gpointer data)
{
GtkWidget *prefs_dialog;
GtkWidget *main_window = data;
GConfClient *client;
prefs_dialog = g_object_get_data (G_OBJECT (main_window), "prefs");
if (prefs_dialog == NULL)
{
client = g_object_get_data (G_OBJECT (main_window), "client");
prefs_dialog = create_prefs_dialog (main_window, client);
g_object_set_data (G_OBJECT (main_window), "prefs", prefs_dialog);
g_signal_connect (G_OBJECT (prefs_dialog), "destroy",
G_CALLBACK (prefs_dialog_destroyed),
main_window);
gtk_widget_show_all (prefs_dialog);
}
else
{
/* show existing dialog */
gtk_window_present (GTK_WINDOW (prefs_dialog));
}
}
static GtkWidget*
create_main_window (GConfClient *client)
{
GtkWidget *w;
GtkWidget *vbox;
GtkWidget *config;
GtkWidget *prefs;
w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (w), "basic-gconf-app Main Window");
vbox = gtk_vbox_new (FALSE, 5);
gtk_container_add (GTK_CONTAINER (w), vbox);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
/* Create labels that we can "configure" */
config = create_configurable_widget (client, "/apps/basic-gconf-app/foo");
gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);
config = create_configurable_widget (client, "/apps/basic-gconf-app/bar");
gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);
config = create_configurable_widget (client, "/apps/basic-gconf-app/baz");
gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);
config = create_configurable_widget (client, "/apps/basic-gconf-app/blah");
gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (w), "destroy",
G_CALLBACK (destroy_callback), NULL);
g_object_set_data (G_OBJECT (w), "client", client);
prefs = gtk_button_new_with_mnemonic ("_Prefs");
gtk_box_pack_end (GTK_BOX (vbox), prefs, FALSE, FALSE, 0);
g_signal_connect (G_OBJECT (prefs), "clicked",
G_CALLBACK (prefs_clicked), w);
return w;
}
/*
* Preferences dialog code. NOTE that the prefs dialog knows NOTHING
* about the existence of the main window; it is purely a way to fool
* with the GConf database. It never does something like change
* the main window directly; it ONLY changes GConf keys via
* GConfClient. This is _important_, because people may configure
* your app without using your preferences dialog.
*
* This is an instant-apply prefs dialog. For a complicated
* apply/revert/cancel dialog as in GNOME 1, see the
* complex-gconf-app.c example. But don't actually copy that example
* in GNOME 2, thanks. ;-) complex-gconf-app.c does show how
* to use GConfChangeSet.
*/
/* Commit changes to the GConf database. */
static gboolean
config_entry_commit (GtkWidget *entry, GdkEvent *event, gpointer callback_data)
{
gchar *text;
const gchar *key;
GConfClient *client;
client = g_object_get_data (G_OBJECT (entry), "client");
text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
key = g_object_get_data (G_OBJECT (entry), "key");
/* Unset if the string is zero-length, otherwise set */
if (*text != '\0')
gconf_client_set_string (client, key, text, NULL);
else
gconf_client_unset (client, key, NULL);
g_free (text);
return FALSE;
}
/* Create an entry used to edit the given config key */
static GtkWidget*
create_config_entry (GtkWidget *prefs_dialog,
GConfClient *client,
const gchar *config_key,
gboolean focus)
{
GtkWidget *hbox;
GtkWidget *entry;
GtkWidget *label;
char *str;
hbox = gtk_hbox_new (FALSE, 5);
label = gtk_label_new (config_key);
entry = gtk_entry_new ();
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
/* this will print an error via default error handler
* if the key isn't set to a string
*/
str = gconf_client_get_string (client, config_key, NULL);
if (str)
{
gtk_entry_set_text (GTK_ENTRY (entry), str);
g_free (str);
}
g_object_set_data (G_OBJECT (entry), "client", client);
g_object_set_data_full (G_OBJECT (entry), "key",
g_strdup (config_key),
(GDestroyNotify) g_free);
/* Commit changes if the user focuses out, or hits enter; we don't
* do this on "changed" since it'd probably be a bit too slow to
* round-trip to the server on every "changed" signal.
*/
g_signal_connect (G_OBJECT (entry), "focus_out_event",
G_CALLBACK (config_entry_commit),
NULL);
g_signal_connect (G_OBJECT (entry), "activate",
G_CALLBACK (config_entry_commit),
NULL);
/* Set the entry insensitive if the key it edits isn't writable.
* Technically, we should update this sensitivity if the key gets
* a change notify, but that's probably overkill.
*/
gtk_widget_set_sensitive (entry,
gconf_client_key_is_writable (client,
config_key, NULL));
if (focus)
gtk_widget_grab_focus (entry);
return hbox;
}
static GtkWidget*
create_prefs_dialog (GtkWidget *parent,
GConfClient *client)
{
GtkWidget* dialog;
GtkWidget* vbox;
GtkWidget* entry;
dialog = gtk_dialog_new_with_buttons ("basic-gconf-app Preferences",
GTK_WINDOW (parent),
0,
GTK_STOCK_CLOSE,
GTK_RESPONSE_ACCEPT,
NULL);
/* destroy dialog on button press */
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (gtk_widget_destroy),
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
/* resizing doesn't grow the entries anyhow */
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
vbox = gtk_vbox_new (FALSE, 5);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
vbox, TRUE, TRUE, 0);
entry = create_config_entry (dialog, client, "/apps/basic-gconf-app/foo",
TRUE);
gtk_box_pack_start (GTK_BOX (vbox), entry,
FALSE, FALSE, 0);
entry = create_config_entry (dialog, client, "/apps/basic-gconf-app/bar",
FALSE);
gtk_box_pack_start (GTK_BOX (vbox), entry,
FALSE, FALSE, 0);
entry = create_config_entry (dialog, client, "/apps/basic-gconf-app/baz",
FALSE);
gtk_box_pack_start (GTK_BOX (vbox), entry,
FALSE, FALSE, 0);
entry = create_config_entry (dialog, client, "/apps/basic-gconf-app/blah",
FALSE);
gtk_box_pack_start (GTK_BOX (vbox), entry,
FALSE, FALSE, 0);
return dialog;
}
syntax highlighted by Code2HTML, v. 0.9.1