/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* MateConf
 * 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/* This program demonstrates how to use MateConf.  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 MateConfClient 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 "mateconftool" instead of your preferences dialog to set
 * preferences. For example:
 *
 *   mateconftool --type=string --set /apps/basic-mateconf-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:
 *
 *   mateconftool --break-key /apps/basic-mateconf-app/foo
 *
 * Remember, the MateConf database is just like an external file or
 * the network - it may have bogus values in it. MateConf admin
 * tools will let people put in whatever they can think of.
 *
 * MateConf does guarantee that string values will be valid UTF-8, for
 * convenience.
 *   
 */

/* Throughout, this program is letting MateConfClient use its default
 * error handlers rather than checking for errors or attaching custom
 * handlers to the "unreturned_error" signal. Thus the last arg to
 * MateConfClient functions is NULL.
 */

/* Special mention of an idiom often used in GTK+ apps that does
 * not work right with MateConf 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 MateConf 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 <mateconf/mateconf-client.h>
#include <gtk/gtk.h>

static GtkWidget* create_main_window  (MateConfClient *client);
static GtkWidget* create_prefs_dialog (GtkWidget   *parent,
                                       MateConfClient *client);

int
main (int argc, char** argv)
{
  MateConfClient *client;
  GtkWidget *main_window;
  
  gtk_init (&argc, &argv);

  /* Get the default client */
  client = mateconf_client_get_default ();

  /* Tell MateConfClient that we're interested in the given directory.
   * This means MateConfClient 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 MateConf 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.
   */
   
  mateconf_client_add_dir (client, "/apps/basic-mateconf-app",
                        MATECONF_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 MateConf 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;
  MateConfClient *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)
    mateconf_client_notify_remove (client, notify_id);
}

/* Notification callback for our label widgets that
 * monitor the current value of a mateconf key. i.e.
 * we are conceptually "configuring" the label widgets
 */
static void
configurable_widget_config_notify (MateConfClient *client,
                                   guint        cnxn_id,
                                   MateConfEntry  *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
   * mateconftool --break-key
   */
  
  if (mateconf_entry_get_value (entry) == NULL)
    {
      gtk_label_set_text (GTK_LABEL (label), "");
    }
  else if (mateconf_entry_get_value (entry)->type == MATECONF_VALUE_STRING)
    {
      gtk_label_set_text (GTK_LABEL (label),
                          mateconf_value_get_string (mateconf_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 (MateConfClient *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 = mateconf_client_get_string (client, config_key, NULL);

  if (str != NULL)
    {
      gtk_label_set_text (GTK_LABEL (label), str);
      g_free (str);
    }

  notify_id = mateconf_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;
  MateConfClient *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 (MateConfClient *client)
{
  GtkWidget *w;
  GtkWidget *vbox;
  GtkWidget *config;
  GtkWidget *prefs;
  
  w = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  gtk_window_set_title (GTK_WINDOW (w), "basic-mateconf-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-mateconf-app/foo");
  gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);

  config = create_configurable_widget (client, "/apps/basic-mateconf-app/bar");
  gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);
  
  config = create_configurable_widget (client, "/apps/basic-mateconf-app/baz");
  gtk_box_pack_start (GTK_BOX (vbox), config, TRUE, TRUE, 0);

  config = create_configurable_widget (client, "/apps/basic-mateconf-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 MateConf database. It never does something like change
 * the main window directly; it ONLY changes MateConf keys via
 * MateConfClient. 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 MATE 1, see the
 * complex-mateconf-app.c example. But don't actually copy that example
 * in MATE 2, thanks. ;-) complex-mateconf-app.c does show how
 * to use MateConfChangeSet.
 */

/* Commit changes to the MateConf database. */
static gboolean
config_entry_commit (GtkWidget *entry, GdkEvent *event, gpointer callback_data)
{
  gchar *text;
  const gchar *key;
  MateConfClient *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')
    mateconf_client_set_string (client, key, text, NULL);
  else
    mateconf_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,
                     MateConfClient *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 = mateconf_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,
                            mateconf_client_key_is_writable (client,
                                                          config_key, NULL));

  if (focus)
    gtk_widget_grab_focus (entry);
  
  return hbox;
}

static GtkWidget*
create_prefs_dialog (GtkWidget   *parent,
                     MateConfClient *client)
{
  GtkWidget* dialog;
  GtkWidget* vbox;
  GtkWidget* entry;
  
  dialog = gtk_dialog_new_with_buttons ("basic-mateconf-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_get_content_area(dialog)),
                      vbox, TRUE, TRUE, 0);

  entry = create_config_entry (dialog, client, "/apps/basic-mateconf-app/foo",
                               TRUE);
  gtk_box_pack_start (GTK_BOX (vbox), entry, 
                      FALSE, FALSE, 0);
  
  entry = create_config_entry (dialog, client, "/apps/basic-mateconf-app/bar",
                               FALSE);
  gtk_box_pack_start (GTK_BOX (vbox), entry, 
                      FALSE, FALSE, 0);
  
  entry = create_config_entry (dialog, client, "/apps/basic-mateconf-app/baz",
                               FALSE);
  gtk_box_pack_start (GTK_BOX (vbox), entry, 
                      FALSE, FALSE, 0);

  entry = create_config_entry (dialog, client, "/apps/basic-mateconf-app/blah",
                               FALSE);
  gtk_box_pack_start (GTK_BOX (vbox), entry, 
                      FALSE, FALSE, 0);
  
  return dialog;
}
