/*
    ragmaan/main.c            (C) 2003 Raymond (zandbergen@home.nl)

    "ragmaan" is an anagram generator

    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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <gtk/gtk.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gettext.h"
#include "ragmaan.h"
#include "hash_func.h"
#include "search.h"
#include "db_files.h"
#include "state_stack.h"
#include "idle_task.h"
#include "scratchpad.h"
#include "sort.h"
#include "config_file.h"
#include "find.h"


HASH_TABLE_T hash_table = { 0 };
gchar *db_name = NULL;
static void update_buttons_active_state (void);
static void load_db_selected (GtkWidget * button, GtkFileSelection * data);
static void load_db_name (GtkWindow * parent, gchar * file_name);
static void about_box (void);
static gpointer load_db_background (gpointer data);
static GtkTreeModel *create_model (const gchar * target);

struct global_widgets_s global_widgets;

void
set_busy_cursor (gboolean b)
{
  if (b != global_widgets.is_busy)
    {
      gdk_window_set_cursor (GTK_WIDGET (global_widgets.main_window)->window,
			     b ? global_widgets.busy_cursor : global_widgets.
			     default_cursor);
    }
  global_widgets.is_busy = b;
}

static gint
close_application (GtkWidget * widget, GdkEvent * event, gpointer unused)
{
  if (scratchpad_get_modified ())
    {
      gint response;
      GtkWidget *dialog;
      dialog =
	gtk_message_dialog_new (GTK_WINDOW (global_widgets.main_window),
				GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
				_("The results have not been saved. "
				  "Quit anyway?"));
      response = gtk_dialog_run (GTK_DIALOG (dialog));
      gtk_widget_destroy (dialog);
      if (response == GTK_RESPONSE_CANCEL)
	return TRUE;
    }
  gtk_main_quit ();
  return FALSE;
}

gboolean
action_allowed (gint action)
{
  switch (action)
    {
    case ALLOW_BACK:
      return state_stack_depth () > 0;
    case ALLOW_FORWARD:
      {
	GtkTreeSelection *sel;
	GtkTreeModel *m;
	GtkTreeIter iter;
	sel =
	  gtk_tree_view_get_selection (GTK_TREE_VIEW
				       (global_widgets.result_tree_view));
	if (gtk_tree_selection_get_selected (sel, &m, &iter))
	  {
	    gint status;
	    gtk_tree_model_get (m, &iter, COLUMN_STATUS, &status, -1);
	    return status != SUBWORD_STATE_FINISHED
	      && status != SUBWORD_STATE_DEAD;
	  }
	else			/* nothing selected */
	  return FALSE;
      }
    case ALLOW_COPY:
      return g_ascii_strcasecmp (gtk_label_get_text
				 (GTK_LABEL
				  (global_widgets.intermediate_result_label)),
				 "-") != 0;
    case ALLOW_FIND:
      {
	GtkTreeModel *m;
	m =
	  gtk_tree_view_get_model (GTK_TREE_VIEW
				   (global_widgets.result_tree_view));
	return gtk_tree_model_iter_n_children (m, NULL) > 0;
      }
    }
  return FALSE;
}

static void
update_buttons_active_state (void)
{
  gtk_widget_set_sensitive (global_widgets.toolbar_button[TOOLBAR_FORWARD],
			    action_allowed (ALLOW_FORWARD));
  gtk_widget_set_sensitive (global_widgets.toolbar_button[TOOLBAR_BACK],
			    action_allowed (ALLOW_BACK));
  gtk_widget_set_sensitive (global_widgets.toolbar_button[TOOLBAR_COPY],
			    action_allowed (ALLOW_COPY));
  gtk_widget_set_sensitive (global_widgets.toolbar_button[TOOLBAR_FIND],
			    action_allowed (ALLOW_FIND));
}

static gint
cmp_str (gconstpointer a, gconstpointer b)
{
  return g_ascii_strcasecmp ((gchar *) a, (gchar *) b);
}

GList *
history_update (GList * history, const gchar * new_entry)
{
  GList *from_hist;
  from_hist = g_list_find_custom (history, new_entry, cmp_str);
  if (from_hist)
    history = g_list_delete_link (history, from_hist);
  history = g_list_prepend (history, g_strdup (new_entry));
  while (g_list_length (history) > TARGET_HISTORY_SIZE)
    {
      GList *killme = g_list_last (history);
      g_free (killme->data);
      history = g_list_delete_link (history, killme);
    }
  return history;
}

static gint
search (GtkWidget * unused1, gpointer unused2)
{
  const gchar *p;
  GtkTreeModel *m;

  p =
    gtk_entry_get_text (GTK_ENTRY
			(GTK_COMBO (global_widgets.target_entry)->entry));
  if (!p[0])
    return FALSE;
  idle_task_stop ();
  state_stack_clear (p);
  gtk_label_set_text (GTK_LABEL (global_widgets.intermediate_result_label),
		      "-");
  m =
    gtk_tree_view_get_model (GTK_TREE_VIEW (global_widgets.result_tree_view));
  g_object_unref (G_OBJECT (m));
  set_busy_cursor (TRUE);
  while (gtk_events_pending ())
    gtk_main_iteration ();
  gtk_tree_view_set_model (GTK_TREE_VIEW (global_widgets.result_tree_view),
			   create_model (p));
  scratchpad_printf ("\n%s:\n\n", p);
  update_buttons_active_state ();
  global_widgets.target_history =
    history_update (global_widgets.target_history, p);
  gtk_combo_set_popdown_strings (GTK_COMBO (global_widgets.target_entry),
				 global_widgets.target_history);
  idle_task_init ();
  idle_task_start ();
  return TRUE;
}

static gint
descend (GtkWidget * unused1, gpointer unused2)
{
  gchar *p;
  gchar *buf;
  if (!action_allowed (ALLOW_FORWARD))
    return TRUE;
  idle_task_stop ();
  p = state_stack_rest ();
  state_stack_tos->model =
    gtk_tree_view_get_model (GTK_TREE_VIEW (global_widgets.result_tree_view));
  set_busy_cursor (TRUE);
  while (gtk_events_pending ())
    gtk_main_iteration ();
  gtk_tree_view_set_model (GTK_TREE_VIEW (global_widgets.result_tree_view),
			   create_model (p));
  g_free (p);
  gtk_tree_view_scroll_to_point (GTK_TREE_VIEW
				 (global_widgets.result_tree_view), 1, 1);
  state_stack_push ();
  buf = state_stack_to_string ();
  gtk_label_set_text (GTK_LABEL
		      (global_widgets.intermediate_result_label), buf);
  g_free (buf);
  idle_task_init ();
  idle_task_start ();
  update_buttons_active_state ();
  return TRUE;
}

static gint
ascend (GtkWidget * widget, gpointer unused)
{
  GtkTreeModel *m;
  GtkTreeSelection *sel;
  gchar *buf;
  gint n;

  if (!action_allowed (ALLOW_BACK))
    return TRUE;
  idle_task_stop ();
  if (state_stack_tos->selected_path)
    {
      gtk_tree_path_free (state_stack_tos->selected_path);
      state_stack_tos->selected_path = NULL;
    }
  state_stack_pop ();
  set_busy_cursor (TRUE);
  while (gtk_events_pending ())
    gtk_main_iteration ();
  m =
    gtk_tree_view_get_model (GTK_TREE_VIEW (global_widgets.result_tree_view));
  g_object_unref (G_OBJECT (m));
  m = state_stack_tos->model;
  gtk_tree_view_set_model (GTK_TREE_VIEW (global_widgets.result_tree_view),
			   m);
  sel =
    gtk_tree_view_get_selection (GTK_TREE_VIEW
				 (global_widgets.result_tree_view));
  gtk_tree_selection_select_path (sel, state_stack_tos->selected_path);
  buf = state_stack_to_string ();
  gtk_label_set_text (GTK_LABEL
		      (global_widgets.intermediate_result_label), buf);
  g_free (buf);
  n = gtk_tree_model_iter_n_children (m, NULL);
  if (n == 1)
    print_to_statusbar (_("found %d word"), n);
  else
    print_to_statusbar (_("found %d words"), n);
  while (gtk_events_pending ())
    gtk_main_iteration ();
  gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW
				(global_widgets.result_tree_view),
				state_stack_tos->selected_path, NULL, TRUE,
				0.5, 0.5);
  update_buttons_active_state ();
  idle_task_start ();
  return TRUE;
}

static GtkTreeModel *
create_model (const gchar * target)
{
  gint i = 0;
  GtkListStore *store;
  GtkTreeIter iter;
  SEARCH_STATE_T search_state;
  gchar *s;
  gchar *rest;
  gint rest_stack_count[26];

  store = gtk_list_store_new (COLUMN_MAX,
			      G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
  if (hash_table.valid)
    {
      init_search (&hash_table, &search_state, target,
		   rm_str_real_len (target), 1, 0);
      rest = state_stack_rest ();	/* for size only */
      char_count (rest, rest_stack_count);
      while ((s = find_word_wl (&search_state, &hash_table)))
	{
	  subtract2 (rest, rest_stack_count, s);
	  gtk_list_store_append (store, &iter);
	  gtk_list_store_set (store, &iter,
			      COLUMN_STATUS,
			      rest[0] ==
			      0 ? SUBWORD_STATE_FINISHED :
			      SUBWORD_STATE_DESCEND_UNKNOWN, COLUMN_SUBWORD,
			      s, COLUMN_REST, rest, -1);
	  i++;
	}
      g_free (rest);
      gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), COLUMN_REST,
				       sort_rest, NULL, NULL);
      gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
				       COLUMN_STATUS, sort_status, NULL,
				       NULL);
      if (i == 1)
	print_to_statusbar (_("found %d word"), i);
      else
	print_to_statusbar (_("found %d words"), i);
    }
  else
    print_to_statusbar (_("no wordlist read"));
  return GTK_TREE_MODEL (store);
}

static void
tree_selection_changed_cb (GtkTreeSelection * selection, gpointer data)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  gchar *selected_target;
  gchar *buf;
  if (gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      gtk_tree_model_get (model, &iter, COLUMN_SUBWORD, &selected_target, -1);
      if (state_stack_tos->selected_path)
	gtk_tree_path_free (state_stack_tos->selected_path);
      state_stack_tos->selected_path = gtk_tree_model_get_path (model, &iter);
      state_stack_store (selected_target);
      buf = state_stack_to_string ();
      gtk_label_set_text (GTK_LABEL
			  (global_widgets.intermediate_result_label), buf);
      g_free (buf);
      g_free (selected_target);
    }
  update_buttons_active_state ();
}

static void
status_column_set_pixbuf (GtkTreeViewColumn * tree_column,
			  GtkCellRenderer * cell,
			  GtkTreeModel * model,
			  GtkTreeIter * iter, gpointer data)
{
  gint status;

  gtk_tree_model_get (model, iter, 0, &status, -1);
  g_object_set (GTK_CELL_RENDERER (cell), "pixbuf",
		global_widgets.pixbuf[status], NULL);
}

static void
add_columns (GtkTreeView * treeview)
{
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkTreeSelection *select;

  column = gtk_tree_view_column_new ();
  gtk_tree_view_column_set_title (column, _("Status"));
  renderer = gtk_cell_renderer_pixbuf_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_cell_data_func (column, renderer,
					   status_column_set_pixbuf, NULL,
					   NULL);
  gtk_tree_view_column_set_sort_column_id (column, COLUMN_STATUS);
  gtk_tree_view_column_set_resizable (column, TRUE);
  gtk_tree_view_append_column (treeview, column);

  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes (_("Result"),
						     renderer,
						     "text",
						     COLUMN_SUBWORD, NULL);
  gtk_tree_view_column_set_sort_column_id (column, COLUMN_SUBWORD);
  gtk_tree_view_column_set_resizable (column, TRUE);
  gtk_tree_view_append_column (treeview, column);

  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes (_("Remaining"),
						     renderer,
						     "text",
						     COLUMN_REST, NULL);
  gtk_tree_view_column_set_sort_column_id (column, COLUMN_REST);
  gtk_tree_view_column_set_resizable (column, TRUE);
  gtk_tree_view_append_column (treeview, column);


  gtk_tree_view_set_enable_search (GTK_TREE_VIEW (treeview), FALSE);
  select = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
  gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
  g_signal_connect (G_OBJECT (select), "changed",
		    G_CALLBACK (tree_selection_changed_cb), NULL);
}

static void
descend_or_copy (GtkTreeView * p1, GtkTreePath * a1 /*ptr??? */ ,
		 GtkTreeViewColumn * p2, gpointer a2)
{
  if (action_allowed (ALLOW_FORWARD))
    descend (NULL, NULL);
  else if (action_allowed (ALLOW_COPY))
    scratchpad_copy (NULL, NULL);
}

static GtkWidget *
new_list (void)
{
  GtkWidget *sw;
  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
				       GTK_SHADOW_ETCHED_IN);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
				  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

  global_widgets.result_tree_view = gtk_tree_view_new ();
  gtk_tree_view_set_rules_hint (GTK_TREE_VIEW
				(global_widgets.result_tree_view), TRUE);

  gtk_container_add (GTK_CONTAINER (sw), global_widgets.result_tree_view);
  gtk_widget_set_size_request (sw, 300, 300);

  add_columns (GTK_TREE_VIEW (global_widgets.result_tree_view));
  gtk_tree_view_set_model (GTK_TREE_VIEW (global_widgets.result_tree_view),
			   create_model (""));

  return sw;
}

static gint
db_load_cb (GtkWidget * unused1, gpointer unused2)
{
  new_file_selector (_("load wordlist"), load_db_selected, db_name);
  return TRUE;
}

struct load_window_s
{
  gchar *file_name;
  gchar *error_message;
  GtkWidget *window;
  GtkWidget *vbox;
  GtkWidget *label;
  GtkWidget *progress_bar[2];
  gint progress_bar_idx;
};

static void
show_db_progress (void *data, gdouble fraction)
{
  struct load_window_s *lw = data;
  gdk_threads_enter ();
  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR
				 (lw->progress_bar[lw->progress_bar_idx]),
				 fraction);
  gdk_threads_leave ();
}

static gboolean
db_load_error (gpointer data)
{
  struct load_window_s *lw = data;
  GtkWidget *dialog;
  gdk_threads_enter ();
  dialog =
    gtk_message_dialog_new (GTK_WINDOW (global_widgets.main_window),
			    GTK_DIALOG_DESTROY_WITH_PARENT,
			    GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
			    lw->error_message);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (GTK_WIDGET (dialog));
  g_free (lw->error_message);
  gdk_threads_leave ();
  return 0;
}

static void
load_db_selected (GtkWidget * button, GtkFileSelection * fs)
{
  GtkWindow *parent = GTK_WINDOW (fs);
  gchar *file_name = g_strdup (gtk_file_selection_get_filename (fs));
  gtk_widget_destroy (GTK_WIDGET (parent));
  load_db_name (parent, file_name);
}

static void
load_db_name (GtkWindow * parent, gchar * file_name)
{
  static struct load_window_s load_window;
  gchar *buf;
  GtkWidget *icon;
  GtkWidget *hbox;
  gint i;
  parent = GTK_WINDOW (global_widgets.main_window);

  load_window.file_name = file_name;

  print_to_statusbar (_("reading wordlist from %s"), load_window.file_name);
  load_window.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_transient_for (GTK_WINDOW (load_window.window),
				GTK_WINDOW (global_widgets.main_window));
  gtk_window_set_destroy_with_parent (GTK_WINDOW (load_window.window), TRUE);
  gtk_window_set_modal (GTK_WINDOW (load_window.window), TRUE);
  gtk_window_set_title (GTK_WINDOW (load_window.window),
			_("reading wordlist"));
  load_window.vbox = gtk_vbox_new (FALSE, 10);
  gtk_container_add (GTK_CONTAINER (load_window.window), load_window.vbox);
  gtk_container_set_border_width (GTK_CONTAINER (load_window.window), 10);

  hbox = gtk_hbox_new (FALSE, 10);
  gtk_container_add (GTK_CONTAINER (load_window.vbox), hbox);
  icon = gtk_image_new_from_stock (GTK_STOCK_CDROM, GTK_ICON_SIZE_BUTTON);
  gtk_container_add (GTK_CONTAINER (hbox), icon);
  buf = g_strdup_printf (_("reading wordlist\n%s"), load_window.file_name);
  load_window.label = gtk_label_new (buf);
  g_free (buf);
  gtk_container_add (GTK_CONTAINER (hbox), load_window.label);
  for (i = 0; i < 2; i++)
    {
      load_window.progress_bar[i] = gtk_progress_bar_new ();
      gtk_container_add (GTK_CONTAINER (load_window.vbox),
			 load_window.progress_bar[i]);
    }
  gtk_widget_show_all (load_window.window);
  g_thread_create (load_db_background, &load_window, FALSE, NULL);
}

static gpointer
load_db_background (gpointer data)
{
  size_t file_size;
  gchar *all_words;
  struct load_window_s *lw = data;

  lw->progress_bar_idx = 0;
  gdk_threads_enter ();
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (lw->progress_bar[0]),
			     _("reading"));
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (lw->progress_bar[1]),
			     _("indexing"));
  gdk_threads_leave ();
  if (hash_table.valid)
    {
      free_hash_table (&hash_table);
      free (hash_table.dictionary_start);
    }
  all_words =
    read_words_from_file (lw->file_name, &file_size, show_db_progress, lw);
  if (!all_words)
    {
      /* cannot run a modal error dialog from a background thread */
      /* so schedule a high-priority idle task in the main thread */
      lw->error_message = g_strdup_printf (_("could not open\n %s:\n%s\n"),
					   lw->file_name, g_strerror (errno));
      gdk_threads_enter ();
      print_to_statusbar (_("no wordlist read"));
      gtk_idle_add_priority (G_PRIORITY_HIGH_IDLE, db_load_error, lw);
      gtk_widget_destroy (GTK_WIDGET (lw->window));
      gdk_threads_leave ();
      return NULL;
    }
  else
    {
      lw->progress_bar_idx = 1;
      if (-1 ==
	  create_hash (&hash_table, all_words, file_size, show_db_progress,
		       lw))
	{
	  free (all_words);
	  lw->error_message =
	    g_strdup_printf (_("%s\nis not a valid wordlist"), lw->file_name);
	  gdk_threads_enter ();
	  print_to_statusbar (_("no wordlist read"));
	  gtk_idle_add_priority (G_PRIORITY_HIGH_IDLE, db_load_error, lw);
	  gtk_widget_destroy (GTK_WIDGET (lw->window));
	  gdk_threads_leave ();
	  return NULL;
	}
    }
  if (db_name)
    g_free (db_name);
  db_name = g_strdup (lw->file_name);
  gdk_threads_enter ();
  print_to_statusbar (_("%d words read"), hash_table.nof_words);
  gtk_widget_destroy (GTK_WIDGET (lw->window));
  gdk_threads_leave ();
  return NULL;
}

void
print_to_statusbar (const gchar * fmt, ...)
{
  gchar *s;
  gchar *buf;
  va_list ap;

  va_start (ap, fmt);
  s = g_strdup_vprintf (fmt, ap);
  buf = g_strconcat (" ", s, NULL);
  gtk_statusbar_pop (GTK_STATUSBAR (global_widgets.statusbar), 0);
  gtk_statusbar_push (GTK_STATUSBAR (global_widgets.statusbar), 0, buf);
  g_free (s);
  g_free (buf);
  va_end (ap);
}

static GtkWidget *
icon_label_box (gchar * stock_id, gchar * label_text)
{
  GtkWidget *box, *label, *image;

  box = gtk_hbox_new (FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (box), 2);
  image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
  label = gtk_label_new (NULL);
  gtk_label_set_markup (GTK_LABEL (label), label_text);
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
  gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 3);
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 3);
  gtk_widget_show (image);
  gtk_widget_show (label);
  return box;
}

static void
about_box (void)
{
  gchar *buf;
  GtkWidget *button, *labelbox, *label;
  GtkWidget *dialog = gtk_dialog_new_with_buttons (_("About ..."),
						   GTK_WINDOW (global_widgets.
							       main_window),
						   GTK_DIALOG_MODAL, 0);
  gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)), 10);
  buf = g_strdup_printf (_("<b>%s version %s</b> - "
			   "An anagram generator"), PACKAGE, VERSION);
  labelbox = icon_label_box (GTK_STOCK_DIALOG_INFO, buf);
  g_free (buf);
  gtk_widget_show (labelbox);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
		      labelbox, TRUE, TRUE, 0);
  label = gtk_label_new (NULL);
  gtk_label_set_markup (GTK_LABEL (label),
			_("<i>Author:</i> Raymond (zandbergen@home.nl) - "
			  "GPL applies - <b>No Warranty</b>"));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
		      label, TRUE, TRUE, 10);
  gtk_window_set_policy (GTK_WINDOW (dialog), FALSE, FALSE, FALSE);
  gtk_widget_show (label);
  button = gtk_button_new_from_stock (GTK_STOCK_OK);
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  g_signal_connect_swapped (G_OBJECT (button), "clicked",
			    G_CALLBACK (gtk_widget_destroy),
			    G_OBJECT (dialog));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), button,
		      TRUE, FALSE, 0);
  gtk_widget_show (button);
  gtk_widget_show (dialog);
}

void
new_file_selector (const gchar * title,
		   void (handle_filename) (GtkWidget *, GtkFileSelection *),
		   gchar * default_selection)
{
  GtkWidget *file_selector;

  file_selector = gtk_file_selection_new (title);
  if (default_selection)
    gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_selector),
				     default_selection);

  g_signal_connect (GTK_OBJECT
		    (GTK_FILE_SELECTION (file_selector)->ok_button),
		    "clicked", G_CALLBACK (handle_filename),
		    (gpointer) file_selector);

  g_signal_connect_swapped (GTK_OBJECT
			    (GTK_FILE_SELECTION (file_selector)->
			     cancel_button), "clicked",
			    G_CALLBACK (gtk_widget_destroy),
			    (gpointer) file_selector);

  gtk_widget_show (file_selector);
}

static void
store_toolbar_button (GtkWidget * i, gpointer unused)
{
  static gint j;
  global_widgets.toolbar_button[j++] = i;
}

static GtkWidget *
new_toolbar (void)
{
  GtkWidget *toolbar, *icon;

  toolbar = gtk_toolbar_new ();
  icon =
    gtk_image_new_from_stock (GTK_STOCK_GO_BACK, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("back"),
			   _("delete last word"), "back", icon,
			   G_CALLBACK (ascend), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
			      GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("forward"),
			   _("continue search in remainder"), "forward",
			   icon, G_CALLBACK (descend), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("find"),
			   _("find in results"), "find",
			   icon, G_CALLBACK (find_cb), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("scratchpad"),
			   _("copy result to scratchpad"), "scratchpad", icon,
			   G_CALLBACK (scratchpad_copy), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_NEW, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("clear"),
			   _("erase scratchpad"), "clear", icon,
			   G_CALLBACK (scratchpad_clear), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("open"),
			   _("load scratchpad"), "load", icon,
			   G_CALLBACK (scratchpad_load), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("save"),
			   _("save scratchpad"), "save", icon,
			   G_CALLBACK (scratchpad_save), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_CDROM, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("wordlist"),
			   _("load wordlist"), "wordlist", icon,
			   G_CALLBACK (db_load_cb), NULL);
  icon =
    gtk_image_new_from_stock (GTK_STOCK_QUIT, GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_toolbar_append_item (GTK_TOOLBAR (toolbar), _("exit"),
			   _("exit program"), "exit", icon,
			   G_CALLBACK (close_application), NULL);
  gtk_container_foreach (GTK_CONTAINER (toolbar), store_toolbar_button, NULL);
  gtk_widget_show (toolbar);
  return toolbar;
}

static GtkWidget *
new_complex_entry (void)
{
  /* label + combobox + OK-button */
  GtkWidget *label, *hbox, *button;

  GtkTooltips *tooltips;
  tooltips = gtk_tooltips_new ();
  hbox = gtk_hbox_new (FALSE, 10);
  gtk_widget_show (hbox);

  label = gtk_label_new (_("input:"));
  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);

  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 10);

  gtk_widget_show (label);

  global_widgets.target_history = NULL;
  global_widgets.target_entry = gtk_combo_new ();
  gtk_combo_disable_activate (GTK_COMBO (global_widgets.target_entry));
  g_signal_connect (G_OBJECT (GTK_COMBO (global_widgets.target_entry)->entry),
		    "activate", G_CALLBACK (search), NULL);

  gtk_widget_show (global_widgets.target_entry);
  gtk_box_pack_start (GTK_BOX (hbox), global_widgets.target_entry, TRUE, TRUE,
		      0);

  button = gtk_button_new_from_stock (GTK_STOCK_OK);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 10);

  gtk_tooltips_set_tip (tooltips, button, _("search for anagrams"), NULL);
  gtk_widget_show (button);
  g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (search), NULL);
  return hbox;

}

static GtkWidget *
new_panes (void)
{
  GtkWidget *label;
  GtkWidget *list;
  GtkWidget *left, *right, *resultbox;
  GtkWidget *paned;

  paned = gtk_hpaned_new();
  left = gtk_vbox_new (FALSE, 0);
  right = gtk_vbox_new (FALSE, 0);
  resultbox = gtk_hbox_new (FALSE, 0);
  gtk_paned_add1(GTK_PANED(paned),left);
  gtk_paned_add2(GTK_PANED(paned),right);
  gtk_box_pack_start (GTK_BOX (left), resultbox, FALSE, FALSE, 10);
  label = gtk_label_new (_("result:"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (resultbox), label, FALSE, FALSE, 10);
  label = gtk_label_new ("-");
  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (resultbox), label, TRUE, TRUE, 10);
  global_widgets.intermediate_result_label = label;
  list = new_list ();
  gtk_box_pack_start (GTK_BOX (left), list, TRUE, TRUE, 10);
  label = gtk_label_new (_("scratchpad:"));
  g_signal_connect (G_OBJECT (global_widgets.result_tree_view),
		    "row-activated", G_CALLBACK (descend_or_copy), NULL);
  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (right), label, FALSE, FALSE, 10);
  gtk_box_pack_start (GTK_BOX (right), new_scratchpad(), TRUE, TRUE, 10);
  return paned;
}

static GtkItemFactoryEntry menu_items[] = {
  {N_("/_File"), NULL, 0, 0, "<Branch>"},
  {N_("/File/_Clear scratchpad"), NULL,
   (GtkItemFactoryCallback1) scratchpad_clear, 0},
  {N_("/File/_Open scratchpad"), NULL,
   (GtkItemFactoryCallback1) scratchpad_load, 0},
  {N_("/File/_Save scratchpad"), NULL,
   (GtkItemFactoryCallback1) scratchpad_save, 0},
  {N_("/File/Save scratchpad _As..."), NULL,
   (GtkItemFactoryCallback1) scratchpad_save_as, 0},
  {N_("/File/sep1"), NULL, NULL, 0, "<Separator>"},
  {N_("/File/Open wordlist"), NULL, (GtkItemFactoryCallback1) db_load_cb,
   0},
  {N_("/File/sep2"), NULL, NULL, 0, "<Separator>"},
  {N_("/File/_Quit"), NULL, (GtkItemFactoryCallback1) close_application,
   0},

  {N_("/_Edit"), NULL, 0, 0, "<Branch>"},
  {N_("/Edit/_Back"), NULL, (GtkItemFactoryCallback1) ascend, 0},
  {N_("/Edit/Forwar_d"), NULL, (GtkItemFactoryCallback1) descend, 0},
  {N_("/Edit/sep1"), NULL, NULL, 0, "<Separator>"},
  {N_("/Edit/_Find"), NULL, (GtkItemFactoryCallback1) find_cb, 0},
  {N_("/Edit/sep2"), NULL, NULL, 0, "<Separator>"},
  {N_("/Edit/_Result to scratchpad"), NULL,
   (GtkItemFactoryCallback1) scratchpad_copy, 0},

  {N_("/_Help"), NULL, 0, 0, "<LastBranch>"},
  {N_("/Help/abou_t"), NULL, about_box, 0},
  {NULL, NULL, NULL, 0}
};

static void
new_main_window (void)
{
  GtkWidget *main_vbox;
  gint i;
  GtkAccelGroup *accel_group;
  GtkItemFactory *item_factory;

  /* main window */
  global_widgets.main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (global_widgets.main_window), PACKAGE);
  g_signal_connect (G_OBJECT (global_widgets.main_window), "delete_event",
		    G_CALLBACK (close_application), NULL);

  main_vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (global_widgets.main_window), main_vbox);
  gtk_widget_show (main_vbox);

  /* menu */
  accel_group = gtk_accel_group_new ();
  item_factory =
    gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", accel_group);
  g_object_set_data_full (G_OBJECT (global_widgets.main_window), "<main>",
			  item_factory, (GDestroyNotify) g_object_unref);
  gtk_window_add_accel_group (GTK_WINDOW (global_widgets.main_window),
			      accel_group);
  for (i = 0; menu_items[i].path; i++)
    {
      /* translate menu items */
      gchar *acc = NULL;
      menu_items[i].path = gettext (menu_items[i].path);
      if (menu_items[i].item_type == 0)
	acc = strrchr (menu_items[i].path, '_');
      if (acc)
	acc = g_strdup_printf ("<control>%c", acc[1]);
      menu_items[i].accelerator = acc;
    }
  gtk_item_factory_create_items (item_factory, i, menu_items, NULL);
  gtk_box_pack_start (GTK_BOX (main_vbox),
		      gtk_item_factory_get_widget (item_factory, "<main>"),
		      FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (main_vbox), new_toolbar (), FALSE, FALSE, 0);
  /* statusbar */
  global_widgets.statusbar = gtk_statusbar_new ();
  /* push a dummy so print_to_statusbar() can do a pop the first time */

  gtk_statusbar_push (GTK_STATUSBAR (global_widgets.statusbar), 0, "");
  /* statusbar packing postponed ... */
  /* main entry stuff */
  gtk_box_pack_start (GTK_BOX (main_vbox), new_complex_entry (), FALSE, FALSE,
		      10);
  gtk_box_pack_start (GTK_BOX (main_vbox), new_panes (), TRUE, TRUE, 10);
  gtk_box_pack_start (GTK_BOX (main_vbox), global_widgets.statusbar, FALSE,
		      FALSE, 0);
  gtk_widget_show (global_widgets.statusbar);
  print_to_statusbar (PACKAGE_STRING);

  gtk_widget_show (global_widgets.main_window);
  gtk_widget_show_all (global_widgets.main_window);
  update_buttons_active_state ();
}

static void
init_global_widgets (void)
{
  gint i;
  /* load some icons */
  for (i = 0; i < SUBWORD_STATE_MAX; i++)
    {
      static const gchar *subword_state_pic[] = {
	"applet-okay.png", "folder-open.png", "folder-closed.png",
	"emblem-nowrite.png"
      };
      GError *err = NULL;
      gchar *buf = g_strdup_printf ("%s%c%s", DATADIR, G_DIR_SEPARATOR,
				    subword_state_pic[i]);
      global_widgets.pixbuf[i] = gdk_pixbuf_new_from_file (buf, &err);
      if (err)
	{
	  g_print ("%s\n", err->message);
	  g_error_free (err);
	}
      g_free (buf);
    }
  /* select cursors */
  global_widgets.default_cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
  global_widgets.busy_cursor = gdk_cursor_new (GDK_WATCH);
  global_widgets.is_busy = TRUE;
  global_widgets.find_window = NULL;
  global_widgets.find_history = NULL;
  set_busy_cursor (FALSE);
}

void
update_state_from_config (void)
{
  config_read ();
  if (global_widgets.target_history)
    gtk_combo_set_popdown_strings (GTK_COMBO (global_widgets.target_entry),
				   global_widgets.target_history);
  if (db_name)
    load_db_name (GTK_WINDOW (global_widgets.main_window),
		  g_strdup (db_name));
  else
    {
      db_name = g_strdup_printf ("%s%c", DATADIR, G_DIR_SEPARATOR);
      db_load_cb (NULL, NULL);
    }
}

int
main (int argc, char *argv[])
{
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
  g_thread_init (NULL);
  gdk_threads_init ();
  gtk_init (&argc, &argv);
  state_stack_init ();
  new_main_window ();
  init_global_widgets ();
  gdk_threads_enter ();
  update_state_from_config ();
  gtk_main ();
  config_write ();
  gdk_threads_leave ();
  return 0;
}
