/* 
 * gnc-tree-view.c -- new GtkTreeView with extra features used by 
 *                    all the tree views in gnucash
 *
 * Copyright (C) 2003,2005 David Hampton <hampton@employees.org>
 *
 * 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 of
 * the License, 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, contact:
 *
 * Free Software Foundation           Voice:  +1-617-542-5942
 * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652
 * Boston, MA  02111-1307,  USA       gnu@gnu.org
 */

/** @addtogroup GUI
    @{ */
/** @addtogroup GncTreeView
 * @{ */
/** @file gnc-tree-view.c
    @brief Base GncTreeView implementation for gnucash trees.
    @author David Hampton <hampton@employees.org>
*/

#include "config.h"

#include <gtk/gtk.h>
#include <libgnome/libgnome.h>
#include <string.h>

#include "gnc-tree-view.h"
#include "gnc-engine.h"
#include "gnc-gconf-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-gobject-utils.h"

/* The column id refers to a specific column in the tree model.  It is
 * also attached to the side of the tree column to allow lookup of a
 * GtkTreeViewColumn when passed a column id from the underlying
 * model. By convention, negative column numbers are used when the
 * visible column has no association with the underlying model.*/
#define MODEL_COLUMN "model_column"

/* A column with this attribute cannot be hidden from view. */
#define ALWAYS_VISIBLE  "always-visible"

/* For checkbox columns, this contains the real title for the column. */
#define REAL_TITLE  "real_title"

/* The name of this column as it should appear in gconf.  This is
 * attached to the column when it is created, and used to map back and
 * forth to gconf keys.  The actual gconf keys are built from these
 * strings. */
#define PREF_NAME  "pref-name"

/* The actual gconf key for a particular column visibility.  This is
 * attached to the menu items that are in the column selection menu.
 * Makes it very easy to update gconf when a menu item is toggled. */
#define GCONF_KEY  "gconf-key"

/* Gconf keys within this particular section of gconf. */
#define GCONF_KEY_SORT_COLUMN  "sort_column"
#define GCONF_KEY_SORT_ORDER   "sort_order"
#define GCONF_KEY_COLUMN_ORDER "column_order"

/* Partial gconf keys within this particular section of gconf. These
   are appended to the various column names to create the actual
   keys. */
#define GCONF_KEY_VISIBLE      "visible"
#define GCONF_KEY_WIDTH        "width"

enum {
  PROP_0,
  PROP_GCONF_SECTION,
  PROP_SHOW_COLUMN_MENU,
};

/** Static Globals *******************************************************/

/* This static indicates the debugging module that this .o belongs to.  */
static QofLogModule log_module = GNC_MOD_GUI;

/**** Declarations ******************************************************/
static void gnc_tree_view_class_init (GncTreeViewClass *klass);
static void gnc_tree_view_init (GncTreeView *view, GncTreeViewClass *klass);
static void gnc_tree_view_finalize (GObject *object);
static void gnc_tree_view_destroy (GtkObject *object);
static void gnc_tree_view_set_property (GObject         *object,
					guint            prop_id,
					const GValue    *value,
					GParamSpec      *pspec);
static void gnc_tree_view_get_property (GObject         *object,
					guint            prop_id,
					GValue          *value,
					GParamSpec      *pspec);
static void gnc_tree_view_remove_gconf(GncTreeView *view);
static gboolean gnc_tree_view_drop_ok_cb (GtkTreeView *view,
					  GtkTreeViewColumn *column,
					  GtkTreeViewColumn *prev_column,
					  GtkTreeViewColumn *next_column,
					  gpointer data);
static void gtk_tree_view_sort_column_changed_cb (GtkTreeSortable *treesortable,
						  GncTreeView *view);
static void gnc_tree_view_build_column_menu (GncTreeView *view);
static void gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
					    GncTreeView *view);

/** Private Data Structure ***********************************************/

struct GncTreeViewPrivate
{
  GtkTooltips       *title_tips;
  GSList            *default_visible;

  /* Column selection menu related values */
  GtkTreeViewColumn *column_menu_column;
  GtkWidget         *column_menu;
  gboolean           show_column_menu;

  /* Gconf related values */
  gchar             *gconf_section;
  gboolean           seen_gconf_visibility;
  gulong             columns_changed_cb_id;
  gulong             sort_column_changed_cb_id;
  gulong             size_allocate_cb_id;
};


/************************************************************/
/*               g_object required functions                */
/************************************************************/

/** @name Gnc Tree View Object Implementation
 @{ */

static GObjectClass *parent_class = NULL;

/** Create a new glib type for the base gnucash tree view.
 *
 *  @internal
 *
 *  @return The new type value.
 */
GType
gnc_tree_view_get_type (void)
{
	static GType gnc_tree_view_type = 0;

	if (gnc_tree_view_type == 0) {
		static const GTypeInfo our_info = {
			sizeof (GncTreeViewClass),
			NULL,
			NULL,
			(GClassInitFunc) gnc_tree_view_class_init,
			NULL,
			NULL,
			sizeof (GncTreeView),
			0,
			(GInstanceInitFunc) gnc_tree_view_init
		};
		
		gnc_tree_view_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
							     GNC_TREE_VIEW_NAME,
							     &our_info, 0);
	}

	return gnc_tree_view_type;
}

/** Initialize the class for the new base gnucash tree view.  This
 *  will set up any function pointers that override functions in the
 *  parent class, and also installs the proprieties that are unique to
 *  this class.
 *
 *  @param klass The new class structure created by the object system.
 *
 *  @internal
 */
static void
gnc_tree_view_class_init (GncTreeViewClass *klass)
{
	GObjectClass *gobject_class;
	GtkObjectClass *gtkobject_class;

	parent_class = g_type_class_peek_parent (klass);

	gobject_class = G_OBJECT_CLASS (klass);
	gtkobject_class = GTK_OBJECT_CLASS (klass);

	gobject_class->set_property = gnc_tree_view_set_property;
	gobject_class->get_property = gnc_tree_view_get_property;

        g_object_class_install_property (gobject_class,
					 PROP_GCONF_SECTION,
					 g_param_spec_string ("gconf-section",
							      "Gconf Section",
							      "The Gconf section to use for storing settings",
							      NULL,
							      G_PARAM_READWRITE));
        g_object_class_install_property (gobject_class,
					 PROP_SHOW_COLUMN_MENU,
					 g_param_spec_boolean ("show-column-menu",
							       "Show Column Menu",
							       "Show the column menu so user can change what columns are visible.",
							       FALSE,
							       G_PARAM_READWRITE));
  
	/* GObject signals */
	gobject_class->finalize = gnc_tree_view_finalize;

	/* GtkObject signals */
	gtkobject_class->destroy = gnc_tree_view_destroy;
}

/** Initialize a new instance of a base gnucash tree view.  This
 *  function allocates and initializes the object private storage
 *  space.  It also adds the new object to a list (for memory tracking
 *  purposes), and sets up a callback for the column drag function.
 *
 *  @param view The new object instance created by the object system.
 *
 *  @internal
 */
static void
gnc_tree_view_init (GncTreeView *view, GncTreeViewClass *klass)
{
  GtkTreeViewColumn *column;
  GtkWidget *icon;
  GtkRequisition requisition;

  gnc_gobject_tracking_remember(G_OBJECT(view), G_OBJECT_CLASS(klass));

  view->priv = g_new0 (GncTreeViewPrivate, 1);

  view->priv->default_visible = NULL;
  view->priv->column_menu = NULL;
  view->priv->show_column_menu = FALSE;
  view->priv->gconf_section = NULL;
  view->priv->seen_gconf_visibility = FALSE;
  view->priv->columns_changed_cb_id = 0;
  view->priv->sort_column_changed_cb_id = 0;
  view->priv->size_allocate_cb_id = 0;

  /* Ask gtk to help the user keep track of rows. */
  g_object_set(view, "rules-hint", TRUE, NULL);

  /* Handle column drag and drop */
  gtk_tree_view_set_column_drag_function(GTK_TREE_VIEW(view),
					 gnc_tree_view_drop_ok_cb, NULL, NULL);

  /* Create the next to last column which is always present, visible,
   * and empty. Override the defaults and make this a one pixel wide
   * column, but have it take up any extra space in the window. */
  column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
					  -1, -1, NULL);
  gtk_object_set(GTK_OBJECT(column),
		 "fixed-width", 1,
		 "expand", TRUE, NULL);

  /* Create the last column which contains the column selection
   * widget.  gnc_tree_view_add_text_column will do most of the
   * work. */
  icon = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN,
				  GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_widget_show(icon);
  gtk_widget_size_request(icon, &requisition);
  column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
					  -1, -1, NULL);
  gtk_object_set(GTK_OBJECT(column),
		 "clickable", TRUE,
		 "widget", icon,
		 "fixed-width", requisition.width + 10,
		 NULL);
  g_signal_connect(G_OBJECT(column), "clicked",
		   G_CALLBACK (gnc_tree_view_select_column_cb),
		   view);
  view->priv->column_menu_column = column;
}

/** Finalize the GncTreeView object.  This function is called from the
 *  G_Object level to complete the destruction of the object.  It
 *  should release any memory not previously released by the destroy
 *  function (i.e. the private data structure), then chain up to the
 *  parent's destroy function.
 *
 *  @param object The object being destroyed.
 *
 *  @internal
 */
static void
gnc_tree_view_finalize (GObject *object)
{
  GncTreeView *view;

  ENTER("view %p", object);
  g_return_if_fail (object != NULL);
  g_return_if_fail (GNC_IS_TREE_VIEW (object));

  gnc_gobject_tracking_forget(object);

  view = GNC_TREE_VIEW (object);
  g_free (view->priv);

  if (G_OBJECT_CLASS (parent_class)->finalize)
    (* G_OBJECT_CLASS (parent_class)->finalize) (object);
  LEAVE(" ");
}

/** Destroy the GncTreeView object.  This function is called (possibly
 *  multiple times) from the Gtk_Object level to destroy the object.
 *  It should release any memory owned by the object that isn't
 *  fundamental to the implementation.  In this function any active
 *  callbacks are disconnected, all memory other than the private data
 *  structure are freed.
 *
 *  @param object The object being destroyed.
 *
 *  @internal
 */
static void
gnc_tree_view_destroy (GtkObject *object)
{
  GncTreeView *view;
  GSList *slist;

  ENTER("view %p", object);
  g_return_if_fail (object != NULL);
  g_return_if_fail (GNC_IS_TREE_VIEW (object));

  view = GNC_TREE_VIEW (object);

  gnc_tree_view_remove_gconf(view);

  if (view->priv->default_visible) {
    slist = view->priv->default_visible;
    view->priv->default_visible = NULL;
    g_slist_foreach(slist, (GFunc)g_free, NULL);
    g_slist_free(slist);
  }

  if (view->priv->column_menu) {
    DEBUG("removing column selection menu");
    gtk_widget_unref(view->priv->column_menu);
    view->priv->column_menu = NULL;
  }

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
  LEAVE(" ");
}

/** @} */

/************************************************************/
/*                g_object other functions                  */
/************************************************************/

/** @name Gnc Tree View Object Implementation
 @{ */

/** Retrieve a property specific to this GncTreeView object.  This is
 *  nothing more than a dispatch function for routines that can be
 *  called directly.  It has the nice feature of allowing a single
 *  function call to retrieve multiple properties.
 *
 *  @internal
 */
static void
gnc_tree_view_get_property (GObject     *object,
			    guint        prop_id,
			    GValue      *value,
			    GParamSpec  *pspec)
{
  GncTreeView *view = GNC_TREE_VIEW (object);
  
  switch (prop_id)
    {
    case PROP_GCONF_SECTION:
      g_value_set_string (value, view->priv->gconf_section);
      break;
    case PROP_SHOW_COLUMN_MENU:
      g_value_set_boolean (value, view->priv->show_column_menu);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}


/** Set a property specific to this GncTreeView object.  This is
 *  nothing more than a dispatch function for routines that can be
 *  called directly.  It has the nice feature of allowing a new view
 *  to be created with a varargs list specifying the properties,
 *  instead of having to explicitly call each property function.
 *
 *  @internal
 */
static void
gnc_tree_view_set_property (GObject      *object,
			    guint         prop_id,
			    const GValue *value,
			    GParamSpec   *pspec)
{
  GncTreeView *view = GNC_TREE_VIEW (object);
  
  switch (prop_id)
    {
    case PROP_GCONF_SECTION:
      gnc_tree_view_set_gconf_section (view, g_value_get_string (value));
      break;
    case PROP_SHOW_COLUMN_MENU:
      gnc_tree_view_set_show_column_menu (view, g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

/** @} */

/************************************************************/
/*                   Auxiliary Functions                    */
/************************************************************/

/** @name Gnc Tree View Auxiliary Functions
 @{ */

/** Find a tree column given a column id number from the underlying
 *  data model.  This function should only be called by code that has
 *  visibility into the data model.  The column id numbers shouldn't
 *  be used for any other purpose.
 *
 *  This function simply runs the list of all (visible and invisible)
 *  columns looking for a match.  Column id numbers were attached to
 *  each column at the time the column was created.
 *
 *  @param view The visible tree widget.
 *
 *  @param wanted The column id number to find.
 *
 *  @internal
 */
static GtkTreeViewColumn *
view_column_find_by_model_id (GncTreeView *view,
			      const gint wanted)
{
  GtkTreeViewColumn *column, *found = NULL;
  GList *column_list, *tmp;
  gint id;

  // ENTER("view %p, name %s", view, name);
  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  for (tmp = column_list; tmp; tmp = g_list_next(tmp)) {
    column = tmp->data;
    id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), MODEL_COLUMN));
    if (id != wanted)
      continue;
    found = column;
    break;
  }
  g_list_free(column_list);

  // LEAVE("column %p", found);
  return found;
}

/** Find a tree column given the "pref name" used with gconf.  This
 *  function simply runs the list of all (visible and invisible)
 *  columns looking for a match.  Column names were attached to each
 *  column at the time the column was created.
 *
 *  @param view The visible tree widget.
 *
 *  @param id The "pref name" to find.
 *
 *  @internal
 */
static GtkTreeViewColumn *
view_column_find_by_name (GncTreeView *view,
			  const gchar *wanted)
{
  GtkTreeViewColumn *column, *found = NULL;
  GList *column_list, *tmp;
  const gchar *name;

  // ENTER("view %p, wanted %s", view, wanted);
  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  for (tmp = column_list; tmp; tmp = g_list_next(tmp)) {
    column = tmp->data;
    name = g_object_get_data(G_OBJECT(column), PREF_NAME);
    if (!name || (strcmp(name, wanted) != 0))
      continue;
    found = column;
    break;
  }
  g_list_free(column_list);

  // LEAVE("column %p", found);
  return found;
}

/** @} */

/************************************************************/
/*                     Tree Callbacks                       */
/************************************************************/

/** @name Gnc Tree View Callback Functions
 @{ */

/** This function is called to determine whether it is acceptable to
 *  drop a dragged tree column at a given location.  This function
 *  will be called multiple times by the GtkTreeView code while the
 *  user is dragging a column.  Each call is a check to see if the
 *  proposed location is an acceptable place to drop the column.  In
 *  the case of a GncTreeView, the only drop locations that are
 *  prohibited are on the right side of the visible tree.
 *
 *  @param view The visible tree widget.
 *
 *  @param column The column being dragged.
 *
 *  @param prev_column The column before (left) of the proposed drop
 *  location.
 *
 *  @param next_column The column after (right) of the proposed drop
 *  location.
 *
 *  @param data Unused.
 *
 *  @return TRUE if this drop location is acceptable. FALSE if not
 *  acceptable.
 *
 *  @internal
 */
static gboolean
gnc_tree_view_drop_ok_cb (GtkTreeView *view,
			  GtkTreeViewColumn *column,
			  GtkTreeViewColumn *prev_column,
			  GtkTreeViewColumn *next_column,
			  gpointer data)
{
  const gchar *pref_name;

  /* Should we allow a drop at the left side of the tree view before
   * the widget to open a new display level?  I can think of cases
   * where the user might want to do this with a checkbox column. */
  if (prev_column == NULL)
    return TRUE;

  /* Do not allow a drop at the right side of the tree view after the
   * column selection widget.  */
  if (next_column == NULL)
    return FALSE;

  /* Columns without pref names are considered fixed at the right hand
   * side of the view.  At the time of this writing, the only two are
   * the column where the "column selection widget" is stored, and the
   * "padding" column to the left of that where extra view space ends
   * up. */
  pref_name = g_object_get_data(G_OBJECT(prev_column), PREF_NAME);
  if (!pref_name)
    return FALSE;

  /* Everything else is allowed. */
  return TRUE;
}

/** This function is called when the sort order has changed on the
 *  underlying GtkTreeModel.  It propagates these changes to gconf to
 *  keep it in sync with the user's view of the tree.
 *
 *  @param sortable The underlying sortable model.
 *
 *  @param view A pointer to the GncTreeView that displays the model
 *  data.
 *
 *  @internal
 */
static void
gtk_tree_view_sort_column_changed_cb (GtkTreeSortable *treesortable,
				      GncTreeView *view)
{
  GtkTreeViewColumn *column;
  const gchar  	    *gconf_section;
  gchar        	    *column_pref_name;
  GtkSortType  	     order;
  gint         	     id;

  g_return_if_fail(GTK_IS_TREE_SORTABLE(treesortable));
  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER(" ");
  if (!view->priv->gconf_section) {
    LEAVE("no gconf section");
    return;
  }

  /* Set defaults, then extract data from the model */
  if (!gtk_tree_sortable_get_sort_column_id(treesortable, &id, &order)) {
    order = GTK_SORT_ASCENDING;
    id = 0;
  }
  column = view_column_find_by_model_id (view, id);
  column_pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);

  /* Store the values in gconf */
  gconf_section = view->priv->gconf_section;
  gnc_gconf_set_string(gconf_section, "sort_column", column_pref_name, NULL);
  gnc_gconf_set_string(gconf_section, "sort_order",
		       gnc_enum_to_nick(GTK_TYPE_SORT_TYPE, order), NULL);
  LEAVE(" ");
}

/** This function is called when the column order has changed in the
 *  gtk tree view.  It propagates these changes to gconf to keep it in
 *  sync with the user's view of the tree.  This function runs the
 *  list of all tree column data structures, building a second list of
 *  column names.  This second list is passed to gconf for storing.
 *  Only columns that have names can be saved in this fashion.
 *
 *  @param view The tree view.
 *
 *  @param data Unused.
 *
 *  @internal
 */
static void
gtk_tree_view_columns_changed_cb (GncTreeView *view,
				  gpointer data)
{
  GList *column_list, *tmp;
  GSList *column_names = NULL;
  gchar *name;

  //ENTER("view %p, data %p", view, data);
  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  for (tmp = column_list; tmp; tmp = g_list_next(tmp)) {
    name = g_object_get_data(tmp->data, PREF_NAME);
    if (!name)
      continue;
    column_names = g_slist_append(column_names, name);
    //DEBUG("%s", name);
  }
  g_list_free(column_list);

  gnc_gconf_set_list(view->priv->gconf_section, GCONF_KEY_COLUMN_ORDER,
		     GCONF_VALUE_STRING, column_names, NULL);
  g_slist_free(column_names);
  //LEAVE(" ");
}

/** This is the helper function for gtk_tree_view_size_allocate_cb().
 *  It compares the actual column width to the width as stored in
 *  gconf.  If the two are different, it the updates gconf with the
 *  actual width.  This will trigger a notification from gconf that
 *  the size has changes, and that code must be smart enough to
 *  prevent an infinite loop.  Not storing unchanged values prevents
 *  spurious callbacks from gconf and just saves processing time.
 *
 *  @param column The column whose size should be updated in gconf..
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gtk_tree_view_size_allocate_helper (GtkTreeViewColumn *column,
				    GncTreeView *view)
{
  const gchar *column_pref_name;
  gint new_width, current_width;
  gchar *key;

  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  /* Any updates to be made? */
  column_pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
  if (!column_pref_name)
    return;
  new_width = gtk_tree_view_column_get_width (column);
  if (!new_width)
    return;

  /* Do it */
  key = g_strdup_printf("%s_%s", column_pref_name, GCONF_KEY_WIDTH);
  current_width = gnc_gconf_get_int(view->priv->gconf_section, key, NULL);
  if (new_width != current_width) {
    gnc_gconf_set_int(view->priv->gconf_section, key, new_width, NULL);
    DEBUG("set %s width to %d", column_pref_name, new_width);
  }
  g_free(key);
}

/** This function is called when the tree size has been reallocated.
 *  When called, one of two events has occurred.  1) The column sizes
 *  within the tree have changed.  2) The window containing the tree
 *  has been resized.  This code is concerned with case one.  Case two
 *  is a don't care, unless the window resizing has caused a column
 *  resizing.
 *
 *  This function simply runs the list of all tree columns, calling the
 *  gtk_tree_view_size_allocate_helper() function on each column.
 *  There is no callback that can be used for notification of an
 *  individual column change.  Its this or nothing.
 *
 *  @param widget The tree view.
 *
 *  @param allocation Unused.
 *
 *  @param data Unused.
 *
 *  @internal
 */
static void
gtk_tree_view_size_allocate_cb (GtkWidget *widget,
				GtkAllocation *allocation,
				gpointer data)
{
  GncTreeView *view;
  GList *column_list;

  g_return_if_fail(GNC_IS_TREE_VIEW(widget));

  view = GNC_TREE_VIEW(widget);
  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  g_list_foreach(column_list, (GFunc)gtk_tree_view_size_allocate_helper, view);
  g_list_free(column_list);
}

/** @} */

/************************************************************/
/*                  Gconf Setup / Callbacks                 */
/************************************************************/

/** @name Gnc Tree View Gconf Callback / Related Functions
 @{ */

/** Determine the visibility of a column.  This function first looks
 *  for columns specially marked to be always visible, or columns
 *  without a preference name.  These are always show.  Next this
 *  function checks to see if gconf is responsible for this view *and*
 *  that gconf visibility keys have been seen.  (This handles the
 *  'first run' case where gconf should manage a view but no keys yet
 *  exist in gconf.)  If so, the gconf visibility key is returned.
 *  Otherwise the "'default visible column' list is checked and a
 *  value of TRUE returned if the pref name is found.
 *
 *  @param view A GncTreeView.
 *
 *  @param column The GtkTreeViewColumn in question.  Either this
 *  value or the pref_name parameter must be non-NULL.
 *
 *  @param pref_name The name of the column in question.  Either this
 *  value or the column parameter must be non-NULL.
 *
 *  @returns TRUE if the column should be visible.  FALSE otherwise.
 *
 *  @internal
 */
static gboolean
gnc_tree_view_column_visible (GncTreeView *view,
			      GtkTreeViewColumn *column,
			      const gchar *pref_name)
{
  gboolean visible;
  gchar *key;

  ENTER("column %p, name %s", column, pref_name);
  if (column) {
    if (g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE)) {
      LEAVE("1, first column");
      return TRUE;
    }
    pref_name = (gchar *)g_object_get_data(G_OBJECT(column), PREF_NAME);
    DEBUG("pref_name is %s", pref_name);
  }

  if (!pref_name) {
    LEAVE("1, no pref name");
    return TRUE;
  }
  
  /* Using gconf? */
  if (view->priv->gconf_section) {
    if (view->priv->seen_gconf_visibility) {
      key = g_strdup_printf("%s_%s", pref_name, GCONF_KEY_VISIBLE);
      visible = gnc_gconf_get_bool(view->priv->gconf_section, key, NULL);
      g_free(key);
      LEAVE("%d, gconf visibility", visible);
      return visible;
    }

    visible =  (g_slist_find_custom(view->priv->default_visible, pref_name,
				    (GCompareFunc)strcmp) != NULL);
    LEAVE("%d, gconf but using defaults", visible);
    return visible;
  }

  /* Check the default columns list */
  visible = (g_slist_find_custom(view->priv->default_visible, pref_name,
				 (GCompareFunc)strcmp) != NULL);
  LEAVE("defaults says %d", visible);
  return visible;
}

/** This function updates the visibility of a single column.  It
 *  checks if the column should be visible, and if so tells the view
 *  to show the column and (f needed) updates the gconf database.
 *
 *  @param column The column whose visibility should be updated.
 *
 *  @param view The GncTreeView containing the column.
 *
 *  @internal
 */
static void
gnc_tree_view_update_visibility (GtkTreeViewColumn *column,
				 GncTreeView *view)
{
  gchar *name, *key;
  gboolean visible;

  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER(" ");
  visible = gnc_tree_view_column_visible(view, column, NULL);
  gtk_tree_view_column_set_visible(column, visible);
  if (view->priv->gconf_section) {
    name = (gchar *)g_object_get_data(G_OBJECT(column), PREF_NAME);
    if (!name) {
      LEAVE("no pref name");
      return;
    }
    key = g_strdup_printf("%s_%s", name, GCONF_KEY_VISIBLE);
    gnc_gconf_set_bool(view->priv->gconf_section, key, visible, NULL);
    g_free(key);
    LEAVE("made visible, set gconf key");
    return;
  }
  LEAVE("made visible");
}

/** This function is called via the gconf notification callback when
 *  the sort order has changed in gconf.  It must update the model
 *  sort order to match what is specified in gconf.  The sort column
 *  used will be whatever is the current sort column.  This function
 *  is careful to prevent an infinite loop by blocking the
 *  "sort-column-changed" callback that is attached to the model.
 *
 *  @param widget The tree view.
 *
 *  @param name The sort order enum (in string form). Either
 *  "ascending" or "descending".
 *
 *  @internal
 */
static void
gnc_tree_view_set_sort_order (GncTreeView *view,
			      const gchar *name)
{
  GtkTreeModel *s_model;
  GtkSortType order;
  gint current;

  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  if (!s_model)
    return;
  order = gnc_enum_from_nick(GTK_TYPE_SORT_TYPE, name, GTK_SORT_ASCENDING);
  if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
					    &current, &order))
    current = 0;
  g_signal_handler_block(s_model, view->priv->sort_column_changed_cb_id);
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
				       current, order);
  g_signal_handler_unblock(s_model, view->priv->sort_column_changed_cb_id);
  DEBUG("sort_order set to %s", gnc_enum_to_nick(GTK_TYPE_SORT_TYPE, order));
}

/** This function is called via the gconf notification callback when
 *  the sort column has changed in gconf.  It must update the model
 *  sort column to match what is specified in gconf.  The sort order
 *  used for this column will be whatever sort order is in use on the
 *  current sort column.  This function is careful to prevent an
 *  infinite loop by blocking the "sort-column-changed" callback that
 *  is attached to the model.
 *
 *  @param widget The tree view.
 *
 *  @param name The name of the column that should be made the sort column.
 *
 *  @internal
 */
static void
gnc_tree_view_set_sort_column (GncTreeView *view,
			       const gchar *name)
{
  GtkTreeModel *s_model;
  GtkTreeViewColumn *column;
  GtkSortType order;
  gint model_column, current;

  column = view_column_find_by_name(view, name);
  if (!column)
    return;

  model_column =
    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), MODEL_COLUMN));
  if (model_column == GNC_TREE_VIEW_COLUMN_DATA_NONE)
    return;

  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
					    &current, &order))
    order = GTK_SORT_ASCENDING;
  g_signal_handler_block(s_model, view->priv->sort_column_changed_cb_id);
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
				       model_column, order);
  g_signal_handler_unblock(s_model, view->priv->sort_column_changed_cb_id);
  DEBUG("sort column set to %s", name);
}

/** This function is called via the gconf notification callback when
 *  the column order has changed in gconf.  It must update the order
 *  of the columns in the model to match what is specified in gconf.
 *  This function is careful to prevent an infinite loop by blocking
 *  the "columns-changed" callback that is attached to the view.
 *
 *  @param widget The tree view.
 *
 *  @param name A list of pointers to strings.  These strings are the
 *  names of the columns in the order they should appear.
 *
 *  @internal
 */
static void
gnc_tree_view_set_column_order (GncTreeView *view,
				const GSList *column_names)
{
  GtkTreeViewColumn *column, *prev;
  const gchar *name;
  const GSList *tmp;
  GSList *columns;

  /* First, convert from names to pointers */
  ENTER(" ");
  columns = NULL;
  for (tmp = column_names; tmp; tmp = g_slist_next(tmp)) {
    name = gconf_value_get_string(tmp->data);
    column = view_column_find_by_name(view, name);
    if (!column)
      continue;
    columns = g_slist_append(columns, column);
  }
    
  /* Then reorder the columns */
  g_signal_handler_block(view, view->priv->columns_changed_cb_id);
  for (prev = NULL, tmp = columns; tmp; tmp = g_slist_next(tmp)) {
    column = tmp->data;
    gtk_tree_view_move_column_after(GTK_TREE_VIEW(view), column, prev);
    prev = column;
  }
  g_signal_handler_unblock(view, view->priv->columns_changed_cb_id);

  /* Clean up */
  g_slist_free(columns);
  LEAVE("column order set");
}

/** This function is called via the gconf notification callback when
 *  any change has occurred.  It must update the appropriate aspect of
 *  the model/view to match what is specified in gconf.  Some of the
 *  updates are handled in this function, while others are handled by
 *  other helper functions.
 *
 *  @param client A pointer to the gconf client object from which
 *  detected a change in gconf.  Unused by this function.
 *
 *  @cnxn_id The identifier for the notification that this callback
 *  represents.  Unused since this code only has a single notification
 *  request.
 *
 *  @entry A pointer to the key/value pair in gconf that changed.
 *
 *  @param data The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_gconf_changed (GConfClient *client,
			     guint cnxn_id,
			     GConfEntry *entry,
			     gpointer data)
{
  GncTreeView *view;
  GtkTreeViewColumn *column;
  GConfValue *value;
  const gchar *key, *local;
  gchar *column_name, *type_name;
  gboolean known;
  gint width;

  g_return_if_fail(GNC_IS_TREE_VIEW(data));

  ENTER(" ");
  view = GNC_TREE_VIEW(data);
  key = gconf_entry_get_key(entry);
  value = gconf_entry_get_value(entry);
  if (!value) {
    /* Values can be unset */
    LEAVE("Unset valued for %s", key);
    return;
  }

  DEBUG("Key %s, value %p", key, value);
  local = rindex(key, '/')+1;
  if (strcmp(local, GCONF_KEY_SORT_COLUMN) == 0) {
    gnc_tree_view_set_sort_column(view, gconf_value_get_string(value));
  } else if (strcmp(local, GCONF_KEY_SORT_ORDER) == 0) {
    gnc_tree_view_set_sort_order(view, gconf_value_get_string(value));
  } else if (strcmp(local, GCONF_KEY_COLUMN_ORDER) == 0) {
    gnc_tree_view_set_column_order(view, gconf_value_get_list(value));
  } else {
    /* Make a copy of the local part of the key so it can be split
     * into column name and key type */
    known = FALSE;
    column_name = strdup(local);
    type_name = rindex(column_name, '_');
    *type_name++ = '\0';

    if (strcmp(type_name, GCONF_KEY_VISIBLE) == 0) {
      view->priv->seen_gconf_visibility = TRUE;
      column = view_column_find_by_name(view, column_name);
      if (column) {
	known = TRUE;
	if (!g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE)) {
	  gtk_tree_view_column_set_visible(column, gconf_value_get_bool(value));
	}
      }
    } else if (strcmp(type_name, GCONF_KEY_WIDTH) == 0) {
      width = gconf_value_get_int(value);
      column = view_column_find_by_name(view, column_name);
      if (column) {
	known = TRUE;
	if (width && (width != gtk_tree_view_column_get_width(column))) {
	  gtk_tree_view_column_set_fixed_width(column, width);
	}
      }
    }
    if (!known) {
      DEBUG("Ignored key %s", key);
    }
    g_free(column_name);
  }
  LEAVE(" ");
}

/** This function is called force synchronization between the gconf
 *  database and a tree view.  It will be called for major events like
 *  assigning a gconf section to a tree view.  All values in gconf
 *  will be propagated into the view.  This function simply requests a
 *  list of all key in the section from gconf, then feeds them one at
 *  a time to gnc_tree_view_gconf_changed().
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_gconf_force_update (GncTreeView *view)
{
  GSList *all_entries, *etmp;
  GList *columns;

  ENTER("view %p", view);
  all_entries = gnc_gconf_client_all_entries(view->priv->gconf_section);

  /* Set a flag indicating that the gconf data section may be empty.
   * It will be checked later on and appropriate action taken if its
   * still set. */
  view->priv->seen_gconf_visibility = FALSE;

  /* Pull all the entries from gconf */
  for (etmp = all_entries; etmp; etmp = g_slist_next(etmp)) {
    gnc_tree_view_gconf_changed(NULL, 0, etmp->data, view);
    gconf_entry_free(etmp->data);
  }
  g_slist_free(all_entries);

  /* No visibilities seen yet.  Write out any columns we may have */
  if (!view->priv->seen_gconf_visibility) {
    columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
    g_list_foreach(columns, (GFunc)gnc_tree_view_update_visibility, view);
    g_list_free(columns);
  }
  
  LEAVE(" ");
}

/** This internal function is called to disconnect a tree view from a
 *  gconf section.  It first disconnects any signals that are in
 *  effect on the model/view, then removes the gconf notification
 *  callback, then clears the gconf section string.  This function may
 *  be called at any time; either when the user wants to disconnect or
 *  when the view object is being destroyed.
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_remove_gconf(GncTreeView *view)
{
  GtkTreeModel *model;

  ENTER(" ");
  if (!view->priv->gconf_section) {
    LEAVE("no gconf section");
    return;
  }

  if (view->priv->sort_column_changed_cb_id) {
    model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
    if (model) {
      DEBUG("removing sort_column_changed callback (# %ld)",
	    view->priv->sort_column_changed_cb_id);
      g_signal_handler_disconnect(GTK_TREE_SORTABLE(model),
				  view->priv->sort_column_changed_cb_id);
      view->priv->sort_column_changed_cb_id = 0;
    }
  }

  if (view->priv->columns_changed_cb_id) {
    DEBUG("removing columns_changed callback (# %ld)",
	  view->priv->columns_changed_cb_id);
    g_signal_handler_disconnect(view, view->priv->columns_changed_cb_id);
    view->priv->columns_changed_cb_id = 0;
  }

  if (view->priv->size_allocate_cb_id) {
    DEBUG("removing size_allocate callback (# %ld)",
	  view->priv->size_allocate_cb_id);
    g_signal_handler_disconnect(view, view->priv->size_allocate_cb_id);
    view->priv->size_allocate_cb_id = 0;
  }

  DEBUG("removing gconf notification");
  gnc_gconf_remove_notification(G_OBJECT(view), view->priv->gconf_section);
  g_free(view->priv->gconf_section);
  view->priv->gconf_section = NULL;
  LEAVE(" ");
}

/** This function is called to set up or remove an association between
 *  a gconf section and the display of a view.  It will first remove
 *  any existing association, and then install the new one.  This
 *  involves storing the gconf section value, requesting notification
 *  from gconf of any changes to keys in that section, then attaching
 *  several signals to catch user changes to the view.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
void
gnc_tree_view_set_gconf_section (GncTreeView *view,
				 const gchar *section)
{
  GtkTreeModel *model;
  gulong id;

  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER("view %p, section %s", view, section);
  gnc_tree_view_remove_gconf(view);

  if (!section) {
    LEAVE("cleared gconf section");
    return;
  }

  /* Catch changes in gconf. Propagate to view. */
  view->priv->gconf_section = g_strdup(section);
  gnc_gconf_add_notification(G_OBJECT(view), section,
			     gnc_tree_view_gconf_changed);

  /* Catch changes to the sort column. Propagate to gconf. The key can
   * be set before the model, so the code must handle that case. */
  model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
  if (model)
    view->priv->sort_column_changed_cb_id =
      g_signal_connect(GTK_TREE_SORTABLE(model), "sort-column-changed",
		       (GCallback)gtk_tree_view_sort_column_changed_cb, view);

  /* Catch changes to the column order. Propagate to gconf */
  id = g_signal_connect(view, "columns-changed",
			(GCallback)gtk_tree_view_columns_changed_cb, NULL);
  view->priv->columns_changed_cb_id = id;

  /* Catch changes to the column width. Propagate to gconf */
  id = g_signal_connect(view, "size-allocate",
			(GCallback)gtk_tree_view_size_allocate_cb, NULL);
  view->priv->size_allocate_cb_id = id;

  /* Force an update of the view with all items from gconf. */
  gnc_tree_view_gconf_force_update(view);

  /* Rebuild the column visibility menu */
  gnc_tree_view_build_column_menu(view);
  LEAVE("set gconf section");
}

/** This function is called to get the current association between a
 *  gconf section and the display of a view.  It returns the same
 *  value passed to gnc_tree_view_set_gconf_section(); i.e. a string
 *  like "dialogs/edit_prices".
 *
 *  Parameters are defined in gnc-tree-view.h
 */
const gchar *
gnc_tree_view_get_gconf_section (GncTreeView *view)
{
  g_return_val_if_fail(GNC_IS_TREE_VIEW(view), NULL);

  return(view->priv->gconf_section);
}


/** @} */

/************************************************************/
/*                   Column Selection Menu                  */
/************************************************************/

/** @name Gnc Tree View Column Selection Menu Related Functions
 @{ */

/** This function is called when an entry in the column selection menu
 *  is toggled.  It must update gconf with the current value of the
 *  checkbox.  This will trigger a callback to other functions which
 *  will then change the actual state of the view.
 *
 *  @param checkmenuitem A checkbox visible in the menu.
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_menu_item_toggled (GtkCheckMenuItem *checkmenuitem,
				 GncTreeView *view)
{
  gboolean value;
  gchar *key;

  g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER("checkmenuitem %p, view %p", checkmenuitem, view);
  if (!view->priv->gconf_section) {
    LEAVE("no gconf section");
    return;
  }

  key = g_object_get_data(G_OBJECT(checkmenuitem), GCONF_KEY);
  value = gtk_check_menu_item_get_active(checkmenuitem);
  gnc_gconf_set_bool(view->priv->gconf_section, key, value, NULL);
  LEAVE("set gconf section %s, key %s, visible %d",
	view->priv->gconf_section, key, value);
}

/** This function is called to create a single checkmenuitem in the
 *  column selection menu.  It is called once for each column in the
 *  view.  It creates a menu item for the corresponding column, and
 *  attaches to it a copy of the gconf key for this column's
 *  visibility.  This makes the toggle callback function trivial.
 *
 *  This function will create the column selection menu if one doesn't
 *  already exist.
 *
 *  @param column Create a menu item for this column.
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_create_menu_item (GtkTreeViewColumn *column,
				GncTreeView *view)
{
  GncTreeViewPrivate *priv;
  GtkWidget *widget;
  const gchar *column_name, *pref_name;
  gulong id;
  gchar *key;

  // ENTER("view %p, column %p", view, column);
  priv = view->priv;
  if (!priv->gconf_section) {
    // LEAVE("no gconf section");
    return;
  }

  pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
  if (!pref_name) {
    // LEAVE("column has no pref_name");
    return;
  }

  /* Create the menu if we don't have one already */
  if (!priv->column_menu) {
    priv->column_menu = gtk_menu_new();
    g_object_ref(priv->column_menu);
    gtk_object_sink(GTK_OBJECT(priv->column_menu));
  }

  /* Create the check menu item */
  column_name = g_object_get_data(G_OBJECT(column), REAL_TITLE);
  if (!column_name)
    column_name = gtk_tree_view_column_get_title(column);
  widget = gtk_check_menu_item_new_with_label(column_name);
  gtk_menu_shell_append(GTK_MENU_SHELL(priv->column_menu), widget);

  /* Should never be able to hide the first column */
  if (g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE)) {
    g_object_set_data(G_OBJECT(widget), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
    gtk_widget_set_sensitive(widget, FALSE);
  }

  /* Set up the callback  */
  id = g_signal_connect(widget, "toggled",
			(GCallback)gnc_tree_view_menu_item_toggled, view);

  /* Store data on the widget for callbacks */
  key = g_strdup_printf("%s_%s", pref_name, GCONF_KEY_VISIBLE);
  g_object_set_data_full(G_OBJECT(widget), GCONF_KEY, key, g_free);
  // LEAVE(" ");
}

/** This function is called to build the column selection menu.  It
 *  first destroys any old column selection menu, then checks to see
 *  if a new menu should be built.  If so, it calls the
 *  gnc_tree_view_create_menu_item() for each column in the view.
 *  This function is invoked then either the "gconf-section" or the
 *  "show-column-menu" property is changed on the view.
 *
 *  @param view Build a selection menu for this tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_build_column_menu (GncTreeView *view)
{
  GncTreeViewPrivate *priv;
  GList *column_list;

  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER("view %p", view);
  priv = GNC_TREE_VIEW(view)->priv;

  /* Destroy any old menu */
  if (priv->column_menu) {
    gtk_widget_unref(priv->column_menu);
    priv->column_menu = NULL;
  }

  if (priv->show_column_menu && priv->gconf_section) {
    /* Show the menu popup button */
    if (view->priv->column_menu_column)
      g_object_set(G_OBJECT(view->priv->column_menu_column),
		   "visible", TRUE, NULL);

    /* Now build a new menu */
    column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
    g_list_foreach(column_list, (GFunc)gnc_tree_view_create_menu_item, view);
    g_list_free(column_list);
  } else {
    /* Hide the menu popup button */
    if (view->priv->column_menu_column)
      g_object_set(G_OBJECT(view->priv->column_menu_column),
		   "visible", FALSE, NULL);
  }
  LEAVE("menu: show %d, section %s", priv->show_column_menu,
	priv->gconf_section);
}

/** This function is called to synchronize the checkbox on a menu item
 *  with the current gconf visibility value for the corresponding
 *  column (which will be the visibility of the column if the rest of
 *  the code in this file is working correctly).  It simply takes the
 *  gconf key attached to the menu item, reads it, and sets the menu
 *  item to the retrieved value.  It does take care to block signals
 *  from the menu item to prevent updating gconf (since the value was
 *  just read from gconf).
 *
 *  @param checkmenuitem The menu item to update.
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_update_column_menu_item (GtkCheckMenuItem *checkmenuitem,
				       GncTreeView *view)
{
  gchar *key;
  gboolean visible;

  g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  key = g_object_get_data(G_OBJECT(checkmenuitem), GCONF_KEY);
  if (g_object_get_data(G_OBJECT(checkmenuitem), ALWAYS_VISIBLE)) {
    visible = TRUE;
  } else {
    visible = gnc_gconf_get_bool(view->priv->gconf_section, key, NULL);
  }

  g_signal_handlers_block_by_func(checkmenuitem,
				  gnc_tree_view_menu_item_toggled, view);
  gtk_check_menu_item_set_active(checkmenuitem, visible);
  g_signal_handlers_unblock_by_func(checkmenuitem,
				    gnc_tree_view_menu_item_toggled, view);
}

/** This function when the user clicks on the button to show the
 *  column selection menu.  It first synchronize the checkboxes on all
 *  menu item with the gconf visibility values.  It then pops up the
 *  menu for the user to choose from.
 *
 *  @param column The tree column containing the column selection
 *  button.
 *
 *  @param view The tree view.
 *
 *  @internal
 */
static void
gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
				GncTreeView *view)
{
  GtkWidget *widget, *menu;

  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  menu = view->priv->column_menu;
  if (!menu)
    return;

  /* Synchronize the menu before display */
  gtk_container_foreach(GTK_CONTAINER(menu),
			(GtkCallback)gnc_tree_view_update_column_menu_item,
			view);

  /* Ensure all components are visible */
  gtk_widget_show_all(menu);

  /* Pop the menu up at the button */
  widget = gtk_tree_view_column_get_widget(column);
  gtk_menu_popup(GTK_MENU(view->priv->column_menu), NULL, GTK_WIDGET(menu),
		 NULL, NULL, 0, gtk_get_current_event_time());
}

/** This function is called to set the "show-column-menu" property on
 *  this view.  This function has no visible effect if the
 *  "gconf-section" property has not been set.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
void
gnc_tree_view_set_show_column_menu (GncTreeView *view,
				    gboolean visible)
{
  GncTreeViewPrivate *priv;

  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER("view %p, show menu %d", view, visible);
  priv = GNC_TREE_VIEW(view)->priv;
  priv->show_column_menu = visible;
  gnc_tree_view_build_column_menu(view);
  LEAVE(" ");
}

/** This function is called to get the current value of the
 *  "show-column-menu" property.  It returns the same value passed to
 *  gnc_tree_view_set_show_menu_column().
 *
 *  Parameters are defined in gnc-tree-view.h
 */
gboolean
gnc_tree_view_get_show_column_menu (GncTreeView *view)
{
  g_return_val_if_fail(GNC_IS_TREE_VIEW(view), FALSE);

  return(view->priv->show_column_menu);
}

/** @} */

/************************************************************/
/*                    Tree View Creation                    */
/************************************************************/

/** Attach a data model to a visible GncTreeView widget.  Users of
 *  this view object must use this function instead of directly
 *  calling the gtk_tree_view_set_model function.  This function takes
 *  the additional step of attaching a callback function to the model
 *  to catch any changes to the sorting of the model.  These changes
 *  are propagated into gconf by the callback function.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
void
gnc_tree_view_set_model(GncTreeView *view, GtkTreeModel *model)
{
  GtkTreeModel *old_model;

  /* Remove existing callback */
  if (view->priv->sort_column_changed_cb_id) {
    old_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
    g_signal_handler_disconnect (old_model,
				 view->priv->sort_column_changed_cb_id);
    view->priv->sort_column_changed_cb_id = 0;
  }

  gtk_tree_view_set_model (GTK_TREE_VIEW(view), model);

  /* Maybe add a new callback */
  if (model && view->priv->gconf_section) {
    view->priv->sort_column_changed_cb_id =
      g_signal_connect(GTK_TREE_SORTABLE(model), "sort-column-changed",
		       (GCallback)gtk_tree_view_sort_column_changed_cb, view);
  }
}

/** Configure (by name) the default set of visible columns in an gnc
 *  tree view.  This is the list of columns that will be shown if the
 *  view isn't using gconf to manage column visibility.  If gconf is
 *  used, this list will be used the very first time the view is
 *  presented to the user, then gconf will be used after that.  The
 *  available list of columns can be found in the file
 *  gnc-tree-view-xxx.c.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
void
gnc_tree_view_configure_columns (GncTreeView *view,
				 gchar *first_column_name,
				 ...)
{
  GSList *slist;
  GList *columns;
  gchar *name;
  va_list args;

  g_return_if_fail(GNC_IS_TREE_VIEW(view));

  ENTER(" ");
  /* Delete any old list of column names */
  if (view->priv->default_visible) {
    slist = view->priv->default_visible;
    view->priv->default_visible = NULL;
    g_slist_foreach(slist, (GFunc)g_free, NULL);
    g_slist_free(slist);
  }

  /* Build a new list */
  va_start (args, first_column_name);
  name = first_column_name;
  while (name != NULL) {
    view->priv->default_visible =
      g_slist_append(view->priv->default_visible, g_strdup(name));
    name = va_arg(args, gchar*);
  }
  va_end (args);

  /* Update the view and gconf */
  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  g_list_foreach(columns, (GFunc)gnc_tree_view_update_visibility, view);
  g_list_free(columns);

  if (view->priv->gconf_section)
    view->priv->seen_gconf_visibility = TRUE;
  LEAVE(" ");
}


/** This internal function sets a variety of common properties on a
 *  newly created GtkTreeViewColumn and its renderer.
 *
 *  @param view A pointer to a generic GncTreeView.
 *
 *  @param column The newly created tree view column.
 *
 *  @param pref_name The internal name of this column.  This name is
 *  used in several functions to look up the column, and it is also
 *  used to create the keys used to record the column width and
 *  visibility in gconf.
 *
 *  @param data_column The index of the GtkTreeModel data column used
 *  to determine the data that will be displayed in this column for
 *  each row in the view.  Use GNC_TREE_VIEW_COLUMN_DATA_NONE if you
 *  plan on using an non-model data source for this column.
 *
 *  @param default_width The width this column should be if not
 *  specified by gconf.  If the this value is zero, the column will
 *  be marked as automatically sized.
 *
 *  @param resizable Whether to mark the column as user resizable.
 *  This marking is only relevant for fixed width columns.
 *
 *  @param column_sort_function The function that GtkTreeModelSort
 *  will call to compare two rows to determine their displayed order.
 *
 *  @internal
 */
static void
gnc_tree_view_column_properties (GncTreeView *view,
				 GtkTreeViewColumn *column,
				 const gchar *pref_name,
				 gint data_column,
				 gint default_width,
				 gboolean resizable,
				 GtkTreeIterCompareFunc column_sort_fn)
{
  GtkTreeModel *s_model;
  gboolean visible;
  int width = 0;
  gchar *key;

  /* Set data used by other functions */
  if (pref_name)
    g_object_set_data(G_OBJECT(column), PREF_NAME, (gpointer)pref_name);
  if (data_column == 0)
    g_object_set_data(G_OBJECT(column), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
  if (data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
    g_object_set_data(G_OBJECT(column), MODEL_COLUMN,
		      GINT_TO_POINTER(data_column));

  /* Get visibility */
  visible = gnc_tree_view_column_visible(view, NULL, pref_name);

  /* Get width */
  if (view->priv->gconf_section) {
    key = g_strdup_printf("%s_%s", pref_name, GCONF_KEY_WIDTH);
    width = gnc_gconf_get_int(view->priv->gconf_section, key, NULL);
    g_free(key);
  }

  /* If gconf comes back with a width of zero (or there is no gconf
   * width) the use the default width for the column.  Allow for
   * padding L and R of the displayed data. */
  if (width == 0)
    width = default_width + 10;
  if (width == 0)
    width = 10;

  /* Set column attributes */
  g_object_set(G_OBJECT(column),
	       "visible",     visible,
	       "sizing",      GTK_TREE_VIEW_COLUMN_FIXED,
	       "fixed-width", width,
	       "resizable",   resizable && pref_name != NULL,
	       "reorderable", pref_name != NULL,
	       NULL);

  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  if (GTK_IS_TREE_MODEL_SORT(s_model)) {
    gtk_tree_view_column_set_sort_column_id (column, data_column);
    if (column_sort_fn) {
      gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(s_model),
				       data_column, column_sort_fn,
				       NULL /* user_data */,
				       NULL /* destroy fn */);
    }
  }

  /* Add to the column selection menu */
  if (pref_name) {
    gnc_tree_view_create_menu_item(column, view);
  }
}

/** This function adds a new toggle column to a GncTreeView base view.
 *  It takes all the parameters necessary to hook a GtkTreeModel
 *  column to a GtkTreeViewColumn.  It handles creating a tooltip to
 *  show the full title name, and setting the sort and edit callback
 *  functions.  If the tree has a gconf section associated with it,
 *  this function also wires up the column so that its visibility and
 *  width are remembered.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
GtkTreeViewColumn *
gnc_tree_view_add_toggle_column (GncTreeView *view,
				 const gchar *column_title,
				 const gchar *column_short_title,
				 const gchar *pref_name,
				 gint model_data_column,
				 gint model_visibility_column,
				 GtkTreeIterCompareFunc column_sort_fn,
				 renderer_toggled toggle_edited_cb)
{
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  PangoLayout* layout;
  int title_width;

  g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);

  renderer = gtk_cell_renderer_toggle_new ();
  column =
    gtk_tree_view_column_new_with_attributes (gettext(column_short_title),
					      renderer,
					      "active", model_data_column,
					      NULL);

  /* Add the full title to the object for menu creation */
  g_object_set_data_full(G_OBJECT(column), REAL_TITLE,
			 g_strdup(column_title), g_free);
  if (toggle_edited_cb)
    g_signal_connect(G_OBJECT(renderer), "toggled",
		     G_CALLBACK(toggle_edited_cb), view);

  if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
    gtk_tree_view_column_add_attribute (column, renderer,
					"visible", model_visibility_column);

  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view),
					   column_short_title);
  pango_layout_get_pixel_size(layout, &title_width, NULL);
  g_object_unref(layout);

  gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
				   title_width, FALSE, column_sort_fn);

  gnc_tree_view_append_column (view, column);

  /* Also add the full title to the object as a tooltip */
  if (!view->priv->title_tips)
    view->priv->title_tips = gtk_tooltips_new();
  gtk_tooltips_set_tip(view->priv->title_tips,
		       column->button,
		       column_title, NULL);

  return column;
}

/** This function adds a new text column to a GncTreeView base view.
 *  It takes all the parameters necessary to hook a GtkTreeModel
 *  column to a GtkTreeViewColumn.  If the tree has a gconf section
 *  associated with it, this function also wires up the column so that
 *  its visibility and width are remembered.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
GtkTreeViewColumn *
gnc_tree_view_add_text_column (GncTreeView *view,
			       const gchar *column_title,
			       const gchar *pref_name,
			       const gchar *stock_icon_name,
			       const gchar *sizing_text,
			       gint model_data_column,
			       gint model_visibility_column,
			       GtkTreeIterCompareFunc column_sort_fn)
{
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  PangoLayout* layout;
  int default_width, title_width;

  g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);

  column = gtk_tree_view_column_new ();
  gtk_tree_view_column_set_title (column, gettext(column_title));

  /* Set up an icon renderer if requested */
  if (stock_icon_name) {
    renderer = gtk_cell_renderer_pixbuf_new ();
    g_object_set (renderer, "stock-id", stock_icon_name, NULL);
    gtk_tree_view_column_pack_start (column, renderer, FALSE);
  }

  /* Set up a text renderer and attributes */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, TRUE);

  /* Set renderer attributes controlled by the model */
  if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
    gtk_tree_view_column_add_attribute (column, renderer,
					"text", model_data_column);
  if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
    gtk_tree_view_column_add_attribute (column, renderer,
					"visible", model_visibility_column);

  /* Default size is the larger of the column title and the sizing text */
  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
  pango_layout_get_pixel_size(layout, &title_width, NULL);
  g_object_unref(layout);
  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
  pango_layout_get_pixel_size(layout, &default_width, NULL);
  g_object_unref(layout);
  default_width = MAX(default_width, title_width);
  if (default_width)
    default_width += 10; /* padding on either side */
  gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
				   default_width, TRUE, column_sort_fn);

  gnc_tree_view_append_column (view, column);
  return column;
}

/** This function adds a new numeric column to a GncTreeView base
 *  view.  It takes all the parameters necessary to hook a
 *  GtkTreeModel column to a GtkTreeViewColumn.  If the tree has a
 *  gconf section associated with it, this function also wires up the
 *  column so that its visibility and width are remembered.  A numeric
 *  column is nothing more then a text column with a few extra
 *  attributes.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
GtkTreeViewColumn *
gnc_tree_view_add_numeric_column (GncTreeView *view,
				  const gchar *column_title,
				  const gchar *pref_name,
				  const gchar *sizing_text,
				  gint model_data_column,
				  gint model_color_column,
				  gint model_visibility_column,
				  GtkTreeIterCompareFunc column_sort_fn)
{
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  GList *renderers;

  column = gnc_tree_view_add_text_column (view, column_title, pref_name,
					  NULL, sizing_text, model_data_column,
					  model_visibility_column,
					  column_sort_fn);

  /* Get the list of one renderer */
  renderers = gtk_tree_view_column_get_cell_renderers(column);
  renderer = renderers->data;
  g_list_free(renderers);

  /* Right align the column title and data */
  g_object_set(G_OBJECT(column), "alignment",   1.0, NULL);
  g_object_set(G_OBJECT(renderer), "xalign",   1.0, NULL);

  /* Change the text color */
  if (model_color_column != GNC_TREE_VIEW_COLUMN_COLOR_NONE)
    gtk_tree_view_column_add_attribute (column, renderer,
					"foreground", model_color_column);

  return column;
}

/** Add a column to a view based upon a GncTreeView.  This function
 *  knows about the two special columns on the right side of this type
 *  of view, and adds the new column before these two columns.  You
 *  could say that it appends to the data columns and ignores the
 *  infrastructure columns.
 *
 *  Parameters are defined in gnc-tree-view.h
 */
gint
gnc_tree_view_append_column (GncTreeView *view,
			     GtkTreeViewColumn *column)
{
  GList *columns;
  int n;

  /* There's no easy way to get this number. */
  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  n = g_list_length(columns);
  g_list_free(columns);

  /* Ignore the initial two columns (the spacer and the selection menu) */
  if (n >= 2)
    n -= 2;
  return gtk_tree_view_insert_column (GTK_TREE_VIEW(view), column, n);
}

/** @} */
