/*
**  $Id: snmp_dialog.c,v 1.8 2000/07/23 00:55:29 remlali Exp $
**
**  GXSNMP -- An snmp management application
**
**  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, Cambridge, MA 02139, USA.
*/

/*
**  snmp_dialog.c defines the dialog that is used to enter and edit
**  information about SNMP configurations.
*/
#ifdef HAVE_CONFIG_H
#include <gnome.h>
#endif
#include "gnome-property-dialog.h"
#include "dbapi.h"
#include "gxsnmp/gxsnmp_dbapi.h"
#include "snmp_dialog.h"
#include "main.h"			/* Needed for gxsnmp struct */

#include "snmp_panel.h"
#include "panel_utility.h"
#include "gxsnmp_window.h"

#include "debug.h"

extern gxsnmp *app_info;

/******************************************************************************
**
**  Static Data
**
******************************************************************************/

#define CLIST_COLUMNS 2

static char * titles [] = { "Configuration Name", "Interfaces" };

static gint title_widths[2] = { 15, 6 };

/******************************************************************************
**
**  Forward declarations and callback functions
**
******************************************************************************/

static void snmp_dialog_class_init (GXsnmp_snmp_dialogClass * klass);

static void snmp_dialog_init	   (GXsnmp_snmp_dialog      * dialog);

static void snmp_refresh_cb	   (G_sqldb_table	    * table,
				    gpointer		      row,
				    G_sqldb_cb_type	      type,
				    gpointer		      data);

static void snmp_refresh_row       (GXsnmp_snmp_dialog      * dialog,
				    DB_snmp 		    * dbs);

static void snmp_select_row_cb 	   (GtkWidget 	            * widget,
                    		    gint           	      row,
                   		    gint             	      column,
                  		    GdkEventButton 	    * event,
                   		    gpointer         	      data);

static void snmp_changed_cb 	   (GtkWidget 		    * widget, 
				    gpointer 	 	      data);

static void snmp_renamed_cb	   (GtkWidget		    * widget,
				    gpointer		      data);

static void snmp_add_button_cb 	   (GtkWidget 		    * widget,
				    gpointer		      data);

static void snmp_delete_button_cb  (GtkWidget               * widget, 
                                    gpointer                  data);

static void snmp_apply_cb          (GtkWidget 		    * widget,
				    gpointer		      data);

static gint snmp_close_cb	   (GtkWidget		    * widget,
				    gpointer		      data);

static void snmp_destroy_object    (DB_snmp		    * dbs);

/*****************************************************************************
**
**  gxsnmp_snmp_dialog_get_type()
**
*****************************************************************************/

guint
gxsnmp_snmp_dialog_get_type ()
{
  static guint widget_type = 0;
  D_FUNC_START;
  if (!widget_type)
    {
      GtkTypeInfo widget_info =
      {
        "GXsnmp_snmp_dialog",
        sizeof (GXsnmp_snmp_dialog),
        sizeof (GXsnmp_snmp_dialogClass),
        (GtkClassInitFunc) snmp_dialog_class_init,
        (GtkObjectInitFunc) snmp_dialog_init,
        (GtkArgSetFunc) NULL,
        (GtkArgGetFunc) NULL
      };
      d_print (DEBUG_TRACE, "Registering new widget type\n");
      widget_type = gtk_type_unique (gnome_property_dialog_get_type (), 
				     &widget_info);
    }
  D_FUNC_END;
  return widget_type;
}

/*****************************************************************************
**
**  The class initialization subroutine
**
*****************************************************************************/

static void
snmp_dialog_class_init (GXsnmp_snmp_dialogClass *class)
{
  d_print (DEBUG_TRACE, "\n");
}

/*****************************************************************************
**
**  The widget initialization subroutine
**
*****************************************************************************/

static void
snmp_dialog_init (GXsnmp_snmp_dialog *dialog)
{
  GtkWidget * label;
  GtkWidget * button_box;
  GtkWidget * pixmap;
  GtkWidget * vbox;
  GtkWidget * hbox;
  gint        i, c_width, c_height;
  D_FUNC_START;
/*
**  First build the widget structure
*/

  dialog->table = gtk_table_new (3, 2, FALSE);
  gtk_box_pack_start (GTK_BOX (GNOME_DIALOG (dialog)->vbox),
		      dialog->table, TRUE, TRUE, 0);
  gtk_widget_show (dialog->table);

  c_width  = gdk_string_width (dialog->table->style->font, "xW") / 2;
  c_height = dialog->table->style->font->ascent + 
             dialog->table->style->font->descent;

  vbox = gtk_vbox_new (FALSE, 8);
  gtk_table_attach (GTK_TABLE (dialog->table), vbox, 0, 1, 0, 2,
                    0, GTK_EXPAND | GTK_FILL, 8, 8);
  gtk_widget_show (vbox);

  dialog->scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (dialog->scrolled),
                                  GTK_POLICY_AUTOMATIC,
                                  GTK_POLICY_AUTOMATIC);

  gtk_box_pack_start (GTK_BOX (vbox), dialog->scrolled, TRUE, TRUE, 0);
  gtk_widget_show (dialog->scrolled);

  dialog->snmp_clist = gtk_clist_new_with_titles (CLIST_COLUMNS, titles);
  gtk_clist_set_selection_mode (GTK_CLIST (dialog->snmp_clist),
                                GTK_SELECTION_BROWSE);
  gtk_clist_column_titles_passive (GTK_CLIST (dialog->snmp_clist));
  for (i = 0; i < CLIST_COLUMNS; i++)
    gtk_clist_set_column_width (GTK_CLIST (dialog->snmp_clist), i,
                                c_width * title_widths[i]);
  gtk_container_add (GTK_CONTAINER(dialog->scrolled), dialog->snmp_clist);
  gtk_widget_show (dialog->snmp_clist);

  button_box = gtk_hbutton_box_new ();
  gtk_box_pack_start (GTK_BOX (vbox), button_box, 0, 0, 0);
  gtk_widget_show (button_box);

  pixmap = gnome_stock_pixmap_widget (GTK_WIDGET(dialog), 
				      "gxsnmp/add_host.xpm"); 
  dialog->add_button = gnome_pixmap_button (pixmap, "Add");
  gtk_box_pack_start (GTK_BOX (button_box), dialog->add_button, 
		      TRUE, TRUE, 0);
  gtk_widget_show (dialog->add_button);

  pixmap = gnome_stock_pixmap_widget (GTK_WIDGET(dialog), 
				      "gxsnmp/del_host.xpm");
  dialog->delete_button = gnome_pixmap_button (pixmap, "Delete");
  gtk_box_pack_start (GTK_BOX (button_box), dialog->delete_button, 
		      TRUE, TRUE, 0);
  gtk_widget_show (dialog->delete_button);

  vbox = gtk_vbox_new (FALSE, 4); 
  gtk_table_attach (GTK_TABLE (dialog->table), vbox, 2, 3, 0, 2,
		    0, 0, 8, 8);
  gtk_widget_show (vbox);

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

  label = gtk_label_new ("Configuration Name");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (hbox), label, 0, 0, 0);
  gtk_widget_show(label);

  dialog->name_entry = gtk_entry_new ();
  gtk_box_pack_start (GTK_BOX (hbox), dialog->name_entry, TRUE, TRUE, 0);
  gtk_widget_show (dialog->name_entry);

  dialog->snmp_panel = gxsnmp_snmp_panel_new (app_info->version_names,
                       		              app_info->version_values);
  gtk_box_pack_start (GTK_BOX (vbox), dialog->snmp_panel, 0, 0, 0);
  gtk_widget_show (dialog->snmp_panel);

/*
**  Now connect up all our signals
*/

  gtk_signal_connect (GTK_OBJECT (dialog), "apply",
                      GTK_SIGNAL_FUNC (snmp_apply_cb), dialog);

  gtk_signal_connect (GTK_OBJECT (dialog), "close",
                      GTK_SIGNAL_FUNC (snmp_close_cb), dialog);

  gtk_signal_connect (GTK_OBJECT (dialog->snmp_clist), "select_row",
                      (GtkSignalFunc) snmp_select_row_cb, dialog);

  gtk_signal_connect (GTK_OBJECT (dialog->add_button), "clicked",
                      (GtkSignalFunc) snmp_add_button_cb, dialog);

  gtk_signal_connect (GTK_OBJECT (dialog->delete_button), "clicked",
                      (GtkSignalFunc) snmp_delete_button_cb, dialog);

  gtk_signal_connect (GTK_OBJECT(dialog->name_entry), "changed",
                      (GtkSignalFunc) snmp_renamed_cb, dialog);

  gtk_signal_connect (GTK_OBJECT(dialog->snmp_panel), "changed", 
		      (GtkSignalFunc) snmp_changed_cb, dialog);
  D_FUNC_END;
}

/******************************************************************************
**
**  Callback function to refresh the entire widget contents.
**
**  The currently selected SNMP entry might be a new entry, in which case
**  it will have a rowid of zero, and won't be in the database yet.  In
**  this case, the new entry is appended to the end of the CList, and we
**  modify the adjustment on the scrolled window so as to display the 
**  bottom of the list, with the newly added entry.
**
******************************************************************************/

static void
snmp_refresh_cb (G_sqldb_table *table, gpointer row, 
	         G_sqldb_cb_type type, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  GtkCList	     * clist;	
  GList		     * gl;
  GtkAdjustment	     * adjustment;
  double	       old_adjustment_value;
  double	       old_adjustment_upper;

  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (data);
  clist	 = GTK_CLIST (dialog->snmp_clist);

  adjustment = gtk_scrolled_window_get_vadjustment (
                        	GTK_SCROLLED_WINDOW (dialog->scrolled));
  old_adjustment_value = adjustment->value;
  old_adjustment_upper = adjustment->upper;

  gtk_clist_freeze (clist);
  gtk_clist_clear (clist);
  gtk_signal_handler_block_by_data (GTK_OBJECT (clist), dialog);

  gl = g_sqldb_table_list (snmp_sqldb);
  while (gl)
    {
       snmp_refresh_row (dialog, (DB_snmp *)gl->data);
       gl = gl->next;
    }

  if (dialog->selected_snmp->rowid == 0)
    {
      snmp_refresh_row (dialog, dialog->selected_snmp);
      gtk_adjustment_set_value (adjustment, old_adjustment_upper);
    }
  else
    gtk_adjustment_set_value (adjustment, old_adjustment_value);

  gtk_signal_handler_unblock_by_data (GTK_OBJECT (clist), dialog);
  gtk_clist_thaw (clist);
  D_FUNC_END;
}

/******************************************************************************
**
**  Private function used by snmp_refresh_cb to process a row
**  This function is entered, and exits, with the clist signal-blocked.
**  Unblocking around the gtk_clist_select_row function causes the
**  "select_row" signal to be emitted to the selected row, triggering
**  a call to snmp_select_row_cb, which fills in the panel.
**
******************************************************************************/

static void
snmp_refresh_row (GXsnmp_snmp_dialog * dialog, DB_snmp * dbs)
{
  GtkCList * clist;
  gchar    * entry[2];
  gint       row;
  D_FUNC_START;
  clist = GTK_CLIST (dialog->snmp_clist);

  entry[0] = dbs->name;
  entry[1] = g_strdup_printf ("%d", g_list_length (dbs->DB_interfaces));
  row = gtk_clist_append (clist, entry);
  g_free (entry[1]);

  gtk_clist_set_row_data (clist, row, GINT_TO_POINTER (dbs->rowid));

  if (dialog->selected_snmp == dbs) 
    {
      gtk_signal_handler_unblock_by_data (GTK_OBJECT (clist), dialog);
      gtk_clist_select_row (clist, row, 0);
      gtk_signal_handler_block_by_data (GTK_OBJECT (clist), dialog);
    }
  D_FUNC_END;
}

/******************************************************************************
**
**  Callback for when the clist row changes ... i.e. we are changing the
**  configuration.
**
**  If the rowid associated with the clist row is 0, then the user has 
**  just added a new configuration.  In this case, the entry is not in the
**  database yet, and dialog->selected_snmp already points to the new
**  configuration.
**
**  If the rowid of the selected row is 1, then we are editing the default
**  configuration, and the 'delete' button is disabled.  Also, if the
**  configuration we are editing has any hosts referencing it, the 'delete'
**  button is also disabled.
**
******************************************************************************/

static void
snmp_select_row_cb (GtkWidget      * widget,
		    gint	     row,
		    gint	     column,
		    GdkEventButton * event,
		    gpointer         data)	/* &snmp_dialog */
{
  GXsnmp_snmp_dialog * dialog; 
  GtkCList	     * clist;
  guint		       rowid;
  DB_snmp            * dbs;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (data);
  clist  = GTK_CLIST (widget);

  dialog->row = row;

  rowid = GPOINTER_TO_INT (gtk_clist_get_row_data (clist, row));
  if (rowid)
    {
      dbs   = g_sqldb_row_find (snmp_sqldb, "_rowid", &rowid);
      dialog->selected_snmp = dbs;
    }
  else
      dbs = dialog->selected_snmp;

  gtk_signal_handler_block_by_data (GTK_OBJECT (dialog->snmp_panel), dialog);
  gxsnmp_snmp_panel_put_data (GXSNMP_SNMP_PANEL (dialog->snmp_panel), dbs);
  gtk_signal_handler_unblock_by_data (GTK_OBJECT (dialog->snmp_panel), dialog);

  gtk_signal_handler_block_by_data (GTK_OBJECT (dialog->name_entry), dialog);
  gtk_entry_set_text (GTK_ENTRY (dialog->name_entry), dbs->name);
  gtk_signal_handler_unblock_by_data (GTK_OBJECT (dialog->name_entry), dialog);

  if ((rowid == 1) || (dialog->selected_snmp->DB_interfaces != NULL))
    gtk_widget_set_sensitive (dialog->delete_button, FALSE);
  else
    gtk_widget_set_sensitive (dialog->delete_button, TRUE);
  D_FUNC_END;
}

/*****************************************************************************
**
**  Callback function for when some element in the SNMP panel is changed.
**  Desensitizes the selection clist, disables the "add" button, and
**  enables the "ok" and "apply" buttons.
**
*****************************************************************************/

static void
snmp_changed_cb (GtkWidget * widget, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (data);

  gtk_widget_set_sensitive (dialog->snmp_clist, FALSE);
  gtk_widget_set_sensitive (dialog->add_button, FALSE);

  gnome_property_dialog_changed (data); 
  D_FUNC_END;
}

/******************************************************************************
**
**  Callback function for when the name field in the SNMP panel is changed.
**  Update the name in the clist, then call snmp_changed_cb as if the change
**  were to any other field in the panel.
**  
******************************************************************************/

static void
snmp_renamed_cb (GtkWidget * widget, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  GtkCList           * clist;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (data);
  clist  = GTK_CLIST (dialog->snmp_clist);

  gtk_clist_set_text (clist, dialog->row, 0,
		      gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));  

  snmp_changed_cb (widget, data);
  D_FUNC_END;
}

/******************************************************************************
**
**  Callback function for the "Add" button.  Create a new snmp entry, 
**  assign a temporary rowid of 0, and call snmp_refresh_cb to display
**  the new entry.  Also, call snmp_changed_cb to mark the new entry
**  as "changed", thus forcing the user to either apply or cancel before
**  selecting a different entry (and losing the dbs pointer).
**
******************************************************************************/

static void
snmp_add_button_cb (GtkWidget * widget, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  DB_snmp	     * dbs;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (data);

  dbs 	             = g_new0 (DB_snmp, 1);
  dbs->rowid         = 0;
  dbs->created       = db_timestamp ();
  dbs->modified      = g_strdup (dbs->created);
  dbs->name          = g_strdup ("New Configuration");
  dbs->version       = PMODEL_SNMPV1;
  dbs->port          = 161;
  dbs->timeout       = 30;
  dbs->retries       = 3;
  dbs->read_c        = g_strdup ("public");
  dbs->write_c       = g_strdup ("private");
  dbs->DB_interfaces = NULL;

  dialog->selected_snmp = dbs;

  snmp_refresh_cb (snmp_sqldb, dbs, G_SQLDB_CB_UPDATE, dialog);
  snmp_changed_cb (widget, dialog);
  D_FUNC_END;
}

/******************************************************************************
**
**  Callback function for the "Delete" button
**
**  Careful management of the "delete" button should ensure that this 
**  callback is only ever entered under the following conditions:
**
**  1)  The rowid is NOT 1  (you can't delete the default configuration)
**  2)  The currently selected entry is not used by any interfaces.
**      (otherwise the interfaces would be left with an orphan SNMP rowid)
**
**  If the rowid of the selected entry is non-zero, then this is an entry
**  that is in the database (as opposed to a new entry that was never
**  committed.)  In this case, call g_sqldb_row_delete to remove the entry
**  from the database.
**
**  Then select the entry in the previous clist row, and issue a
**  refresh callback to force the clist to be updated.
**
**  Finally, re-enable the add button, the Ok button, and the apply button.
**
******************************************************************************/

static void 
snmp_delete_button_cb (GtkWidget * widget, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  DB_snmp            * dbs;
  GtkCList           * clist;
  guint		       rowid;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (data);
  clist  = GTK_CLIST (dialog->snmp_clist);
  dbs    = dialog->selected_snmp;
 
  g_return_if_fail (dbs->rowid != 1);
  g_return_if_fail (dbs->DB_interfaces == NULL);

  if (dbs->rowid != 0)
    g_sqldb_row_delete ( snmp_sqldb, dbs);

  snmp_destroy_object (dbs);

  dialog->row--;
  if (dialog->row < 0) 
    dialog->row = 0;

  rowid = GPOINTER_TO_INT (gtk_clist_get_row_data (clist, dialog->row));
  g_return_if_fail (rowid != 0);
  dialog->selected_snmp = g_sqldb_row_find (snmp_sqldb, "_rowid", &rowid);

  snmp_refresh_cb (snmp_sqldb, dialog->selected_snmp, 
		   G_SQLDB_CB_UPDATE, dialog);

  gtk_widget_set_sensitive (dialog->add_button, TRUE);
  gnome_property_dialog_set_state (GNOME_PROPERTY_DIALOG (dialog), FALSE);
  D_FUNC_END;
}

/*****************************************************************************
**
**  Callback function for when the 'ok' or 'apply' button is pressed.
**  Stores any changes back in the DB_snmp structure.
**
**  If the rowid of the currently selected entry is 0, then assign a rowid
**  and add the new entry to the database.
**
**  Otherwise, only issue a g_sqldb_row_update if something has changed.
**
**  Finally, we reenable the clist and the add button, and we are done.
**
*****************************************************************************/

static void
snmp_apply_cb (GtkWidget * widget, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  DB_snmp	     * dbs;
  gboolean	       changed;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (widget);
  dbs    = dialog->selected_snmp;

  changed = gxsnmp_snmp_panel_get_data (
                        GXSNMP_SNMP_PANEL (dialog->snmp_panel), dbs);
  changed |= update_string_field (&dbs->name,
			gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));

  if (dbs->rowid == 0)		/* New entry to add to the database */
    {
      dbs->rowid = g_sqldb_highest_rowid (snmp_sqldb, "_rowid") + 1;
      g_sqldb_row_add ( snmp_sqldb, dbs);
    }

  if (changed)
    {
      if (dbs->modified)
        g_free (dbs->modified);
      dbs->modified = db_timestamp ();

      g_sqldb_row_update ( snmp_sqldb, dbs);
    }
  gtk_widget_set_sensitive (dialog->snmp_clist, TRUE);
  gtk_widget_set_sensitive (dialog->add_button, TRUE);
  D_FUNC_END;
}

/******************************************************************************
**
**  Callback function for when the dialog is closed.
**
**  If the currently selected entry has a rowid of zero, then the user 
**  added a new entry, but did not commit it.  In this case, discard
**  the data item.
**
**  Otherwise, just return FALSE to allow the dialog to be destroyed.
**
******************************************************************************/

static gint
snmp_close_cb (GtkWidget * widget, gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  D_FUNC_START;
  dialog = GXSNMP_SNMP_DIALOG (widget);

  if (dialog->selected_snmp->rowid == 0)
    snmp_destroy_object (dialog->selected_snmp);
  D_FUNC_END;
  return FALSE;
}

/******************************************************************************
**
**  Private function, used by the delete and close callbacks, to destroy
**  a DB_snmp object.
**
******************************************************************************/

static void
snmp_destroy_object (DB_snmp * dbs)
{
  D_FUNC_START;
  if (dbs)
    {
      if (dbs->created)  g_free (dbs->created);
      if (dbs->modified) g_free (dbs->modified);
      if (dbs->name)     g_free (dbs->name);
      if (dbs->read_c)   g_free (dbs->read_c);
      if (dbs->write_c)  g_free (dbs->write_c);
      g_free (dbs);
    }
  D_FUNC_END;
}

/*******************************************************************************
**
**  Public function to create a new SNMP dialog widget         
**
**  The argument is either a pointer to the DB_snmp entry to be displayed 
**  when the dialog is first displayed, or NULL to start with the default.
**
**  The default SNMP configuration is always the configuration with rowid==1.
**  We take particular care to never delete this rowid from the database.
**
*******************************************************************************/

GtkWidget *
gxsnmp_snmp_dialog_new (gpointer data)
{
  GXsnmp_snmp_dialog * dialog;
  DB_snmp	     * dbs;
  guint		       one = 1;	/* Used for default rowid */
  D_FUNC_START;
  dialog = gtk_type_new (gxsnmp_snmp_dialog_get_type());

  dbs = (DB_snmp *)data;
  if (!dbs)
    {
      dbs = g_sqldb_row_find (snmp_sqldb, "_rowid", &one);
      if (!dbs)                 /* EEK! really new snmp config.. */
	{                       /* We'll set some fairly standard options */
	  dbs = snmp_create ();
	  dbs->name    = g_strdup (_("Default Config"));
	  dbs->read_c  = g_strdup (_("public"));
	  dbs->write_c = g_strdup (_("private"));
	  dbs->port    = 161;
	  dbs->retries = 3;
	  dbs->timeout = 300;
	  dbs->version = 1;
	}
    }
  dialog->selected_snmp = dbs;

  snmp_refresh_cb (snmp_sqldb, dbs, G_SQLDB_CB_UPDATE, dialog);
  D_FUNC_END;
  return GTK_WIDGET (dialog);
}

/* EOF */

