/* This is -*- C -*- */
/* $Id: guppi-metric-entry.c,v 1.11 2002/01/14 05:01:23 trow Exp $ */

/*
 * guppi-metric-entry.c
 *
 * Copyright (C) 2000 EMC Capital Management, Inc.
 * Copyright (C) 2001 The Free Software Foundation
 *
 * Developed by Jon Trowbridge <trow@gnu.org> and
 * Havoc Pennington <hp@pobox.com>.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <config.h>
#include "guppi-metric-entry.h"

#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include <glib.h>

#include <gtk/gtkeditable.h>
#include <gtk/gtkentry.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkmenuitem.h>
#include <gtk/gtkoptionmenu.h>

#include "guppi-defaults.h"
#include "guppi-memory.h"


static GtkObjectClass *parent_class = NULL;

enum {
  CHANGED_VALUE,
  CHANGED_UNITS,
  LAST_SIGNAL
};

guint me_signals[LAST_SIGNAL] = { 0 };

static void
guppi_metric_entry_destroy (GtkObject *obj)
{
  if (parent_class->destroy)
    parent_class->destroy (obj);
}

static void
guppi_metric_entry_finalize (GtkObject *obj)
{
  if (parent_class->finalize)
    parent_class->finalize (obj);
}

/**********************************************************************/

static void
read_value (GtkEditable *editable, gpointer data)
{
  GuppiMetricEntry *me = GUPPI_METRIC_ENTRY (data);
  gchar *txt = gtk_editable_get_chars (editable, 0, -1);
  double x = atof (txt);
  guppi_metric_entry_set_pt_value (me, guppi_to_pt (x, me->displayed_units));
  guppi_free (txt);
}

static void
write_value (GuppiMetricEntry *me)
{
  double x = guppi_from_pt (guppi_metric_entry_pt_value (me), me->displayed_units);
  gchar *str = g_strdup_printf ("%g", x);
  gint pos = 0;
  gtk_editable_delete_text (GTK_EDITABLE (me->entry), 0, -1);
  gtk_editable_insert_text (GTK_EDITABLE (me->entry), str, strlen (str), &pos);
  g_free (str);
}

static void
changed_units (GuppiMetricEntry *me)
{
  gchar *str;
  double new_pt_val;

  new_pt_val = guppi_from_pt (me->pt_val, me->displayed_units);
  str = guppi_strdup_printf ("%g", new_pt_val);
  gtk_entry_set_text (GTK_ENTRY (me->entry), str);
  guppi_free (str);
}

/**********************************************************************/

static void
guppi_metric_entry_class_init (GuppiMetricEntryClass *klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *) klass;

  parent_class = gtk_type_class (GTK_TYPE_HBOX);

  object_class->destroy = guppi_metric_entry_destroy;
  object_class->finalize = guppi_metric_entry_finalize;

  klass->changed_units = changed_units;

  me_signals[CHANGED_VALUE] =
    gtk_signal_new ("changed_value",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiMetricEntryClass, changed_value),
		    gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);

  me_signals[CHANGED_UNITS] =
    gtk_signal_new ("changed_units",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiMetricEntryClass, changed_units),
		    gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);

  gtk_object_class_add_signals (object_class, me_signals, LAST_SIGNAL);

}

static void
guppi_metric_entry_init (GuppiMetricEntry *obj)
{

}

GtkType
guppi_metric_entry_get_type (void)
{
  static GtkType guppi_metric_entry_type = 0;
  if (!guppi_metric_entry_type) {
    static const GtkTypeInfo guppi_metric_entry_info = {
      "GuppiMetricEntry",
      sizeof (GuppiMetricEntry),
      sizeof (GuppiMetricEntryClass),
      (GtkClassInitFunc) guppi_metric_entry_class_init,
      (GtkObjectInitFunc) guppi_metric_entry_init,
      NULL, NULL, (GtkClassInitFunc) NULL
    };
    guppi_metric_entry_type =
      gtk_type_unique (GTK_TYPE_HBOX, &guppi_metric_entry_info);
  }
  return guppi_metric_entry_type;
}

static void
unit_select_cb (GtkWidget *w, gpointer ptr)
{
  GuppiMetricEntry *me = GUPPI_METRIC_ENTRY (ptr);
  guppi_metric_t units;

  /* Make sure that what appears in the entry is actually what we have stored
     as the point value. */
  read_value (GTK_EDITABLE (me->entry), me);

  units = (guppi_metric_t)
    GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT (w), "units"));
  guppi_metric_entry_set_displayed_units (me, units);
}

static gboolean
valid_number (GuppiMetricEntry *me, const gchar *str)
{
  gchar *end_ptr;
  gint len = strlen (str);
  double x;

  if (len == 1 && *str == '-' && !me->no_negatives)
    return TRUE;

  if (len == 1 && *str == '.')
    return TRUE;

  x = strtod (str, &end_ptr);

  if (end_ptr == NULL)
    return FALSE;

  if (*end_ptr != '\0')
    return FALSE;

  if (me->no_negatives && x < 0)
    return FALSE;

  if (me->no_zero && x == 0)
    return FALSE;

  return TRUE;
}

static void
insert_text_handler (GtkEditable *editable, const gchar *text, gint length,
		     gint *position, gpointer data)
{
  GuppiMetricEntry *me = GUPPI_METRIC_ENTRY (data);
  gint i, j;
  gchar *result = guppi_new (gchar, length);
  gchar *pre_insert_text;
  gchar *post_insert_text;
  gint saved_pos;

  /* Filter out any characters that couldn't possibly be part of a number. */
  j = 0;
  for (i = 0; i < length; i++)
    if (isdigit ((guchar)text[i]) || text[i] == '.' || text[i] == ','
	|| text[i] == '-') {
      result[j] = text[i];
      ++j;
    }

  gtk_signal_handler_block_by_func (GTK_OBJECT (editable),
				    GTK_SIGNAL_FUNC (insert_text_handler),
				    data);

  pre_insert_text = gtk_editable_get_chars (editable, 0, -1);
  saved_pos = gtk_editable_get_position (editable);
  gtk_editable_insert_text (editable, result, j, position);
  post_insert_text = gtk_editable_get_chars (editable, 0, -1);

  /* Check that our entry is a valid number.  If not, we undo the insert. */
  if (!valid_number (me, post_insert_text)) {
    gint pos = 0;

    gtk_editable_delete_text (editable, 0, -1);
    gtk_editable_insert_text (editable, pre_insert_text,
			      strlen (pre_insert_text), &pos);

    /* This doesn't seem to work... */
    gtk_editable_set_position (editable, saved_pos);

  } else {

    read_value (editable, me);

  }

  gtk_signal_handler_unblock_by_func (GTK_OBJECT (editable),
				      GTK_SIGNAL_FUNC (insert_text_handler),
				      data);

  gtk_signal_emit_stop_by_name (GTK_OBJECT (editable), "insert_text");

  guppi_free (result);
  guppi_free (pre_insert_text);
  guppi_free (post_insert_text);
}

static void
delete_text_handler (GtkEditable *editable, gint start_pos, gint end_pos,
		     gpointer user_data)
{
  read_value (editable, GUPPI_METRIC_ENTRY (user_data));
}

static void
focus_out (GtkEditable *editable, GdkEventFocus *ev, gpointer data)
{
  read_value (editable, data);
}

void
guppi_metric_entry_construct (GuppiMetricEntry *me)
{
  const guppi_metric_t units[] = {
    GUPPI_PT, GUPPI_IN, GUPPI_CM, GUPPI_MM, GUPPI_PX,
    GUPPI_INVALID_METRIC
  };

  gint i, j = 0;
  GtkWidget *menu;

  g_return_if_fail (me != NULL && GUPPI_IS_METRIC_ENTRY (me));

  me->pt_val = 0;
  me->displayed_units = guppi_default_units ();

  me->entry = gtk_entry_new ();
  gtk_signal_connect (GTK_OBJECT (me->entry),
		      "insert_text",
		      GTK_SIGNAL_FUNC (insert_text_handler), me);
  gtk_signal_connect (GTK_OBJECT (me->entry),
		      "delete_text",
		      GTK_SIGNAL_FUNC (delete_text_handler), me);
  gtk_signal_connect (GTK_OBJECT (me->entry),
		      "activate", GTK_SIGNAL_FUNC (read_value), me);
  gtk_signal_connect (GTK_OBJECT (me->entry),
		      "focus_out_event", GTK_SIGNAL_FUNC (focus_out), me);

  gtk_widget_show (me->entry);

  menu = gtk_menu_new ();

  for (i = 0; units[i] != GUPPI_INVALID_METRIC; ++i) {
    GtkWidget *mi;

    if (units[i] == me->displayed_units)
      j = i;

    mi = gtk_menu_item_new_with_label (guppi_metric_name (units[i]));
    gtk_object_set_data (GTK_OBJECT (mi), "units",
			 GINT_TO_POINTER (units[i]));
    gtk_signal_connect (GTK_OBJECT (mi), "activate",
			GTK_SIGNAL_FUNC (unit_select_cb), me);
    gtk_widget_show (mi);

    gtk_menu_append (GTK_MENU (menu), mi);
  }

  me->units_opt = gtk_option_menu_new ();
  gtk_option_menu_set_menu (GTK_OPTION_MENU (me->units_opt), menu);
  gtk_option_menu_set_history (GTK_OPTION_MENU (me->units_opt), j);

  gtk_widget_show (menu);
  gtk_widget_show (me->units_opt);

  gtk_box_pack_start (GTK_BOX (me), me->entry, TRUE, TRUE, 0);
  gtk_box_pack_end (GTK_BOX (me), me->units_opt, FALSE, FALSE, 0);
}

GtkWidget *
guppi_metric_entry_new (void)
{
  GtkObject *obj = guppi_type_new (guppi_metric_entry_get_type ());

  guppi_metric_entry_construct (GUPPI_METRIC_ENTRY (obj));

  return GTK_WIDGET (obj);
}

double
guppi_metric_entry_pt_value (GuppiMetricEntry *me)
{
  g_return_val_if_fail (me != NULL && GUPPI_IS_METRIC_ENTRY (me), 0);

  return me->pt_val;
}

void
guppi_metric_entry_set_pt_value (GuppiMetricEntry *me, double x)
{
  g_return_if_fail (me != NULL && GUPPI_IS_METRIC_ENTRY (me));
  if (me->no_negatives)
    g_return_if_fail (x >= 0);
  if (me->no_zero)
    g_return_if_fail (x != 0);

  if (x != me->pt_val) {
    me->pt_val = x;
    gtk_signal_emit (GTK_OBJECT (me), me_signals[CHANGED_VALUE]);
    write_value (me);
  }
}

guppi_metric_t
guppi_metric_entry_displayed_units (GuppiMetricEntry *me)
{
  g_return_val_if_fail (me != NULL && GUPPI_IS_METRIC_ENTRY (me),
			GUPPI_INVALID_METRIC);

  return me->displayed_units;
}

void
guppi_metric_entry_set_displayed_units (GuppiMetricEntry *me,
					guppi_metric_t units)
{
  g_return_if_fail (me != NULL && GUPPI_IS_METRIC_ENTRY (me));
  g_return_if_fail (units != GUPPI_INVALID_METRIC);

  if (me->displayed_units != units) {
    me->displayed_units = units;
    gtk_signal_emit (GTK_OBJECT (me), me_signals[CHANGED_UNITS]);
  }
}



/* $Id: guppi-metric-entry.c,v 1.11 2002/01/14 05:01:23 trow Exp $ */


syntax highlighted by Code2HTML, v. 0.9.1