Gnome2-GConf
view release on metacpan or search on metacpan
examples/basic-gconf-app.pl view on Meta::CPAN
#!/usr/bin/perl
# 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 None.
#
# Special mention of an idiom often used in GTK+ apps that does
# not work right with GConf but may appear to at first:
#
# i_am_changing_value = gtk.TRUE
# change_value (value)
# i_am_changing_value = gtk.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.
#
# The code is a direct mapping (with some perlisms) of the C
# code; where the code diverge, I placed a comment. (ebassi)
use strict;
use warnings;
use Glib qw/TRUE FALSE/;
use Gtk2 '-init';
use Gnome2::GConf;
our $client = Gnome2::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 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.
$client->add_dir ("/apps/basic-gconf-app", 'preload-none');
our $main_window = create_main_window ($client);
$main_window->show_all;
Gtk2->main;
# Remove any notification on the directory
$client->remove_dir ("/apps/basic-gconf-app");
0;
sub create_main_window
{
my $client = shift;
my $w = Gtk2::Window->new('toplevel');
$w->set_title('basic-gconf-app Main Window');
my $vbox = Gtk2::VBox->new(FALSE, 12);
$vbox->set_border_width(12);
$w->add($vbox);
my $config;
# Create labels that we can "configure"
$config = create_configurable_widget ($client, "/apps/basic-gconf-app/foo");
$vbox->pack_start($config, TRUE, TRUE, 0);
$config = create_configurable_widget ($client, "/apps/basic-gconf-app/bar");
$vbox->pack_start($config, TRUE, TRUE, 0);
$config = create_configurable_widget ($client, "/apps/basic-gconf-app/baz");
$vbox->pack_start($config, TRUE, TRUE, 0);
$config = create_configurable_widget ($client, "/apps/basic-gconf-app/blah");
$vbox->pack_start($config, TRUE, TRUE, 0);
$w->signal_connect(destroy => sub { Gtk2->main_quit });
$w->{client} = $client;
my $prefs = Gtk2::Button->new("Prefs");
$vbox->pack_end($prefs, FALSE, FALSE, 0);
$prefs->signal_connect(clicked => sub {
my $button = shift;
my $main_window = shift;
my $prefs_dialog = $main_window->{prefs};
if (not $prefs_dialog) {
my $client = $main_window->{client};
$prefs_dialog = create_prefs_dialog ($main_window, $client);
$main_window->{prefs} = $prefs_dialog;
$prefs_dialog->signal_connect(
destroy => \&prefs_dialog_destroyed,
$main_window);
$prefs_dialog->show_all;
} else {
# show existing dialog
$prefs_dialog->present;
}
}, $w);
return $w;
}
# Create a GtkLabel inside a frame, that we can "configure"
# (the label displays the value of the config key).
sub create_configurable_widget
{
my $client = shift;
my $config_key = shift;
my $frame = Gtk2::Frame->new($config_key);
my $label = Gtk2::Label->new;
$frame->add($label);
my $s = $client->get_string($config_key);
$label->set_text("Value: $s") if $s;
my $notify_id = $client->notify_add($config_key, sub {
# Notification callback for our label widgets that
# monitor the current value of a gconf key. i.e.
# we are conceptually "configuring" the label widgets
my ($client, $cnxn_id, $entry, $label) = @_;
return unless $entry;
# Note that value can be undef (unset) or it can have
# the wrong type! Need to check that to survive
# gconftool --break-key
unless ($entry->{value}) {
$label->set_text('');
} elsif ($entry->{value}->{type} eq 'string') {
warn(sprintf("got: %s\n", $entry->{value}));
$label->set_text("Value: " . $entry->{value}->{value});
} else {
$label->set_text('!type error!');
}
}, $label);
# Note that notify_id will be 0 if there was an error,
# so we handle that in our destroy callback.
$label->{notify_id} = $notify_id;
$label->{client} = $client;
$label->signal_connect(destroy => sub {
# Remove the notification callback when the widget
# monitoring notifications is destroyed
my $client = $_[0]->{client};
my $notify_id = $_[0]->{notify_id};
$client->notify_remove($notify_id) if $notify_id;
});
return $frame;
}
#
# 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.
#
sub prefs_dialog_destroyed
{
my $dialog = shift;
my $main_window = shift;
$main_window->{prefs} = undef;
}
sub config_entry_commit
{
my $entry = shift;
my $client = $entry->{client};
my $key = $entry->{key};
my $text = $entry->get_chars(0, -1);
# Unset if the string is zero-length, otherwise set
if ($text) {
# show how to use the generic 'set' method, instead of
# get_string. (ebassi)
$client->set($key, {
type => 'string',
value => $text
});
} else {
$client->unset($key);
}
# since we connect the "focus_out_event" to this callback,
# this return is needed. (ebassi)
return FALSE;
}
sub create_config_entry
{
my $prefs_dialog = shift;
my $client = shift;
my $config_key = shift;
my $has_focus = shift || FALSE;
my $hbox = Gtk2::HBox->new(FALSE, 6);
my $label = Gtk2::Label->new("$config_key =");
my $entry = Gtk2::Entry->new;
$hbox->pack_start($label, FALSE, FALSE, 0);
$hbox->pack_end($entry, FALSE, FALSE, 0);
# this will print an error via default error handler
# if the key isn't set to a string
my $s = $client->get_string($config_key);
$entry->set_text($s) if $s;
$entry->{client} = $client;
$entry->{key} = $config_key;
# 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.
$entry->signal_connect(activate => \&config_entry_commit);
$entry->signal_connect(focus_out_event => \&config_entry_commit);
# 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.
$entry->set_sensitive($client->key_is_writable($config_key));
$entry->grab_focus if $has_focus;
return $hbox;
}
sub create_prefs_dialog
{
my $parent = shift;
my $client = shift;
my $dialog = Gtk2::Dialog->new("basic-gconf-app Preferences",
$parent,
[ qw/destroy-with-parent/ ],
'gtk-close', 'accept');
# destroy dialog on button press
$dialog->signal_connect(response => sub { $_[0]->destroy });
$dialog->set_default_response('accept');
# resizing doesn't grow the entries anyhow
$dialog->set_resizable(FALSE);
my $vbox = Gtk2::VBox->new(FALSE, 12);
$vbox->set_border_width(12);
$dialog->vbox->pack_start($vbox, FALSE, FALSE, 0);
my $entry;
$entry = create_config_entry ($dialog, $client, "/apps/basic-gconf-app/foo", TRUE);
$vbox->pack_start($entry, FALSE, FALSE, 0);
$entry = create_config_entry ($dialog, $client, "/apps/basic-gconf-app/bar");
$vbox->pack_start($entry, FALSE, FALSE, 0);
$entry = create_config_entry ($dialog, $client, "/apps/basic-gconf-app/baz");
$vbox->pack_start($entry, FALSE, FALSE, 0);
$entry = create_config_entry ($dialog, $client, "/apps/basic-gconf-app/blah");
$vbox->pack_start($entry, FALSE, FALSE, 0);
return $dialog;
}
( run in 0.842 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )