/* profile_dlg.c
 * Dialog box for profiles editing
 * Stig Bjorlykke <stig@bjorlykke.org>, 2008
 *
 * $Id: profile_dlg.c 38021 2011-07-14 18:09:09Z gerald $
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include <epan/filesystem.h>
#include <epan/prefs.h>

#include "../simple_dialog.h"
#include <wsutil/file_util.h>

#include "gtk/main.h"
#include "gtk/profile_dlg.h"
#include "gtk/dlg_utils.h"
#include "gtk/gui_utils.h"
#include "gtk/gtkglobals.h"
#include "gtk/help_dlg.h"


#define E_PROF_PROFILE_L_KEY        "profile_profile_l"
#define E_PROF_DEL_BT_KEY           "profile_del_bt"
#define E_PROF_NAME_TE_KEY          "profile_name_te"

static GtkWidget *global_profile_w = NULL;
static GList *current_profiles = NULL;
static GList *edited_profiles = NULL;

#define PROF_STAT_DEFAULT  1
#define PROF_STAT_EXISTS   2
#define PROF_STAT_NEW      3
#define PROF_STAT_CHANGED  4
#define PROF_STAT_COPY     5

typedef struct {
  char *name;           /* profile name */
  char *reference;      /* profile reference */
  int   status;
} profile_def;

static GList *
add_profile_entry(GList *fl, const char *profilename, const char *reference, int status)
{
    profile_def *profile;

    profile = (profile_def *) g_malloc(sizeof(profile_def));
    profile->name = g_strdup(profilename);
    profile->reference = g_strdup(reference);
    profile->status = status;
    return g_list_append(fl, profile);
}

static GList *
remove_profile_entry(GList *fl, GList *fl_entry)
{
  profile_def *profile;

  profile = (profile_def *) fl_entry->data;
  g_free(profile->name);
  g_free(profile->reference);
  g_free(profile);
  return g_list_remove_link(fl, fl_entry);
}

static const gchar *
get_profile_parent (const gchar *profilename)
{
  GList *fl_entry = g_list_first(edited_profiles);
  guint no_edited = g_list_length(edited_profiles);
  profile_def *profile;
  guint i;

  if (fl_entry) {
    /* We have edited profiles, find parent */
    for (i = 0; i < no_edited; i++) {
      while (fl_entry) {
	profile = (profile_def *) fl_entry->data;
	if (strcmp (profile->name, profilename) == 0) {
	  if ((profile->status == PROF_STAT_NEW) ||
	      (profile->reference == NULL)) {
	    /* Copy from a new profile */
	    return NULL;
	  } else {
	    /* Found a parent, use this */
	    profilename = profile->reference;
	  }
	}
	fl_entry = g_list_next(fl_entry);
      }
      fl_entry = g_list_first(edited_profiles);
    }
  }

  return profilename;
}

static GList *
add_to_profile_list(const char *name, const char *expression, int status)
{
  edited_profiles = add_profile_entry(edited_profiles, name, expression, status);

  return g_list_last(edited_profiles);
}

static void
remove_from_profile_list(GList *fl_entry)
{
  edited_profiles = remove_profile_entry(edited_profiles, fl_entry);
}

static void
empty_profile_list(gboolean edit_list)
{
  GList **flpp;

  if (edit_list) {
    flpp = &edited_profiles;

    while(*flpp) {
      *flpp = remove_profile_entry(*flpp, g_list_first(*flpp));
    }

    g_assert(g_list_length(*flpp) == 0);
  }

  flpp = &current_profiles;

  while(*flpp) {
    *flpp = remove_profile_entry(*flpp, g_list_first(*flpp));
  }

  g_assert(g_list_length(*flpp) == 0);
}

static void
copy_profile_list(void)
{
    GList      *flp_src;
    profile_def *profile;

    flp_src = edited_profiles;

    /* throw away the "old" destination list - a NULL list is ok here */
    empty_profile_list(FALSE);

    /* copy the list entries */
    while(flp_src) {
        profile = (flp_src)->data;

        current_profiles = add_profile_entry(current_profiles, profile->name,
					     profile->reference, profile->status);
        flp_src = g_list_next(flp_src);
    }
}


static GtkTreeIter *
fill_list(GtkWidget *main_w)
{
  WS_DIR        *dir;             /* scanned directory */
  WS_DIRENT     *file;            /* current file */
  GList         *fl_entry;
  profile_def   *profile;
  GtkTreeView   *profile_l;
  GtkListStore  *store;
  GtkTreeIter    iter, *l_select = NULL;
  const gchar   *profile_name = get_profile_name ();
  const gchar   *profiles_dir, *name;
  gchar         *filename;

  profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
  store = GTK_LIST_STORE(gtk_tree_view_get_model(profile_l));

  fl_entry = add_to_profile_list(DEFAULT_PROFILE, DEFAULT_PROFILE, PROF_STAT_DEFAULT);
  gtk_list_store_append(store, &iter);
  gtk_list_store_set(store, &iter, 0, DEFAULT_PROFILE, 1, fl_entry, -1);
  if (strcmp (profile_name, DEFAULT_PROFILE)==0) {
    l_select = g_memdup(&iter, sizeof(iter));
  }

  /* fill in data */
  profiles_dir = get_profiles_dir();
  if ((dir = ws_dir_open(profiles_dir, 0, NULL)) != NULL) {
    while ((file = ws_dir_read_name(dir)) != NULL) {
      name = ws_dir_get_name(file);
      filename = g_strdup_printf ("%s%s%s", profiles_dir, G_DIR_SEPARATOR_S, name);

      if (test_for_directory(filename) == EISDIR) {
	fl_entry = add_to_profile_list(name, name, PROF_STAT_EXISTS);
	profile    = (profile_def *) fl_entry->data;
	gtk_list_store_append(store, &iter);
	gtk_list_store_set(store, &iter, 0, profile->name, 1, fl_entry, -1);

	if (profile->name) {
	  if (strcmp(profile_name, profile->name) == 0) {
	    /*
	     * XXX - We're assuming that we can just copy a GtkTreeIter
	     * and use it later without any crashes.  This may not be a
	     * valid assumption.
	     */
	    l_select = g_memdup(&iter, sizeof(iter));
	  }
	}
      }
      g_free (filename);
    }
    ws_dir_close (dir);
  }

  /* Make the current list and the edited list equal */
  copy_profile_list ();

  return l_select;
}

static gboolean
profile_is_invalid_name(gchar *name)
{
  gchar  *message = NULL;

#ifdef _WIN32
  char *invalid_dir_char = "\\/:*?\"<>|";
  gboolean invalid = FALSE;
  int i;

  for (i = 0; i < 9; i++) {
    if (strchr(name, invalid_dir_char[i])) {
      /* Invalid character in directory */
      invalid = TRUE;
    }
  }
  if (name[0] == '.' || name[strlen(name)-1] == '.') {
    /* Profile name cannot start or end with period */
    invalid = TRUE;
  }
  if (invalid) {
    message = g_strdup_printf("start or end with period (.), or contain any of the following characters:\n"
			      "   \\ / : * ? \" &lt; &gt; |");
  }
#else
  if (strchr(name, '/')) {
    /* Invalid character in directory */
    message = g_strdup_printf("contain the '/' character.");
  }
#endif

  if (message) {
    simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "A profile name cannot %s\nProfiles unchanged.", message);
    g_free(message);
    return TRUE;
  }

  return FALSE;
}

static void
profile_select(GtkWidget *main_w, GtkTreeView *profile_l, gboolean destroy)
{
  GList            *fl_entry;
  profile_def      *profile;
  GtkTreeSelection *sel;
  GtkTreeModel     *model;
  GtkTreeIter       iter;

  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));

  if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
    gtk_tree_model_get(model, &iter, 1, &fl_entry, -1);
    if (fl_entry) {
      profile = (profile_def *) fl_entry->data;
      if (profile_exists (profile->name)) {
	/* The new profile exists, change */
	change_configuration_profile (profile->name);
      } else if (!profile_exists (get_profile_name())) {
	/* The new profile does not exist, and the previous profile has
	   been deleted.  Change to the default profile */
	change_configuration_profile (NULL);
      }
    }
  }

  if (destroy) {
    /*
     * Destroy the profile dialog box.
     */
    empty_profile_list (TRUE);
    window_destroy(main_w);
  }
}

static void
profile_apply(GtkWidget *main_w, GtkTreeView *profile_l, gboolean destroy)
{
  char        *pf_dir_path, *pf_dir_path2, *pf_filename;
  GList       *fl1, *fl2;
  profile_def *profile1, *profile2;
  gboolean     found;

  /* First validate all profile names */
  fl1 = g_list_first(edited_profiles);
  while (fl1) {
    profile1 = (profile_def *) fl1->data;
    g_strstrip(profile1->name);
    if (profile_is_invalid_name(profile1->name)) {
      return;
    }
    fl1 = g_list_next(fl1);
  }

  /* Then do all copy profiles */
  fl1 = g_list_first(edited_profiles);
  while (fl1) {
    profile1 = (profile_def *) fl1->data;
    g_strstrip(profile1->name);
    if (profile1->status == PROF_STAT_COPY) {
      if (create_persconffile_profile(profile1->name, &pf_dir_path) == -1) {
        simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
                      "Can't create directory\n\"%s\":\n%s.",
                      pf_dir_path, g_strerror(errno));

        g_free(pf_dir_path);
      }
      profile1->status = PROF_STAT_EXISTS;

      if (profile1->reference) {
        if (copy_persconffile_profile(profile1->name, profile1->reference, &pf_filename,
                                      &pf_dir_path, &pf_dir_path2) == -1) {
          simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
                        "Can't copy file \"%s\" in directory\n\"%s\" to\n\"%s\":\n%s.",
                        pf_filename, pf_dir_path2, pf_dir_path, g_strerror(errno));

          g_free(pf_filename);
          g_free(pf_dir_path);
          g_free(pf_dir_path2);
        }
      }

      g_free (profile1->reference);
      profile1->reference = g_strdup(profile1->name);
    }
    fl1 = g_list_next(fl1);
  }


  /* Then create new and rename changed */
  fl1 = g_list_first(edited_profiles);
  while (fl1) {
    profile1 = (profile_def *) fl1->data;
    g_strstrip(profile1->name);
    if (profile1->status == PROF_STAT_NEW) {
      /* We do not create a directory for the default profile */
      if (strcmp(profile1->name, DEFAULT_PROFILE)!=0) {
	if (create_persconffile_profile(profile1->name, &pf_dir_path) == -1) {
	  simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
			"Can't create directory\n\"%s\":\n%s.",
			pf_dir_path, g_strerror(errno));

	  g_free(pf_dir_path);
	}
	profile1->status = PROF_STAT_EXISTS;

	g_free (profile1->reference);
	profile1->reference = g_strdup(profile1->name);
      }
    } else if (profile1->status == PROF_STAT_CHANGED) {
      if (strcmp(profile1->reference, profile1->name)!=0) {
	/* Rename old profile directory to new */
	if (rename_persconffile_profile(profile1->reference, profile1->name,
					&pf_dir_path, &pf_dir_path2) == -1) {
	  simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
			"Can't rename directory\n\"%s\" to\n\"%s\":\n%s.",
			pf_dir_path, pf_dir_path2, g_strerror(errno));

	  g_free(pf_dir_path);
	  g_free(pf_dir_path2);
	}
	profile1->status = PROF_STAT_EXISTS;
	g_free (profile1->reference);
	profile1->reference = g_strdup(profile1->name);
      }
    }
    fl1 = g_list_next(fl1);
  }

  /* Last remove deleted */
  fl1 = g_list_first(current_profiles);
  while (fl1) {
    found = FALSE;
    profile1 = (profile_def *) fl1->data;
    fl2 = g_list_first(edited_profiles);
    while (fl2) {
      profile2 = (profile_def *) fl2->data;
      if (strcmp(profile1->name, profile2->name)==0) {
	/* Profile exists in both lists */
	found = TRUE;
      } else if (strcmp(profile1->name, profile2->reference)==0) {
	/* Profile has been renamed */
	found = TRUE;
      }
      fl2 = fl2->next;
    }
    if (!found) {
      /* Exists in existing list and not in edited, this is a deleted profile */
      if (delete_persconffile_profile(profile1->name, &pf_dir_path) == -1) {
	simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
		      "Can't delete profile directory\n\"%s\":\n%s.",
		      pf_dir_path, g_strerror(errno));

	g_free(pf_dir_path);
      }
    }
    fl1 = g_list_next(fl1);
  }

  copy_profile_list();
  profile_select(main_w, profile_l, destroy);
}

static void
profile_dlg_ok_cb(GtkWidget *ok_bt, gpointer data _U_)
{
  GtkWidget    *main_w = gtk_widget_get_toplevel(ok_bt);
  GtkTreeView  *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));

  /*
   * Apply the profile and destroy the dialog box.
   */
  profile_apply(main_w, profile_l, TRUE);
}

static void
profile_dlg_apply_cb(GtkWidget *apply_bt, gpointer data _U_)
{
  GtkWidget    *main_w    = gtk_widget_get_toplevel(apply_bt);
  GtkTreeView  *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));

  /*
   * Apply the profile, but don't destroy the dialog box.
   */
  profile_apply(main_w, profile_l, FALSE);
}

/* cancel button pressed, revert changes and exit dialog */
static void
profile_dlg_cancel_cb(GtkWidget *cancel_bt, gpointer data _U_)
{
  GtkWidget  *main_w = gtk_widget_get_toplevel(cancel_bt);

  empty_profile_list (TRUE);
  window_destroy(GTK_WIDGET(main_w));
}

/* Treat this as a cancel, by calling "profile_dlg_cancel_cb()" */
static gboolean
profile_dlg_delete_event_cb(GtkWidget *main_w, GdkEvent *event _U_,
			    gpointer data)
{
  profile_dlg_cancel_cb(main_w, data);
  return FALSE;
}

static void
profile_dlg_destroy_cb(GtkWidget *w _U_, gpointer data _U_)
{
  global_profile_w = NULL;
}


static gint
profile_button_press_cb(GtkWidget *list, GdkEventButton *event, gpointer data _U_)
{
  if (event->type == GDK_2BUTTON_PRESS) {
    GtkWidget *main_w = gtk_widget_get_toplevel(list);

    profile_apply (main_w, GTK_TREE_VIEW(list), TRUE);
  }

  return FALSE;
}

static gint
profile_key_release_cb(GtkWidget *list, GdkEventKey *event, gpointer data _U_)
{
  if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
    GtkWidget    *main_w = gtk_widget_get_toplevel(list);

    profile_apply (main_w, GTK_TREE_VIEW(list), TRUE);
  }

  return FALSE;
}

static void
profile_sel_list_cb(GtkTreeSelection *sel, gpointer data _U_)
{
  GtkWidget    *profile_l   = GTK_WIDGET(gtk_tree_selection_get_tree_view(sel));
  GtkWidget    *main_w      = gtk_widget_get_toplevel(profile_l);
  GtkTreeModel *model;
  GtkTreeIter   iter;
  GtkWidget    *name_te     = g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
  GtkWidget    *del_bt      = g_object_get_data(G_OBJECT(main_w), E_PROF_DEL_BT_KEY);
  profile_def  *profile;
  gchar        *name        = NULL;
  GList        *fl_entry;
  gint          sensitivity = FALSE;

  if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
    gtk_tree_model_get(model, &iter, 1, &fl_entry, -1);
    if (fl_entry) {
      profile= (profile_def *) fl_entry->data;
      name   = g_strdup(profile->name);
      if (profile->status!=PROF_STAT_DEFAULT) {
	sensitivity = TRUE;
      }
    }
  }

  /*
   * Did you know that this function is called when the window is destroyed?
   * Funny, that.
   * This means that we have to:
   *
   *	attach to the top-level window data items containing pointers to
   *	the widgets we affect here;
   *
   *	give each of those widgets their own destroy callbacks;
   *
   *	clear that pointer when the widget is destroyed;
   *
   *	don't do anything to the widget if the pointer we get back is
   *	null;
   *
   * so that if we're called after any of the widgets we'd affect are
   * destroyed, we know that we shouldn't do anything to those widgets.
   */
  if (name_te != NULL) {
    gtk_entry_set_text(GTK_ENTRY(name_te), name ? name : "");
    gtk_widget_set_sensitive(name_te, sensitivity);
  }
  if (del_bt != NULL)
    gtk_widget_set_sensitive(del_bt, sensitivity);
  g_free(name);
}

static void
profile_new_bt_clicked_cb(GtkWidget *w, gpointer data _U_)
{
  GtkWidget    *main_w = gtk_widget_get_toplevel(w);
  GtkWidget    *name_te = g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
  GtkTreeView  *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
  GtkListStore *store;
  GtkTreeIter   iter;
  GList        *fl_entry;
  const gchar  *name = "New profile";

  /* Add a new entry to the profile list. */
  fl_entry = add_to_profile_list(name, "", PROF_STAT_NEW);

  store = GTK_LIST_STORE(gtk_tree_view_get_model(profile_l));
  gtk_list_store_append(store, &iter);
  gtk_list_store_set(store, &iter, 0, name, 1, fl_entry, -1);
  /* Select the item. */
  gtk_tree_selection_select_iter(gtk_tree_view_get_selection(profile_l), &iter);

  gtk_editable_select_region(GTK_EDITABLE(name_te), 0, -1);
  gtk_widget_grab_focus(name_te);
}

static void
profile_copy_bt_clicked_cb(GtkWidget *w, gpointer data _U_)
{
  GtkWidget    *main_w = gtk_widget_get_toplevel(w);
  GtkWidget    *name_te = g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
  GtkTreeView  *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
  GtkListStore *store;
  GtkTreeIter   iter;
  GList        *fl_entry;
  const gchar  *name = gtk_entry_get_text(GTK_ENTRY(name_te));
  gchar        *new_name;

  new_name = g_strdup_printf ("%s (copy)", name);

  /* Add a new entry to the profile list. */
  fl_entry = add_to_profile_list(new_name, get_profile_parent(name), PROF_STAT_COPY);

  store = GTK_LIST_STORE(gtk_tree_view_get_model(profile_l));
  gtk_list_store_append(store, &iter);
  gtk_list_store_set(store, &iter, 0, new_name, 1, fl_entry, -1);
  /* Select the item. */
  gtk_tree_selection_select_iter(gtk_tree_view_get_selection(profile_l), &iter);

  gtk_editable_select_region(GTK_EDITABLE(name_te), 0, -1);
  gtk_widget_grab_focus(name_te);

  g_free (new_name);
}

static void
profile_name_te_changed_cb(GtkWidget *w, gpointer data _U_)
{
  GtkWidget   *main_w = gtk_widget_get_toplevel(w);
  GtkWidget   *name_te = g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
  GtkWidget   *profile_l = g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY);
  profile_def *profile;
  GList       *fl_entry;
  const gchar *name = "";

  GtkTreeSelection  *sel;
  GtkTreeModel      *model;
  GtkTreeIter        iter;

  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
  name   = gtk_entry_get_text(GTK_ENTRY(name_te));

  /* if something was selected */
  if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
    gtk_tree_model_get(model, &iter, 1, &fl_entry, -1);
    if (fl_entry != NULL) {
      profile = (profile_def *) fl_entry->data;

      if (strlen(name) > 0 && profile) {
	if (profile->status != PROF_STAT_DEFAULT) {
	  g_free(profile->name);
	  profile->name = g_strdup(name);
	  if ((profile->status != PROF_STAT_NEW) &&
	      (profile->status != PROF_STAT_COPY)) {
	    profile->status = PROF_STAT_CHANGED;
	  }
	  gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, name, -1);
	}
      }
    }
  }
}

static void
profile_del_bt_clicked_cb(GtkWidget *w, gpointer data _U_)
{
  GtkWidget  *main_w = gtk_widget_get_toplevel(w);
  GtkWidget  *profile_l = g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY);
  GList      *fl_entry;

  GtkTreeSelection  *sel;
  GtkTreeModel      *model;
  GtkTreeIter        iter;

  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
  /* If something was selected */
  if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
    gtk_tree_model_get(model, &iter, 1, &fl_entry, -1);

    if (fl_entry != NULL) {
      remove_from_profile_list (fl_entry);
      gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    }
  }

  if (gtk_tree_model_get_iter_first (model, &iter)) {
    gtk_tree_selection_select_iter(sel, &iter);
  }
}

static GtkWidget *
profile_dialog_new(void)
{
  GtkWidget  *main_w,  /* main window */
    *main_vb,          /* main container */
    *bbox,             /* button container */
    *ok_bt,            /* "OK" button */
    *apply_bt,         /* "Apply" button */
    *cancel_bt,        /* "Cancel" button */
    *help_bt;          /* "Help" button */
  GtkWidget  *profile_vb,        /* profile settings box */
    *props_vb;
  GtkWidget  *top_hb,
    *list_bb,
    *new_bt,
    *copy_bt,
    *del_bt,
    *profile_sc,
    *profile_l,
    *middle_hb,
    *name_lb,
    *name_te,
    *profile_fr,
    *edit_fr,
    *props_fr;
  GtkTooltips       *tooltips;
  GtkListStore      *store;
  GtkCellRenderer   *renderer;
  GtkTreeViewColumn *column;
  GtkTreeSelection  *sel;
  GtkTreeIter       *l_select;

  /* Get a pointer to a static variable holding the type of profile on
     which we're working, so we can pass that pointer to callback
     routines. */

  tooltips = gtk_tooltips_new ();

  main_w = dlg_conf_window_new("Wireshark: Configuration Profiles");
  gtk_window_set_default_size(GTK_WINDOW(main_w), 400, 400);

  main_vb = gtk_vbox_new(FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
  gtk_container_add(GTK_CONTAINER(main_w), main_vb);
  gtk_widget_show(main_vb);

  /* Container for each row of widgets */
  profile_vb = gtk_vbox_new(FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(profile_vb), 0);
  gtk_container_add(GTK_CONTAINER(main_vb), profile_vb);
  gtk_widget_show(profile_vb);

  /* Top row: Buttons and profile list */
  top_hb = gtk_hbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(profile_vb), top_hb);
  gtk_widget_show(top_hb);

  edit_fr = gtk_frame_new("Edit");
  gtk_box_pack_start(GTK_BOX(top_hb), edit_fr, FALSE, FALSE, 0);
  gtk_widget_show(edit_fr);

  list_bb = gtk_vbox_new(TRUE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(list_bb), 5);
  gtk_container_add(GTK_CONTAINER(edit_fr), list_bb);
  gtk_widget_show(list_bb);

  new_bt = gtk_button_new_from_stock(GTK_STOCK_NEW);
  g_signal_connect(new_bt, "clicked", G_CALLBACK(profile_new_bt_clicked_cb), NULL);
  gtk_widget_show(new_bt);
  gtk_box_pack_start (GTK_BOX (list_bb), new_bt, FALSE, FALSE, 0);
  gtk_tooltips_set_tip (tooltips, new_bt,
			"Create a new profile (with default properties)", NULL);

  copy_bt = gtk_button_new_from_stock(GTK_STOCK_COPY);
  g_signal_connect(copy_bt, "clicked", G_CALLBACK(profile_copy_bt_clicked_cb), NULL);
  gtk_widget_show(copy_bt);
  gtk_box_pack_start (GTK_BOX (list_bb), copy_bt, FALSE, FALSE, 0);
  gtk_tooltips_set_tip (tooltips, copy_bt,
			"Copy the selected profile", NULL);

  del_bt = gtk_button_new_from_stock(GTK_STOCK_DELETE);
  gtk_widget_set_sensitive(del_bt, FALSE);
  g_signal_connect(del_bt, "clicked", G_CALLBACK(profile_del_bt_clicked_cb), NULL);
  g_object_set_data(G_OBJECT(main_w), E_PROF_DEL_BT_KEY, del_bt);
  gtk_widget_show(del_bt);
  gtk_box_pack_start (GTK_BOX (list_bb), del_bt, FALSE, FALSE, 0);
  gtk_tooltips_set_tip (tooltips, del_bt, "Delete the selected profile", NULL);

  profile_fr = gtk_frame_new("Configuration Profiles");
  gtk_box_pack_start(GTK_BOX(top_hb), profile_fr, TRUE, TRUE, 0);
  gtk_widget_show(profile_fr);

  profile_sc = scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(profile_sc),
				      GTK_SHADOW_IN);

  gtk_container_set_border_width  (GTK_CONTAINER (profile_sc), 5);
  gtk_container_add(GTK_CONTAINER(profile_fr), profile_sc);
  gtk_widget_show(profile_sc);

  store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
  profile_l = tree_view_new(GTK_TREE_MODEL(store));
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(profile_l), FALSE);
  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, NULL);
  gtk_tree_view_column_set_sort_column_id(column, 0);
  gtk_tree_view_append_column(GTK_TREE_VIEW(profile_l), column);
  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
  gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
  g_signal_connect(sel, "changed", G_CALLBACK(profile_sel_list_cb), profile_vb);
  g_signal_connect(profile_l, "button_press_event", G_CALLBACK(profile_button_press_cb), NULL);
  g_signal_connect(profile_l, "key_release_event", G_CALLBACK(profile_key_release_cb), NULL);
  g_object_set_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY, profile_l);
  gtk_container_add(GTK_CONTAINER(profile_sc), profile_l);
  gtk_widget_show(profile_l);

  /* fill in data */
  l_select = fill_list(main_w);

  g_object_unref(G_OBJECT(store));

  props_fr = gtk_frame_new("Properties");
  gtk_box_pack_start(GTK_BOX(profile_vb), props_fr, FALSE, FALSE, 0);
  gtk_widget_show(props_fr);

  props_vb = gtk_vbox_new(FALSE, 3);
  gtk_container_set_border_width(GTK_CONTAINER(props_vb), 5);
  gtk_container_add(GTK_CONTAINER(props_fr), props_vb);
  gtk_widget_show(props_vb);

  /* row: Profile name entry */
  middle_hb = gtk_hbox_new(FALSE, 3);
  gtk_container_add(GTK_CONTAINER(props_vb), middle_hb);
  gtk_widget_show(middle_hb);

  name_lb = gtk_label_new("Profile name:");
  gtk_box_pack_start(GTK_BOX(middle_hb), name_lb, FALSE, FALSE, 0);
  gtk_widget_show(name_lb);

  name_te = gtk_entry_new();
  gtk_box_pack_start(GTK_BOX(middle_hb), name_te, TRUE, TRUE, 0);
  g_object_set_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY, name_te);
  g_signal_connect(name_te, "changed", G_CALLBACK(profile_name_te_changed_cb), NULL);
#ifdef _WIN32
  gtk_tooltips_set_tip (tooltips, name_te, "A profile name cannot start or end with a period (.), and cannot contain any of the following characters:\n   \\ / : * ? \" < > |", NULL);
#else
  gtk_tooltips_set_tip (tooltips, name_te, "A profile name cannot contain the '/' character", NULL);
#endif
  gtk_widget_show(name_te);

  /* button row (create all possible buttons and hide the unrequired later - it's a lot easier) */
  bbox = dlg_button_row_new(GTK_STOCK_OK, GTK_STOCK_APPLY, GTK_STOCK_CANCEL, GTK_STOCK_HELP, NULL);
  gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 5);
  gtk_widget_show(bbox);

  ok_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_OK);
  g_signal_connect(ok_bt, "clicked", G_CALLBACK(profile_dlg_ok_cb), NULL);
  gtk_tooltips_set_tip (tooltips, ok_bt, "Apply the profiles and close this dialog", NULL);

  /* Catch the "activate" signal on the profile name and profile
     list entries, so that if the user types Return
     there, we act as if the "OK" button had been selected, as
     happens if Return is typed if some widget that *doesn't*
     handle the Return key has the input focus. */
  dlg_set_activate(name_te, ok_bt);

  apply_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_APPLY);
  g_signal_connect(apply_bt, "clicked", G_CALLBACK(profile_dlg_apply_cb), NULL);
  gtk_tooltips_set_tip (tooltips, apply_bt, "Apply the profiles and keep this dialog open", NULL);

  cancel_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CANCEL);
  gtk_tooltips_set_tip (tooltips, cancel_bt, "Cancel the changes", NULL);
  g_signal_connect(cancel_bt, "clicked", G_CALLBACK(profile_dlg_cancel_cb), NULL);
  window_set_cancel_button(main_w, cancel_bt, NULL);

  help_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_HELP);
  g_signal_connect(help_bt, "clicked", G_CALLBACK(topic_cb), (gpointer)HELP_CONFIG_PROFILES_DIALOG);
  gtk_tooltips_set_tip (tooltips, help_bt, "Show topic specific help", NULL);

  if(ok_bt) {
    gtk_widget_grab_default(ok_bt);
  }


  /* DO SELECTION THINGS *AFTER* SHOWING THE DIALOG! */
  /* otherwise the updatings can get confused */
  if (l_select) {
    gtk_tree_selection_select_iter(sel, l_select);
    g_free(l_select);
  }

  if (profile_l) {
    gtk_widget_grab_focus(profile_l);
  }

  g_signal_connect(main_w, "delete_event", G_CALLBACK(profile_dlg_delete_event_cb), NULL);
  g_signal_connect(main_w, "destroy", G_CALLBACK(profile_dlg_destroy_cb), NULL);

  gtk_widget_show(main_w);

  window_present(main_w);

  return main_w;
}


static void
select_profile_cb (GtkWidget *w _U_, gpointer data)
{
  const gchar *current_profile = get_profile_name ();
  gchar       *selected_profile = (gchar *) data;

  if (strcmp (selected_profile, current_profile) != 0) {
    change_configuration_profile (selected_profile);
  }
}

gboolean
profile_show_popup_cb (GtkWidget *w _U_, GdkEvent *event)
{
  GdkEventButton *bevent = (GdkEventButton *)event;
  const gchar    *profile_name = get_profile_name ();
  const gchar    *profiles_dir, *name;
  WS_DIR         *dir;             /* scanned directory */
  WS_DIRENT      *file;            /* current file */
  GtkWidget      *menu;
  GtkWidget      *menu_item;

  menu = gtk_menu_new ();

  /* Add a menu item for the Default profile */
  menu_item = gtk_check_menu_item_new_with_label (DEFAULT_PROFILE);
  if (strcmp (profile_name, DEFAULT_PROFILE)==0) {
    /* Check current profile */
    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(menu_item), TRUE);
  }
  g_object_set (G_OBJECT(menu_item), "draw-as-radio", TRUE, NULL);
  g_signal_connect (menu_item, "activate", G_CALLBACK(select_profile_cb), g_strdup (DEFAULT_PROFILE));
  gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item);
  gtk_widget_show (menu_item);

  profiles_dir = get_profiles_dir();
  if ((dir = ws_dir_open(profiles_dir, 0, NULL)) != NULL) {
    while ((file = ws_dir_read_name(dir)) != NULL) {
      name = ws_dir_get_name(file);

      if (profile_exists(name)) {
	menu_item = gtk_check_menu_item_new_with_label (name);
	if (strcmp (name, profile_name)==0) {
	  /* Check current profile */
	  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(menu_item), TRUE);
	}
	g_object_set (G_OBJECT(menu_item), "draw-as-radio", TRUE, NULL);
	g_signal_connect (menu_item, "activate", G_CALLBACK(select_profile_cb), g_strdup (name));
	gtk_menu_shell_append  (GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show (menu_item);
      }
    }
  }

  gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL,
		  bevent->button, bevent->time);

  return TRUE;
}

/* Create a profile dialog for editing display profiles; this is to be used
   as a callback for menu items, toolbars, etc.. */
void
profile_dialog_cb(GtkWidget *w _U_)
{
  /* Has a profiles dialog box already been opened */
  if (global_profile_w != NULL) {
    /* Yes.  Just reactivate it. */
    reactivate_window(global_profile_w);
  } else {
    global_profile_w = profile_dialog_new ();
  }
}
