/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par 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 : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	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 <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <math.h>
#include <string.h>

#include "support.h"
#include "gtk_interactive.h"
#include "visu_gtk.h"
#include "gtk_main.h"
#include "gtk_pick.h"
#include "visu_basic.h"
#include "gtk_renderingWindowWidget.h"
#include "extensions/infos.h"
#include "extensions/marks.h"
#include "extraFunctions/idProp.h"
#include "openGLFunctions/interactive.h"
#include "extraGtkFunctions/gtk_valueIOWidget.h"
#include "uiElements/ui_properties.h"

/**
 * SECTION: gtk_pick
 * @short_description: The pick and measurement tab in the interactive
 * dialog.
 *
 * <para>This action tab provides widgets to display information about
 * selected atoms, like distances or angles. In addition, measured
 * distances and angles are kept in a list when new files are
 * loaded.</para>
 * <para>With the list of selected nodes, one can modify properties
 * associated to nodes like their coordinates, the value of
 * colourisation if any, the forces on them, if any... One can also
 * decide to display information directly on nodes.</para>
 */

#define GTK_PICK_INFO				\
  _("left-button\t\t\t: standard pick\n"			\
    "control-left-button\t\t: toggle highlihgt node\n"		\
    "middle-button\t\t: measure node neighbouring\n"		\
    "shift-middle-button\t: pick 1st reference\n"		\
    "ctrl-middle-button\t\t: pick 2nd reference\n"		\
    "drag-left-button\t\t: make a rectangular selection\n"	\
    "right-button\t\t\t: switch to observe")

/* Treeview used to print data of nodes. */
static VisuUiSelection *listPickedNodes;
static GtkWidget *treeviewPickedNodes, *labelSelection;
#define GTK_PICK_EDITABLE_NODE   "blue"
#define GTK_PICK_UNEDITABLE_NODE "black"

/* Draw data widgets. */
static GtkWidget *tglMarks;
static VisuInteractive *interPick = NULL;

/* The pick viewport. */
static GtkWidget *labelPickOut, *labelPickHistory = (GtkWidget*)0, *labelPickError;
struct _PickHistory
{
  VisuData *dataObj;
  gchar *str;
};
static GList *pickHistory = (GList*)0;

/* Signals that need to be suspended */
static VisuData *currentData;
static gulong propAdd_signal, propRemoved_signal;

/* Local callbacks. */
static void onEditedValue(GtkCellRendererText *cellrenderertext,
                          gchar *path, gchar *text, gpointer user_data);
static void onDrawDistanceChecked(GtkToggleButton* button, gpointer data);
static void onEraseDistanceClicked(GtkButton *button, gpointer user_data);
static gboolean onTreeviewInfosKey(GtkWidget *widget, GdkEventKey *event,
				   gpointer user_data);
static void onHighlightHideToggled(GtkToggleButton *button, gpointer user_data);
static void onHighlightHideStatus(GtkButton *button, gpointer user_data);
static void onNodeSelection(VisuInteractive *inter, VisuInteractivePick pick, 
			    VisuData *dataObj, VisuNode *node0, VisuNode *node1,
                            VisuNode *node2, gpointer data);
static void onSelectionError(VisuInteractive *inter, VisuInteractivePickError error,
			     gpointer data);
static void onMeasurementList(VisuGlExtMarks *marks, VisuData *dataObj, gpointer data);
static void onNodeValueChanged(VisuNodeValues *values, VisuNode *node);
static void onDataNotify(VisuGlNodeScene *scene);

/* Local routines. */
static void _setData(VisuData *dataObj);
static gboolean onLoadXML(const gchar *filename, GError **error);
static gboolean onSaveXML(const gchar *filename, GError **error);
static void _addColumn(GtkTreeView *view, VisuNodeValues *prop);
static void _removeColumn(GtkTreeView *view, VisuNodeValues *prop);
static void _togglePath(VisuUiSelection *selection, gchar *path);

/********************/
/* Public routines. */
/********************/
/**
 * visu_ui_interactive_pick_init: (skip)
 *
 * Internal routine to setup the pick action of the interactive dialog.
 *
 * Since: 3.6
 */
void visu_ui_interactive_pick_init()
{
  listPickedNodes = visu_ui_selection_new();
  labelSelection = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  currentData = (VisuData*)0;
}
static gboolean _haveNodes(GBinding *bind _U_, const GValue *from,
                           GValue *to, gpointer data _U_)
{
  GArray *ids;

  ids = (GArray*)g_value_get_boxed(from);
  g_value_set_boolean(to, ids && ids->len > 0);
  return TRUE;
}
static gboolean _toLblMarks(GBinding *bind _U_, const GValue *from,
                            GValue *to, gpointer data _U_)
{
  GArray *ids;

  ids = (GArray*)g_value_get_boxed(from);  
  if (ids && ids->len)
    g_value_take_string(to, g_strdup_printf(_("Highlights <span size=\"small\">(%d)</span>:"), ids->len));
  else
    g_value_set_string(to, _("Highlights <span size=\"small\">(none)</span>:"));
  return TRUE;
}
static gboolean _toLblList(GBinding *bind, const GValue *from _U_,
                           GValue *to, gpointer data _U_)
{
  gint n;

  n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(g_binding_get_source(bind)), NULL);
  if (n > 0)
    g_value_take_string(to, g_strdup_printf(_("<b>List of %d node(s):</b>"), n));
  else
    g_value_set_string(to, _("<b>List of nodes <span size="
                             "\"small\">(none)</span>:</b>"));
  return TRUE;
}
static gboolean _toDrawSelected(GBinding *bind, const GValue *from,
                                GValue *to, gpointer data _U_)
{
  GArray *nodes = g_value_get_boxed(from);
  g_value_set_boolean(to, (nodes && nodes->len) && visu_gl_ext_getActive(VISU_GL_EXT(g_binding_get_source(bind))));
  return g_value_get_boolean(to);
}
static gboolean _fromDrawSelected(GBinding *bind _U_, const GValue *from,
                                  GValue *to, gpointer data)
{
  if (g_value_get_boolean(from))
    g_value_take_boxed(to, visu_ui_selection_get(VISU_UI_SELECTION(data)));
  return g_value_get_boolean(from);
}
static gboolean _toDrawAlways(GBinding *bind, const GValue *from,
                              GValue *to, gpointer data _U_)
{
  GArray *nodes = g_value_get_boxed(from);
  g_value_set_boolean(to, (!nodes || !nodes->len) && visu_gl_ext_getActive(VISU_GL_EXT(g_binding_get_source(bind))));
  return g_value_get_boolean(to);
}
static gboolean _fromDrawAlways(GBinding *bind _U_, const GValue *from,
                                GValue *to, gpointer data _U_)
{
  if (g_value_get_boolean(from))
    g_value_take_boxed(to, (gpointer)0);
  return g_value_get_boolean(from);
}
static gboolean _toSelection(GBinding *bind _U_, const GValue *from,
                             GValue *to, gpointer data)
{
  if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data)))
    return FALSE;
  g_value_set_boxed(to, g_value_get_boxed(from));
  return TRUE;
}

/**
 * visu_ui_interactive_pick_initBuild: (skip)
 * @main: the main interface.
 * @label: a location to store the name of the pick tab ;
 * @help: a location to store the help message to be shown at the
 * bottom of the window ;
 * @radio: a location on the radio button that will be toggled when
 * the pick action is used.
 * 
 * This routine should be called in conjonction to the
 * visu_ui_interactive_move_initBuild() one. It completes the creation of widgets
 * (and also initialisation of values) for the pick tab.
 */
GtkWidget* visu_ui_interactive_pick_initBuild(VisuUiMain *main, gchar **label,
                                              gchar **help, GtkWidget **radio)
{
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  GtkWidget *lbl, *wd, *wd2, *hbox, *vbox, *image;
  GtkWidget *valueIO, *hboxMarks, *comboDraw;
  GtkWidget *radioDrawNever, *radioDrawSelected, *radioDrawAlways;
  GSList *radioDrawNever_group = NULL;
  GtkToolItem *item;
  VisuUiRenderingWindow *window;
  VisuGlExtMarks *marks;
  VisuGlExtInfos *infos;
  VisuGlNodeScene *scene;
#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 12
  GtkTooltips *tooltips;
  tooltips = gtk_tooltips_new ();
#endif
  
  DBG_fprintf(stderr, "Gtk Pick: setup new action tab.\n");
  window = visu_ui_main_class_getDefaultRendering();
  interPick = visu_interactive_new(interactive_measure);
  
  scene = visu_ui_rendering_window_getGlScene(window);
  g_signal_connect(G_OBJECT(interPick), "node-selection",
		   G_CALLBACK(onNodeSelection), (gpointer)0);
  g_signal_connect(G_OBJECT(interPick), "selection-error",
		   G_CALLBACK(onSelectionError), (gpointer)0);
  g_signal_connect_swapped(G_OBJECT(interPick), "region-selection",
                           G_CALLBACK(visu_ui_selection_append), listPickedNodes);
  g_signal_connect(G_OBJECT(interPick), "stop",
		   G_CALLBACK(visu_ui_interactive_toggle), (gpointer)0);
  marks = visu_gl_node_scene_getMarks(scene);
  infos = visu_gl_node_scene_getInfos(scene);
  g_signal_connect(G_OBJECT(marks), "measurementChanged",
                   G_CALLBACK(onMeasurementList), (gpointer)0);

  *label = g_strdup("Pick");
  *help  = g_strdup(GTK_PICK_INFO);
  *radio = lookup_widget(main->interactiveDialog, "radioPick");

  vbox = lookup_widget(main->interactiveDialog, "vbox25");
  
  hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

  lbl = gtk_label_new("");
  gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
  gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);
  g_object_bind_property_full(listPickedNodes, "selection", lbl, "label",
                              G_BINDING_SYNC_CREATE, _toLblList, NULL,
                              (gpointer)0, (GDestroyNotify)0);

  gtk_box_pack_start(GTK_BOX(hbox), labelSelection, TRUE, TRUE, 0);

  lbl = gtk_label_new(_("<span size=\"smaller\">Values in <span color=\"blue\">blue</span> are editable</span>"));
  gtk_box_pack_end(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
  gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);

  image = gtk_image_new_from_icon_name("help-browser", GTK_ICON_SIZE_MENU);
  gtk_box_pack_end(GTK_BOX(hbox), image, FALSE, FALSE, 0);
  gtk_widget_set_margin_end(image, 3);

  /* Create the treeview and related buttons. */
  DBG_fprintf(stderr, "Gtk Pick: Create the treeview and related buttons.\n");
  hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
  
  g_signal_connect_swapped(hbox, "destroy", G_CALLBACK(g_object_unref), interPick);

  wd = gtk_scrolled_window_new(NULL, NULL);
  gtk_widget_set_size_request(wd, -1, 120);
  gtk_box_pack_start(GTK_BOX(hbox), wd, TRUE, TRUE, 0);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(wd),
				 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(wd), GTK_SHADOW_IN);

  treeviewPickedNodes = gtk_tree_view_new ();
  gtk_container_add(GTK_CONTAINER(wd), treeviewPickedNodes);
  gtk_tree_view_set_model(GTK_TREE_VIEW(treeviewPickedNodes),
			  GTK_TREE_MODEL(listPickedNodes));
  g_object_unref(listPickedNodes);
  g_signal_connect(G_OBJECT(treeviewPickedNodes), "key-press-event",
		   G_CALLBACK(onTreeviewInfosKey), (gpointer)0);
  gtk_tree_selection_set_mode
    (gtk_tree_view_get_selection(GTK_TREE_VIEW(treeviewPickedNodes)),
     GTK_SELECTION_MULTIPLE);

  wd = gtk_toolbar_new();
  gtk_orientable_set_orientation(GTK_ORIENTABLE(wd), GTK_ORIENTATION_VERTICAL);
  gtk_toolbar_set_style(GTK_TOOLBAR(wd), GTK_TOOLBAR_ICONS);
  gtk_toolbar_set_icon_size(GTK_TOOLBAR(wd), GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_box_pack_end(GTK_BOX(hbox), wd, FALSE, FALSE, 0);

  item = gtk_toggle_tool_button_new();
  gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Set / unset highlight status following selection."));
  gtk_toolbar_insert(GTK_TOOLBAR(wd), item, -1);
  gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item),
                                  create_pixmap((GtkWidget*)0, "stock-select-all_20.png"));
  g_object_bind_property(listPickedNodes, "highlight", item, "active",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

  item = gtk_tool_button_new(NULL, NULL);
  gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Empty the selection list."));
  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "edit-clear");
  g_signal_connect_swapped(G_OBJECT(item), "clicked",
                           G_CALLBACK(visu_ui_selection_clear), listPickedNodes);
  gtk_toolbar_insert(GTK_TOOLBAR(wd), item, -1);

  lbl = gtk_label_new(_("<b>Draw data on nodes</b>"));
  gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 0);
  gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);
  gtk_label_set_xalign(GTK_LABEL(lbl), 0);

  hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

  radioDrawNever = gtk_radio_button_new_with_mnemonic(NULL, _("none"));
  g_object_bind_property(infos, "active", radioDrawNever, "active",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN);
  gtk_widget_set_name(radioDrawNever, "message_radio");
  gtk_box_pack_start(GTK_BOX(hbox), radioDrawNever, FALSE, FALSE, 0);
  gtk_radio_button_set_group(GTK_RADIO_BUTTON(radioDrawNever), radioDrawNever_group);
  radioDrawNever_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radioDrawNever));

  radioDrawSelected = gtk_radio_button_new_with_mnemonic(NULL, _("listed"));
  gtk_widget_set_name(radioDrawSelected, "message_radio");
  g_object_bind_property_full(infos, "selection", radioDrawSelected, "active",
                              G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
                              _toDrawSelected, _fromDrawSelected,
                              listPickedNodes, (GDestroyNotify)0);
  g_object_bind_property_full(listPickedNodes, "selection",
                              radioDrawSelected, "sensitive",
                              G_BINDING_SYNC_CREATE,
                              _haveNodes, NULL, (gpointer)0, (GDestroyNotify)0);
  gtk_box_pack_start(GTK_BOX(hbox), radioDrawSelected, FALSE, FALSE, 0);
  gtk_radio_button_set_group(GTK_RADIO_BUTTON(radioDrawSelected), radioDrawNever_group);
  radioDrawNever_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radioDrawSelected));

  radioDrawAlways = gtk_radio_button_new_with_mnemonic (NULL, _("all"));
  gtk_widget_set_name(radioDrawAlways, "message_radio");
  g_object_bind_property_full(infos, "selection", radioDrawAlways, "active",
                              G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
                              _toDrawAlways, _fromDrawAlways,
                              (gpointer)0, (GDestroyNotify)0);
  gtk_box_pack_start(GTK_BOX(hbox), radioDrawAlways, TRUE, TRUE, 0);
  gtk_radio_button_set_group(GTK_RADIO_BUTTON(radioDrawAlways), radioDrawNever_group);
  radioDrawNever_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radioDrawAlways));

  comboDraw = visu_ui_combo_values_new();
  gtk_widget_show(comboDraw);
  gtk_box_pack_start(GTK_BOX(hbox), comboDraw, TRUE, TRUE, 0);
  g_object_bind_property(scene, "data", comboDraw, "model",
                         G_BINDING_SYNC_CREATE);
  g_object_bind_property(infos, "values", comboDraw, "active-values",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

  gtk_widget_show_all(vbox);

  /* Building headers. */
  DBG_fprintf(stderr, "Gtk Pick: Build the tree view.\n");
  /* Highlight colum. */
  renderer = gtk_cell_renderer_toggle_new();
  g_signal_connect_swapped(G_OBJECT(renderer), "toggled",
                           G_CALLBACK(_togglePath), listPickedNodes);
  g_object_set(G_OBJECT(renderer), "indicator-size", 10, NULL);
  column = gtk_tree_view_column_new_with_attributes("", renderer,
						    "active", VISU_UI_SELECTION_HIGHLIGHT,
						    NULL);
  gtk_tree_view_column_set_sort_column_id(column, VISU_UI_SELECTION_HIGHLIGHT);
  gtk_tree_view_append_column (GTK_TREE_VIEW(treeviewPickedNodes), column);

  /* Set the load/save widget. */
  DBG_fprintf(stderr, "Gtk Pick: Add some widgets.\n");
  valueIO = visu_ui_value_io_new(GTK_WINDOW(main->interactiveDialog),
                                 _("Import picked nodes from an existing XML file."),
                                 _("Export listed picked nodes to the current XML file."),
                                 _("Export listed picked nodes to a new XML file."));
  visu_ui_value_io_setSensitiveOpen(VISU_UI_VALUE_IO(valueIO), TRUE);
  visu_ui_value_io_connectOnOpen(VISU_UI_VALUE_IO(valueIO), onLoadXML);
  visu_ui_value_io_connectOnSave(VISU_UI_VALUE_IO(valueIO), onSaveXML);
  g_object_bind_property_full(listPickedNodes, "selection", valueIO, "sensitive-save",
                              G_BINDING_SYNC_CREATE, _haveNodes, NULL,
                              (gpointer)0, (GDestroyNotify)0);
  gtk_widget_show_all(valueIO);
  wd = lookup_widget(main->interactiveDialog, "hboxPick");
  gtk_box_pack_end(GTK_BOX(wd), valueIO, TRUE, TRUE, 10);

  /* Set the names and load the widgets. */
  labelPickOut = lookup_widget(main->interactiveDialog, "pickInfo");
  labelPickHistory = gtk_label_new("");
  gtk_widget_show(labelPickHistory);
  gtk_box_pack_start(GTK_BOX(lookup_widget(main->interactiveDialog, "vbox24")),
                     labelPickHistory, FALSE, FALSE, 0);
  gtk_label_set_use_markup(GTK_LABEL(labelPickHistory), TRUE);
  gtk_label_set_selectable(GTK_LABEL(labelPickHistory), TRUE);
  gtk_widget_set_margin_start(labelPickHistory, 15);
  labelPickError = lookup_widget(main->interactiveDialog, "pickComment");
  gtk_widget_set_name(labelPickError, "label_error");
  wd = lookup_widget(main->interactiveDialog, "viewportPick");
  gtk_widget_set_name(wd, "message_viewport");
  wd = lookup_widget(main->interactiveDialog, "checkDrawDistance");
  gtk_widget_set_name(wd, "message_radio");
  g_signal_connect(G_OBJECT(wd), "toggled",
		   G_CALLBACK(onDrawDistanceChecked), (gpointer)marks);
  wd = lookup_widget(main->interactiveDialog, "buttonEraseDistances");
  g_signal_connect(G_OBJECT(wd), "clicked",
		   G_CALLBACK(onEraseDistanceClicked), (gpointer)marks);
  hboxMarks = lookup_widget(main->interactiveDialog, "hboxMarks");
  g_object_bind_property_full(marks, "highlight", hboxMarks, "sensitive",
                              G_BINDING_SYNC_CREATE, _haveNodes, NULL,
                              (gpointer)0, (GDestroyNotify)0);
  wd = lookup_widget(main->interactiveDialog, "buttonEraseMarks");
  g_signal_connect_swapped(G_OBJECT(wd), "clicked",
                           G_CALLBACK(visu_gl_ext_marks_unHighlight), (gpointer)marks);
  wd = lookup_widget(main->interactiveDialog, "buttonSetMarks");
  g_signal_connect_swapped(G_OBJECT(wd), "clicked",
                           G_CALLBACK(visu_ui_selection_appendHighlightedNodes), listPickedNodes);
  tglMarks = gtk_toggle_button_new();
  gtk_widget_set_tooltip_text(tglMarks, _("Hide nodes depending on highlight status."));
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tglMarks),
                               visu_gl_ext_marks_getHidingMode(marks) != HIDE_NONE);
  image = create_pixmap((GtkWidget*)0, "stock-masking.png");
  gtk_container_add(GTK_CONTAINER(tglMarks), image);
  gtk_box_pack_start(GTK_BOX(hboxMarks), tglMarks, FALSE, FALSE, 0);
  g_signal_connect(G_OBJECT(tglMarks), "toggled",
		   G_CALLBACK(onHighlightHideToggled), marks);
  gtk_box_pack_start(GTK_BOX(hboxMarks), gtk_label_new("("), FALSE, FALSE, 0);
  wd = gtk_radio_button_new_with_mnemonic((GSList*)0, _("_h."));
  g_object_set_data(G_OBJECT(wd), "hiding-mode", GINT_TO_POINTER(HIDE_HIGHLIGHT));
  if (visu_gl_ext_marks_getHidingMode(marks) == HIDE_HIGHLIGHT)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wd), TRUE);
  gtk_widget_set_tooltip_text(wd, _("Hide button will hide highlighted nodes."));
  gtk_widget_set_name(wd, "message_radio");
  gtk_box_pack_start(GTK_BOX(hboxMarks), wd, FALSE, FALSE, 0);
  g_signal_connect(G_OBJECT(wd), "toggled",
		   G_CALLBACK(onHighlightHideStatus), marks);
  wd2 = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(wd), _("_non-h."));
  g_object_set_data(G_OBJECT(wd2), "hiding-mode", GINT_TO_POINTER(HIDE_NON_HIGHLIGHT));
  if (visu_gl_ext_marks_getHidingMode(marks) == HIDE_NON_HIGHLIGHT)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wd2), TRUE);
  gtk_widget_set_tooltip_text(wd2, _("Hide button will hide non-highlighted nodes."));
  gtk_widget_set_name(wd2, "message_radio");
  gtk_box_pack_start(GTK_BOX(hboxMarks), wd2, FALSE, FALSE, 0);
  g_signal_connect(G_OBJECT(wd2), "toggled",
		   G_CALLBACK(onHighlightHideStatus), marks);
  g_object_set_data(G_OBJECT(tglMarks), "hiding-mode", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wd)) ? GINT_TO_POINTER(HIDE_HIGHLIGHT) : GINT_TO_POINTER(HIDE_NON_HIGHLIGHT));
  gtk_box_pack_start(GTK_BOX(hboxMarks), gtk_label_new(")"), FALSE, FALSE, 0);
  lbl = lookup_widget(main->interactiveDialog, "labelMarks");
  gtk_label_set_use_markup(GTK_LABEL(lbl), TRUE);
  g_object_bind_property_full(marks, "highlight", lbl, "label",
                              G_BINDING_SYNC_CREATE, _toLblMarks, NULL,
                              (gpointer)0, (GDestroyNotify)0);
  gtk_widget_show_all(hboxMarks);

  g_object_bind_property(scene, "data", listPickedNodes, "model",
                         G_BINDING_SYNC_CREATE);
  visu_ui_selection_setHighlightModel(listPickedNodes, marks);
  g_object_bind_property_full(listPickedNodes, "selection", infos, "selection",
                              G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
                              _toSelection, _toSelection,
                              g_object_ref(radioDrawSelected), (GDestroyNotify)g_object_unref);

  /* Get the current VisuData object. */
  g_signal_connect_object(scene, "notify::data",
                          G_CALLBACK(onDataNotify), treeviewPickedNodes, 0);
  g_signal_connect_swapped(treeviewPickedNodes, "destroy",
                           G_CALLBACK(_setData), (gpointer)0);
  onDataNotify(scene);

  return (GtkWidget*)0;
}
/**
 * visu_ui_interactive_pick_start:
 * @window: a #VisuUiRenderingWindow object.
 *
 * Initialise a pick session.
 */
void visu_ui_interactive_pick_start(VisuUiRenderingWindow *window)
{
  VisuInteractive *inter;

  g_object_get(window, "interactive", &inter, NULL);
  visu_ui_rendering_window_pushInteractive(window, interPick);
  visu_interactive_setReferences(interPick, inter);
  g_object_unref(inter);
}
/**
 * visu_ui_interactive_pick_stop:
 * @window: a #VisuUiRenderingWindow object.
 *
 * Finalise a pick session.
 */
void visu_ui_interactive_pick_stop(VisuUiRenderingWindow *window)
{
  VisuInteractive *inter;

  visu_ui_rendering_window_popInteractive(window, interPick);
  g_object_get(window, "interactive", &inter, NULL);
  visu_interactive_setReferences(inter, interPick);
  g_object_unref(inter);
}
static void onNodeSelection(VisuInteractive *inter _U_, VisuInteractivePick pick, 
			    VisuData *dataObj, VisuNode *node0, VisuNode *node1,
                            VisuNode *node2, gpointer data _U_)
{
  gchar *errors;
  GString *infos;
  float posRef1[3], posRef2[3], posSelect[3];
  double dx, dy, dz, dr, dx1, dy1, dz1, dr1,  dx2, dy2, dz2, dr2;
  double ang;

  DBG_fprintf(stderr, "Gtk Pick: one measurement done (%d).\n", pick);

  gtk_tree_selection_unselect_all(gtk_tree_view_get_selection
				  (GTK_TREE_VIEW(treeviewPickedNodes)));

  /* Update the texts. */
  infos  = g_string_new("");
  errors = (gchar*)0;
  if (pick == PICK_DISTANCE || pick == PICK_ANGLE ||
      pick == PICK_REFERENCE_1 || pick == PICK_REFERENCE_2)
    g_string_append_printf(infos,
			   _("Reference node\t"
			     " <span font_desc=\"monospace\"><b>#%d</b></span>\n"
			     "<span font_desc=\"monospace\" size=\"small\">"
			     "  ( x  = %7.3f ;  y  = %7.3f ;  z  = %7.3f)\n"
			     "</span>"),
			   node1->number + 1, node1->xyz[0],
			   node1->xyz[1],     node1->xyz[2]);
  if (pick == PICK_ANGLE || pick == PICK_REFERENCE_2)
    {
      visu_data_getNodePosition(dataObj, node1, posRef1);
      visu_data_getNodePosition(dataObj, node2, posRef2);
      dx = posRef2[0] - posRef1[0];
      dy = posRef2[1] - posRef1[1];
      dz = posRef2[2] - posRef1[2];
      dr = sqrt(dx*dx + dy*dy + dz*dz);
      g_string_append_printf(infos,
			     _("2nd Reference node\t"
			       " <span font_desc=\"monospace\"><b>#%d</b>\t"
			       " (<i>i.e.</i> dr = %7.3f)</span>\n"
			       "<span font_desc=\"monospace\" size=\"small\">"
			       "  ( x  = %7.3f ;  y  = %7.3f ;  z  = %7.3f)\n"
			       "  (\316\264x  = %7.3f ; \316\264y  = %7.3f ; \316\264z  = %7.3f)\n"
			       "</span>"),
			     node2->number + 1, dr, node2->xyz[0],
			     node2->xyz[1], node2->xyz[2], dx, dy, dz);
    }
  if (pick == PICK_SELECTED)
    g_string_append_printf(infos,
			   _("Newly picked node\t"
			     " <span font_desc=\"monospace\"><b>#%d</b></span>\n"
			     "<span font_desc=\"monospace\" size=\"small\">"
			     "  ( x  = %7.3f ;  y  = %7.3f ;  z  = %7.3f)\n"
			     "</span>"),
			   node0->number + 1, node0->xyz[0],
			   node0->xyz[1], node0->xyz[2]);
  else if (pick == PICK_DISTANCE)
    {
      visu_data_getNodePosition(dataObj, node0, posSelect);
      visu_data_getNodePosition(dataObj, node1, posRef1);
      dx = posSelect[0] - posRef1[0];
      dy = posSelect[1] - posRef1[1];
      dz = posSelect[2] - posRef1[2];
      dr = sqrt(dx*dx + dy*dy + dz*dz);
      g_string_append_printf(infos,
			     _("Newly picked node\t"
			       " <span font_desc=\"monospace\"><b>#%d</b>\t"
			       " (<i>i.e.</i> dr = %7.3f)</span>\n"
			       "<span font_desc=\"monospace\" size=\"small\">"
			       "  ( x  = %7.3f ;  y  = %7.3f ;  z  = %7.3f)\n"
			       "  (\316\264x  = %7.3f ; \316\264y  = %7.3f ; \316\264z  = %7.3f)\n"
			       "</span>"),
			     node0->number + 1, dr, node0->xyz[0],
			     node0->xyz[1], node0->xyz[2], dx, dy, dz);
    }
  else if (pick == PICK_ANGLE)
    {
      visu_data_getNodePosition(dataObj, node0, posSelect);
      visu_data_getNodePosition(dataObj, node1, posRef1);
      visu_data_getNodePosition(dataObj, node2, posRef2);
      dx1 = posSelect[0] - posRef1[0];
      dy1 = posSelect[1] - posRef1[1];
      dz1 = posSelect[2] - posRef1[2];
      dx2 = posRef2[0] - posRef1[0];
      dy2 = posRef2[1] - posRef1[1];
      dz2 = posRef2[2] - posRef1[2];
      dr1 = sqrt(dx1*dx1 + dy1*dy1 + dz1*dz1);
      g_string_append_printf(infos,
			     _("Newly picked node\t"
			       " <span font_desc=\"monospace\"><b>#%d</b>\t"
			       " (<i>i.e.</i> dr = %7.3f)</span>\n"
			       "<span font_desc=\"monospace\" size=\"small\">"
			       "  ( x  = %7.3f ;  y  = %7.3f ;  z  = %7.3f)\n"
			       "  (\316\264x1 = %7.3f ; \316\264y1 = %7.3f ; \316\264z1 = %7.3f)\n"
			       "  (\316\264x2 = %7.3f ; \316\264y2 = %7.3f ; \316\264z2 = %7.3f)\n"),
			     node0->number + 1, dr1, node0->xyz[0],
			     node0->xyz[1], node0->xyz[2],
			     dx1, dy1, dz1, dx2, dy2, dz2);
      dr2 = sqrt(dx2*dx2 + dy2*dy2 + dz2*dz2);
      ang = acos((dx2*dx1+dy2*dy1+dz2*dz1)/(dr2*dr1))/TOOL_PI180;
      g_string_append_printf(infos,
			     _("  angle (Ref-Ref2, Ref-New) = %5.2f degrees"
			       "</span>"), ang);
    }
  if (pick == PICK_UNREFERENCE_1 || pick == PICK_UNREFERENCE_2)
    errors = g_strdup_printf(_("Unset reference %d."),
			     (pick == PICK_UNREFERENCE_1)?1:2);

  /* Update the remaining of the interface. */
  switch (pick)
    {
    case PICK_SELECTED:
    case PICK_DISTANCE:
    case PICK_ANGLE:
    case PICK_HIGHLIGHT:
      /* Add clicked node to list. */
      visu_ui_selection_add(listPickedNodes, node0->number);
      /* Falls through. */
    case PICK_UNREFERENCE_1:
    case PICK_UNREFERENCE_2:
    case PICK_REFERENCE_1:
    case PICK_REFERENCE_2:
      if (infos)
	{
	  gtk_label_set_markup(GTK_LABEL(labelPickOut), infos->str);
	  g_string_free(infos, TRUE);
	}
      gtk_label_set_text(GTK_LABEL(labelPickError), errors);
      if (errors)
	g_free(errors);
      return;
    case PICK_INFORMATION:
      break;
    default:
      g_warning("Not a pick event!");
    }
  return;
}
static void onSelectionError(VisuInteractive *inter _U_,
			     VisuInteractivePickError error, gpointer data _U_)
{
  switch (error)
    {
    case PICK_ERROR_NO_SELECTION:
      gtk_label_set_text(GTK_LABEL(labelPickError), _("No node has been selected."));
      return;
    case PICK_ERROR_SAME_REF:
      gtk_label_set_text(GTK_LABEL(labelPickError), _("Picked reference and second"
						      " reference are the same."));
      return;
    case PICK_ERROR_REF1:
      gtk_label_set_text(GTK_LABEL(labelPickError), _("Can't pick a second reference"
						      " without any existing first one."));
      return;
    case PICK_ERROR_REF2:
      gtk_label_set_text(GTK_LABEL(labelPickError), _("Can't remove first reference"
						      " before removing the second one."));
      return;
    default:
      return;
    }
}
/**
 * visu_ui_interactive_pick_getSelection:
 *
 * Retrieves the #VisuUiSelection of this panel.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuUiSelection object.
 **/
VisuUiSelection* visu_ui_interactive_pick_getSelection(void)
{
  return listPickedNodes;
}
/**
 * visu_ui_interactive_pick_getSelectionLabel:
 *
 * Retrieves the #GtkWidget used as label for the selection.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #GtkWidget object.
 **/
GtkWidget* visu_ui_interactive_pick_getSelectionLabel(void)
{
  return labelSelection;
}

/*********************/
/* Private routines. */
/*********************/
static void displayProp(GtkTreeViewColumn *column _U_, GtkCellRenderer *renderer,
                        GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
  VisuNodeValues *values = VISU_NODE_VALUES(data);
  gint number;
  gchar *text, *none;
  gboolean editable;

  gtk_tree_model_get(model, iter, VISU_UI_SELECTION_NUMBER, &number, -1);
  text = visu_node_values_toStringFromId(values, (guint)(number - 1));
  if (!text || text[0] == '\0')
    {
      none = g_strdup_printf("<i>%s</i>", _("None"));
      g_object_set(renderer, "markup", none, NULL);
      g_free(none);
    }
  else
    g_object_set(renderer, "markup", text, NULL);
  editable = visu_node_values_getEditable(values);
  g_object_set(renderer, "editable", editable, NULL);
  if (editable)
    g_object_set(renderer, "foreground", GTK_PICK_EDITABLE_NODE, NULL);
  g_free(text);
}

/*************/
/* Callbacks */
/*************/
static void onNodeValueChanged(VisuNodeValues *values _U_, VisuNode *node)
{
  GtkTreeIter iter;
  GtkTreePath *path;
  gboolean valid;
  guint number;

  for (valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(listPickedNodes), &iter);
       valid; valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(listPickedNodes), &iter))
    {
      gtk_tree_model_get(GTK_TREE_MODEL(listPickedNodes), &iter,
                         VISU_UI_SELECTION_NUMBER, &number, -1);
      if (!node || number == node->number + 1)
        {
          path = gtk_tree_model_get_path(GTK_TREE_MODEL(listPickedNodes), &iter);
          gtk_tree_model_row_changed(GTK_TREE_MODEL(listPickedNodes), path, &iter);
          gtk_tree_path_free(path);
          if (node)
            return;
        }
    }
}
static void onEditedValue(GtkCellRendererText *cellrenderertext _U_,
                          gchar *path, gchar *text, gpointer user_data)
{
  GtkTreeIter iter;
  guint number;
  VisuNodeValues *values = VISU_NODE_VALUES(user_data);
  gboolean valid;

  if (text && !strcmp(text, _("None")))
    return;

  valid = gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(listPickedNodes),
                                              &iter, path);
  g_return_if_fail(valid);

  gtk_tree_model_get(GTK_TREE_MODEL(listPickedNodes), &iter,
		     VISU_UI_SELECTION_NUMBER, &number, -1);

  valid = visu_node_values_setFromStringForId(values, number - 1, text);
  if (!valid)
    visu_ui_raiseWarning(_("Reading values"),
			 _("Wrong format. Impossible to parse the data associated"
			   " to the selected node."), (GtkWindow*)0);
}
static void onMeasurementList(VisuGlExtMarks *marks, VisuData *dataObj, gpointer data _U_)
{
  gchar *lbl;
  struct _PickHistory *hist;
  GString *str;
  GList *lst, *rev;

  if (!dataObj)
    return;
  lbl = visu_gl_ext_marks_getMeasurementStrings(marks);
  if (!lbl)
    return;

  if (!pickHistory || ((struct _PickHistory*)pickHistory->data)->dataObj != dataObj)
    {
      hist = g_malloc(sizeof(struct _PickHistory));
      hist->dataObj = dataObj;
      pickHistory = g_list_prepend(pickHistory, (gpointer)hist);
    }
  else
    {
      hist = (struct _PickHistory*)pickHistory->data;
      g_free(hist->str);
    }
  hist->str = lbl;

  if (labelPickHistory)
    {
      lbl = visu_gl_ext_marks_getMeasurementLabels(marks);
      str = g_string_new(lbl);
      g_free(lbl);
      rev = g_list_reverse(pickHistory);
      for (lst = rev; lst; lst = g_list_next(lst))
        g_string_append(str, ((struct _PickHistory*)lst->data)->str);
      pickHistory = g_list_reverse(rev);
      lbl = g_markup_printf_escaped
        ("Measurement history, first 6 values (<b>%d entry(ies)</b>):\n"
         "<span font_desc=\"monospace\" size=\"small\">%s</span>",
         g_list_length(pickHistory), str->str);
      g_string_free(str, TRUE);
      gtk_label_set_markup(GTK_LABEL(labelPickHistory), lbl);
      g_free(lbl);
    }
}
static void _togglePath(VisuUiSelection *selection, gchar *path)
{
  gboolean valid;
  GtkTreeIter iter;

  valid = gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(selection),
					      &iter, path);
  g_return_if_fail(valid);

  visu_ui_selection_highlight(selection, &iter, MARKS_STATUS_TOGGLE);
}
static void onHighlightHideToggled(GtkToggleButton *button, gpointer user_data)
{
  VisuGlExtMarksHidingModes mode;

  if (gtk_toggle_button_get_active(button))
    {
      mode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "hiding-mode"));
      visu_gl_ext_marks_setHidingMode(VISU_GL_EXT_MARKS(user_data), mode);
    }
  else
    visu_gl_ext_marks_setHidingMode(VISU_GL_EXT_MARKS(user_data), HIDE_NONE);
}
static void onHighlightHideStatus(GtkButton *button, gpointer user_data)
{
  VisuGlExtMarksHidingModes mode;

  if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
    return;

  mode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "hiding-mode"));
  g_object_set_data(G_OBJECT(tglMarks), "hiding-mode", GINT_TO_POINTER(mode));
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tglMarks)))
    visu_gl_ext_marks_setHidingMode(VISU_GL_EXT_MARKS(user_data), mode);
}
static void onDrawDistanceChecked(GtkToggleButton* button, gpointer data)
{
  visu_gl_ext_marks_setDrawValues(VISU_GL_EXT_MARKS(data),
                                  gtk_toggle_button_get_active(button));
}
static void onEraseDistanceClicked(GtkButton *button _U_, gpointer user_data)
{
  DBG_fprintf(stderr, "Gtk Pick: clicked on 'erase all measures' button.\n");
  visu_gl_ext_marks_removeMeasures(VISU_GL_EXT_MARKS(user_data), -1);
}
static void onDataNotify(VisuGlNodeScene *scene)
{
  _setData(visu_gl_node_scene_getData(scene));
}
static void _setData(VisuData *dataObj)
{
  GList *lst, *tmpLst;

  DBG_fprintf(stderr, "Gtk Pick: set data model to %p.\n", (gpointer)dataObj);

  if (currentData == dataObj)
    return;

  lst = gtk_tree_view_get_columns(GTK_TREE_VIEW(treeviewPickedNodes));
  for (tmpLst = g_list_next(lst); tmpLst; tmpLst = g_list_next(tmpLst))
    gtk_tree_view_remove_column(GTK_TREE_VIEW(treeviewPickedNodes),
                                GTK_TREE_VIEW_COLUMN(tmpLst->data));
  if (lst) g_list_free(lst);
  
  if (currentData)
    {
      g_signal_handler_disconnect(G_OBJECT(currentData), propAdd_signal);
      g_signal_handler_disconnect(G_OBJECT(currentData), propRemoved_signal);
      g_object_unref(currentData);
    }
  currentData = dataObj;
  if (dataObj)
    {
      g_object_ref(dataObj);
      DBG_fprintf(stderr, " | dataObj has %d ref counts.\n",
                  G_OBJECT(dataObj)->ref_count);
      propAdd_signal = g_signal_connect_swapped
        (G_OBJECT(dataObj), "node-properties-added",
         G_CALLBACK(_addColumn), treeviewPickedNodes);
      propRemoved_signal = g_signal_connect_swapped
        (G_OBJECT(dataObj), "node-properties-removed",
         G_CALLBACK(_removeColumn), treeviewPickedNodes);
      
      lst = visu_data_getAllNodeProperties(dataObj);
      for (tmpLst = lst; tmpLst; tmpLst = g_list_next(tmpLst))
        _addColumn(GTK_TREE_VIEW(treeviewPickedNodes), VISU_NODE_VALUES(tmpLst->data));
      g_list_free(lst);
      DBG_fprintf(stderr, " | dataObj has %d ref counts.\n",
                  G_OBJECT(dataObj)->ref_count);
    }
}
static void _addColumn(GtkTreeView *view, VisuNodeValues *prop)
{
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkWidget *lbl;
  const gchar *title;
  gchar *markup;

  renderer = gtk_cell_renderer_text_new();
  g_signal_connect(G_OBJECT(renderer), "edited",
                   G_CALLBACK(onEditedValue), prop);
  g_object_set(G_OBJECT(renderer), "scale", 0.8, NULL);
  title = visu_node_values_getLabel(prop);
  DBG_fprintf(stderr, " | add column '%s'.\n", title);
  lbl = gtk_label_new("");
  markup = g_markup_printf_escaped("<span size=\"smaller\">%s</span>", title);
  gtk_label_set_markup(GTK_LABEL(lbl), markup);
  g_free(markup);
  gtk_widget_show(lbl);

  column = gtk_tree_view_column_new();
  g_object_set_data(G_OBJECT(column), "NodeProperties", prop);
  gtk_tree_view_column_set_widget(column, lbl);
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_set_cell_data_func(column, renderer,
                                          displayProp, prop, (GDestroyNotify)0);
  gtk_tree_view_column_set_visible(column, TRUE);
  if (VISU_IS_NODE_VALUES_ID(prop))
    gtk_tree_view_column_set_sort_column_id(column, VISU_UI_SELECTION_NUMBER);
  gtk_tree_view_append_column(view, column);
  g_signal_connect_object(G_OBJECT(prop), "changed",
                          G_CALLBACK(onNodeValueChanged), renderer, 0);
}
static void _removeColumn(GtkTreeView *view, VisuNodeValues *prop)
{
  GList *lst, *tmpLst;
  gpointer myprop;

  lst = gtk_tree_view_get_columns(view);
  for (tmpLst = lst; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      myprop = g_object_get_data(G_OBJECT(tmpLst->data), "NodeProperties");
      if (myprop == (gpointer)prop)
        {
          gtk_tree_view_remove_column(view, GTK_TREE_VIEW_COLUMN(tmpLst->data));
          break;
        }
    }
  g_list_free(lst);
}
static gboolean onTreeviewInfosKey(GtkWidget *widget, GdkEventKey *event,
				   gpointer user_data _U_)
{
  GList *selectedPaths;
  GtkTreeModel *model;

  DBG_fprintf(stderr, "Gtk Pick: key pressed on treeview '%d'.\n", event->keyval);
  
  if (event->keyval != GDK_KEY_Delete && event->keyval != GDK_KEY_BackSpace)
    return FALSE;

  selectedPaths = gtk_tree_selection_get_selected_rows
    (gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), &model);
  visu_ui_selection_removePaths(VISU_UI_SELECTION(model), selectedPaths);
  g_list_free_full(selectedPaths, (GDestroyNotify)gtk_tree_path_free);
  return TRUE;
}

static gboolean onLoadXML(const gchar *filename, GError **error)
{
  return visu_gl_node_scene_setMarksFromFile(visu_ui_rendering_window_getGlScene(visu_ui_main_class_getDefaultRendering()), filename, error);
}
static gboolean onSaveXML(const gchar *filename, GError **error)
{
  return visu_gl_node_scene_marksToFile(visu_ui_rendering_window_getGlScene(visu_ui_main_class_getDefaultRendering()), filename, error);
}
