/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2017)
  
	Adresse mèl :
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2017)

	E-mail address:
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include "ui_properties.h"

/**
 * SECTION:ui_properties
 * @short_description: A specialised combo box to list node properties
 * of a #VisuData object.
 *
 * <para>This object is a specialised combo box that lists the node
 * properties of a given #VisuData object.</para>
 */


struct _VisuUiComboValuesPrivate
{
  gboolean dispose_has_run;

  VisuData *data;
  gulong propAdd_signal, propRemoved_signal;

  GtkListStore *model;
};

enum
  {
    PROP_0,
    MODEL_PROP,
    VALUES_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

/* The ids of the columns used in the combobox that identify
   each data node. */
enum
  {
    COLUMN_COMBO_LABEL,
    COLUMN_COMBO_STOCK,
    COLUMN_COMBO_OBJECT,
    NB_COLUMN_COMBO
  };

static void visu_ui_combo_values_dispose     (GObject* obj);
static void visu_ui_combo_values_finalize    (GObject* obj);
static void visu_ui_combo_values_get_property(GObject* obj, guint property_id,
                                            GValue *value, GParamSpec *pspec);
static void visu_ui_combo_values_set_property(GObject* obj, guint property_id,
                                            const GValue *value, GParamSpec *pspec);

G_DEFINE_TYPE_WITH_CODE(VisuUiComboValues, visu_ui_combo_values, GTK_TYPE_COMBO_BOX,
                        G_ADD_PRIVATE(VisuUiComboValues))

/* Local callbacks. */
static void onPropAdded(VisuUiComboValues *combo, VisuNodeValues *prop);
static void onPropRemoved(VisuUiComboValues *combo, VisuNodeValues *prop);
static void emitValuesNotify(GObject *combo);

/* Local routines. */

static void visu_ui_combo_values_class_init(VisuUiComboValuesClass *klass)
{
  DBG_fprintf(stderr, "Visu UiCombo_Values: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_ui_combo_values_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_ui_combo_values_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_ui_combo_values_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_ui_combo_values_get_property;

  /**
   * VisuUiComboValues::model:
   *
   * The #VisuData this combo_values is operating on.
   *
   * Since: 3.8
   */
  _properties[MODEL_PROP] = g_param_spec_object
    ("model", "Model", "node model.", VISU_TYPE_DATA, G_PARAM_READWRITE);
  /**
   * VisuUiComboValues::active-values:
   *
   * The currently selected #VisuNodeValues.
   *
   * Since: 3.8
   */
  _properties[VALUES_PROP] = g_param_spec_object
    ("active-values", "Active values", "current selection.", VISU_TYPE_NODE_VALUES, G_PARAM_READWRITE);
  
  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);
}

static void visu_ui_combo_values_init(VisuUiComboValues *list)
{
  GtkCellRenderer *renderer;

  DBG_fprintf(stderr, "Visu UiCombo_Values: initializing a new object (%p).\n",
	      (gpointer)list);
  list->priv = visu_ui_combo_values_get_instance_private(list);
  list->priv->dispose_has_run = FALSE;

  list->priv->data = (VisuData*)0;

  list->priv->model = gtk_list_store_new(NB_COLUMN_COMBO, G_TYPE_STRING,
                                         G_TYPE_STRING, VISU_TYPE_NODE_VALUES);

  gtk_combo_box_set_model(GTK_COMBO_BOX(list), GTK_TREE_MODEL(list->priv->model));

  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(list), renderer, FALSE);
  gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(list), renderer, "stock-id",
				COLUMN_COMBO_STOCK);
  renderer = gtk_cell_renderer_text_new();
  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(list), renderer, FALSE);
  gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(list), renderer, "markup",
				COLUMN_COMBO_LABEL);

  g_signal_connect(list, "changed", G_CALLBACK(emitValuesNotify), (gpointer)0);
}

static void visu_ui_combo_values_dispose(GObject* obj)
{
  VisuUiComboValues *list;

  DBG_fprintf(stderr, "Visu UiCombo_Values: dispose object %p.\n", (gpointer)obj);

  list = VISU_UI_COMBO_VALUES(obj);
  if (list->priv->dispose_has_run)
    return;
  list->priv->dispose_has_run = TRUE;

  visu_ui_combo_values_setNodeModel(list, (VisuData*)0);
  gtk_combo_box_set_model(GTK_COMBO_BOX(list), NULL);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_ui_combo_values_parent_class)->dispose(obj);
}

static void visu_ui_combo_values_finalize(GObject* obj)
{
  VisuUiComboValues *list;

  DBG_fprintf(stderr, "Visu UiCombo_Values: finalize object %p.\n", (gpointer)obj);

  list = VISU_UI_COMBO_VALUES(obj);

  DBG_fprintf(stderr, " | model (%d)\n", G_OBJECT(list->priv->model)->ref_count);
  g_object_unref(list->priv->model);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_ui_combo_values_parent_class)->finalize(obj);
}

static void visu_ui_combo_values_get_property(GObject* obj, guint property_id,
                                            GValue *value, GParamSpec *pspec)
{
  DBG_fprintf(stderr, "Visu UiCombo_Values: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case MODEL_PROP:
      g_value_set_object(value, VISU_UI_COMBO_VALUES(obj)->priv->data);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    case VALUES_PROP:
      g_value_set_object(value, visu_ui_combo_values_getActive(VISU_UI_COMBO_VALUES(obj)));
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_ui_combo_values_set_property(GObject* obj, guint property_id,
                                            const GValue *value, GParamSpec *pspec)
{
  VisuUiComboValues *list = VISU_UI_COMBO_VALUES(obj);

  DBG_fprintf(stderr, "Visu UiCombo_Values: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case MODEL_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_ui_combo_values_setNodeModel(list, VISU_DATA(g_value_get_object(value)));
      break;
    case VALUES_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_ui_combo_values_setActive(list, VISU_NODE_VALUES(g_value_get_object(value)));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_ui_combo_values_new:
 *
 * Create a new #GtkListStore to store planes.
 *
 * Since: 3.8
 *
 * Returns: a newly created object.
 **/
GtkWidget* visu_ui_combo_values_new()
{
  return g_object_new(VISU_TYPE_UI_COMBO_VALUES, NULL);
}

/**
 * visu_ui_combo_values_setNodeModel:
 * @combo: a #VisuUiComboValues object.
 * @data: (allow-none): a #VisuData object.
 *
 * Bind the @set object to @list.
 *
 * Returns: TRUE if @set is changed
 **/
gboolean visu_ui_combo_values_setNodeModel(VisuUiComboValues *combo,
                                           VisuData *data)
{
  GList *lst, *tmpLst;
  VisuNodeValues *values;

  g_return_val_if_fail(VISU_IS_UI_COMBO_VALUES(combo), FALSE);

  if (combo->priv->data == data)
    return FALSE;

  values = visu_ui_combo_values_getActive(combo);
  if (values)
    g_object_ref(values);
  gtk_list_store_clear(combo->priv->model);
  if (combo->priv->data)
    {
      g_signal_handler_disconnect(G_OBJECT(combo->priv->data),
                                  combo->priv->propAdd_signal);
      g_signal_handler_disconnect(G_OBJECT(combo->priv->data),
                                  combo->priv->propRemoved_signal);
      g_object_unref(combo->priv->data);
    }
  combo->priv->data = data;
  if (data)
    {
      g_object_ref(data);
      combo->priv->propAdd_signal = g_signal_connect_swapped
        (G_OBJECT(data), "node-properties-added",
         G_CALLBACK(onPropAdded), combo);
      combo->priv->propRemoved_signal = g_signal_connect_swapped
        (G_OBJECT(data), "node-properties-removed",
         G_CALLBACK(onPropRemoved), combo);
      
      lst = visu_data_getAllNodeProperties(data);
      for (tmpLst = lst; tmpLst; tmpLst = g_list_next(tmpLst))
        onPropAdded(combo, VISU_NODE_VALUES(tmpLst->data));
      g_list_free(lst);

      if (values)
        visu_ui_combo_values_setActiveByLabel(combo, visu_node_values_getLabel(values));
    }
  if (values)
    g_object_unref(values);

  return TRUE;
}

static void onPropAdded(VisuUiComboValues *combo, VisuNodeValues *prop)
{
  gchar *markup;
  GtkTreeIter iter;

  markup = g_markup_printf_escaped("<span size=\"smaller\">%s</span>", visu_node_values_getLabel(prop));
  gtk_list_store_append(combo->priv->model, &iter);
  gtk_list_store_set(combo->priv->model, &iter,
                     COLUMN_COMBO_LABEL, markup,
                     COLUMN_COMBO_OBJECT, prop,
                     -1);
  g_free(markup);
  if (visu_node_values_getEditable(prop))
    gtk_list_store_set(combo->priv->model, &iter,
                       COLUMN_COMBO_STOCK, "gtk-edit",
                       -1);
}
static void onPropRemoved(VisuUiComboValues *combo, VisuNodeValues *prop)
{
  VisuNodeValues *myprop;
  gboolean valid;
  GtkTreeIter iter;

  for (valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(combo->priv->model), &iter);
       valid; )
    {
      gtk_tree_model_get(GTK_TREE_MODEL(combo->priv->model), &iter,
                         COLUMN_COMBO_OBJECT, &myprop, -1);
      if (myprop)
        g_object_unref(myprop);
      if (myprop == prop)
        valid = gtk_list_store_remove(combo->priv->model, &iter);
      else
        valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(combo->priv->model), &iter);
    }
}

/**
 * visu_ui_combo_values_getActive:
 * @combo: a #VisuUiComboValues object.
 *
 * Retrieves the currently selected #VisuNodeValues object.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the selected #VisuNodeValues object.
 **/
VisuNodeValues* visu_ui_combo_values_getActive(VisuUiComboValues *combo)
{
  GtkTreeIter iter;
  VisuNodeValues *values;
  
  g_return_val_if_fail(VISU_IS_UI_COMBO_VALUES(combo), (VisuNodeValues*)0);

  if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter))
    return (VisuNodeValues*)0;

  gtk_tree_model_get(GTK_TREE_MODEL(combo->priv->model), &iter,
                     COLUMN_COMBO_OBJECT, &values, -1);
  if (values)
    g_object_unref(values);
  return values;
}

/**
 * visu_ui_combo_values_setActive:
 * @combo: a #VisuUiComboValues object.
 * @values: a #VisuNodeValues object.
 *
 * Changes current selection of @combo to be on @values. If @values is
 * not part of @combo, nothing is changed and %FALSE is returned.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @values exists in @combo.
 **/
gboolean visu_ui_combo_values_setActive(VisuUiComboValues *combo,
                                        const VisuNodeValues *values)
{
  GtkTreeIter iter;
  gboolean valid;
  VisuNodeValues *prop;
  
  g_return_val_if_fail(VISU_IS_UI_COMBO_VALUES(combo), FALSE);

  for (valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(combo->priv->model), &iter);
       valid; valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(combo->priv->model), &iter))
    {
      gtk_tree_model_get(GTK_TREE_MODEL(combo->priv->model), &iter,
                         COLUMN_COMBO_OBJECT, &prop, -1);
      g_object_unref(prop);
      if (prop == values)
        {
          gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
          return TRUE;
        }
    }
  return FALSE;
}
/**
 * visu_ui_combo_values_setActiveByLabel:
 * @combo: a #VisuUiComboValues object.
 * @label: a label.
 *
 * Changes current selection of @combo to match a #VisuNodeValues
 * property that is defined by @label. If none exists, nothing is
 * changed and %FALSE is returned.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @label exists in @combo.
 **/
gboolean visu_ui_combo_values_setActiveByLabel(VisuUiComboValues *combo,
                                               const gchar* label)
{
  GtkTreeIter iter;
  gboolean valid;
  VisuNodeValues *prop;

  g_return_val_if_fail(VISU_IS_UI_COMBO_VALUES(combo), FALSE);

  if (!label)
    return FALSE;

  for (valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(combo->priv->model), &iter);
       valid; valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(combo->priv->model), &iter))
    {
      gtk_tree_model_get(GTK_TREE_MODEL(combo->priv->model), &iter,
                         COLUMN_COMBO_OBJECT, &prop, -1);
      g_object_unref(prop);
      if (!strcmp(visu_node_values_getLabel(prop), label))
        {
          gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
          return TRUE;
        }
    }
  return FALSE;
}

static void emitValuesNotify(GObject *combo)
{
  DBG_fprintf(stderr, "Visu UiComboValues: emitting active-values notification.\n");
  g_object_notify_by_pspec(combo, _properties[VALUES_PROP]);
}
