Patch for GtkFileSelection



This patch enhanced GtkFileSelection in many ways. It puts the
directory box and file box in seperate panes, it eliminates the
redundant label at the top, implements multiple configurable sortable
columns, and implements a most-recently-opened-files list for each
application.

With -DUSE_GNOME, it has an option for a mime type column that can
display the mime type and icon of the file, and will store persistent
information using gnome-config.

It does this while being binary-compatible with the old interface as
well.

--- /home/quotemstr/software/gtk+-1.2.8/gtk/gtkfilesel.c	Thu Jan 27 09:39:54 2000
+++ gtkfilesel.c	Tue Feb 13 17:49:00 2001
@@ -1,4 +1,5 @@
-/* GTK - The GIMP Toolkit
+/* -*- c-file-style: "gnu"  -*-
+ * GTK - The GIMP Toolkit
  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
  *
  * This library is free software; you can redistribute it and/or
@@ -34,18 +35,21 @@
 #include <string.h>
 #include <errno.h>
 #include <pwd.h>
+#include <time.h>
 #include "fnmatch.h"
 
+#ifdef GTK_SOURCE
 #include "gdk/gdkkeysyms.h"
 #include "gtkbutton.h"
-#include "gtkentry.h"
+#include "gtkentry.h" 
 #include "gtkfilesel.h"
 #include "gtkhbox.h"
 #include "gtkhbbox.h"
+#include "gtkhpaned.h"
 #include "gtklabel.h"
 #include "gtklist.h"
-#include "gtklistitem.h"
-#include "gtkmain.h"
+#include "gtklistite.h"
+#include "gtkmain.h" 
 #include "gtkscrolledwindow.h"
 #include "gtksignal.h"
 #include "gtkvbox.h"
@@ -55,12 +59,32 @@
 #include "gtkclist.h"
 #include "gtkdialog.h"
 #include "gtkintl.h"
+#include "gtkarrow.h"
+#else
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#endif
+
+#ifdef USE_GNOME
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gnome.h>
+#endif
 
-#define DIR_LIST_WIDTH   180
+#ifndef GTK_SOURCE
+#define _(x) (x)
+#endif
+
+#define TOTAL_WIDTH 400
+
+#define DIR_LIST_WIDTH   (int)(0.333*TOTAL_WIDTH)
 #define DIR_LIST_HEIGHT  180
-#define FILE_LIST_WIDTH  180
+#define FILE_LIST_WIDTH  (int)(0.666*TOTAL_WIDTH)
 #define FILE_LIST_HEIGHT 180
 
+#define NON_GNOME_CONFIG_FILE ".gtkfilesel"
+
+#define NUM_MRU_ENTRIES 5
+
 /* I've put this here so it doesn't get confused with the 
  * file completion interface */
 typedef struct _HistoryCallbackArg HistoryCallbackArg;
@@ -79,6 +103,19 @@
 typedef struct _CompletionUserDir  CompletionUserDir;
 typedef struct _PossibleCompletion PossibleCompletion;
 
+typedef struct _SortingPair        SortingPair;
+typedef struct _PixmapCache        PixmapCache;
+
+typedef struct _Column     			Column;
+typedef struct _ColumnType 			ColumnType;
+typedef struct _FileCell   			FileCell;
+typedef struct _ColumnConfiguration ColumnConfiguration;
+typedef struct _ProcessFileData		ProcessFileData;
+typedef struct _Row                 Row;
+
+/* Returns current column configuration (allocated, so must be deleted later) */
+static ColumnConfiguration* the_column_configuration(void);
+
 /* Non-external file completion decls and structures */
 
 /* A contant telling PRCS how many directories to cache.  Its actually
@@ -183,8 +220,138 @@
   GList* directory_sent_storage;
 
   struct _CompletionUserDir *user_directories;
+
+  /* I know this doesn't beling here, but it is here
+	 because I need to add data without breaking binary
+	 compatibility */
+  
+  GList* row_list;
+  ColumnConfiguration* cc; /* The column configuration in use */
+
+  int dir_sort_ascending;
+  GtkWidget* dir_arrow;
+
+  GtkWidget* parent_scrolled_win;
+  GtkTooltips* tooltips;
+
+  GtkWidget* mru_box;
+
+  char* mru_list[NUM_MRU_ENTRIES];
+};
+
+struct _PixmapCache
+{
+  GdkPixmap *map;
+  GdkBitmap *mask;
+};
+
+/* This holds all the information needed to work with a given column.
+   It replaces several nasty hacks in the old code.
+*/
+
+/* Type is inferred from the contents of the pointers */
+struct _FileCell
+{
+  char* text;
+  GdkPixmap* pixmap;
+  GdkBitmap* mask;
+};
+
+struct _Row
+{
+  ColumnConfiguration* column_config;
+  char* filename; /* Filename of the row --- here so that we can get
+					 the filename regardless of how the user
+					 configured the columns */
+  FileCell** cells; /* Array of FileCell* */
+};
+
+static int magic_row_sort_function(const Row* row1, const Row* row2);
+static Row* row_create(const char* filename, const char* fullpath, ColumnConfiguration* cc);
+static void row_destroy(Row* r);
+
+static void rowlist_destroy(GList* rows);
+
+static void row_populate_clist(GList* rows, GtkCList* cl);
+
+
+struct _ProcessFileData
+{
+  const char*  filename;
+  const char*  fullpath;
+  struct stat* statbuf;
+};
+
+typedef FileCell* (*process_file_func)(Column* Col, const ProcessFileData* data);
+typedef Column* (*create_column_func)(void);
+
+typedef void (*delete_cell_func)(Column* col, FileCell* fs);
+typedef void (*delete_column_func)(Column* col);
+
+typedef int (*column_sort_func)(Column* currcol,
+								const FileCell* fs1,
+								const FileCell* fs2);
+
+/* Return dynamically-allocated string */
+typedef char* (*column_get_config_func)(Column* col);
+
+/* Pop up a dialog for configuration */
+typedef void (*column_configure_thyself_func)(Column* col);
+
+struct _ColumnType
+{
+  char* col_title;
+  char* description;
+  create_column_func constructor;
+};
+
+struct _Column
+{
+  const ColumnType*		col_type;
+  gboolean 				sort_ascending;
+  column_sort_func 		sorter;
+  process_file_func 	filecell_constructor;
+  delete_cell_func 		filecell_destructor;
+  delete_column_func 	destructor;
+
+  /* These are for display */
+  GtkWidget*            arrow; /* Arrow widget associated with column */
+
+  /* These are optional --- leave NULL if no config possible */
+  column_get_config_func        get_config;
+  column_configure_thyself_func config_thyself;
+};
+
+static void destroy_column_configuration(ColumnConfiguration* cc);
+static ColumnConfiguration* create_column_configuration(const char* savedconfig);
+static void columnconfig_set_sorting_column(ColumnConfiguration* cc,
+											int sortcol);
+static char* columnconfig_get_config(ColumnConfiguration* cc);
+
+static void columnconfig_configure_thyself(GtkWidget* w, GtkFileSelection* cc);
+
+static void
+file_handle_click_column(GtkCList* clist,
+						 gint column,
+						 GtkFileSelection* fs);
+static gboolean
+handle_popup_menu(GtkWidget* widget,
+				  GdkEventButton* event,
+				  GtkFileSelection* fs);
+
+struct _ColumnConfiguration
+{
+  GList* the_columns;
+  Column* current_sorting_column;
+  int sorting_ordinal;
+  int num_columns;
 };
 
+/* MRU List */
+static void mru_load(char** list);
+static void mru_prepend(char** list, char* item);
+static void mru_save(char** list);
+static void mru_populate(char** list, GtkCombo* gc);
 
 /* File completion functions which would be external, were they used
  * outside of this file.
@@ -193,7 +360,6 @@
 static CompletionState*    cmpl_init_state        (void);
 static void                cmpl_free_state        (CompletionState *cmpl_state);
 static gint                cmpl_state_okay        (CompletionState* cmpl_state);
-static gchar*              cmpl_strerror          (gint);
 
 static PossibleCompletion* cmpl_completion_matches(gchar           *text_to_complete,
 						   gchar          **remaining_text,
@@ -248,7 +414,6 @@
  */
 static gchar*              cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
 
-
 /* Directory operations. */
 static CompletionDir* open_ref_dir         (gchar* text_to_complete,
 					    gchar** remaining_text,
@@ -323,13 +488,18 @@
 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
 
-
-
 static GtkWindowClass *parent_class = NULL;
 
+/* Same thing, for the directory box) */
+static void get_dir_selection_click_column(
+	   GtkCList* clist, gint column, gpointer user_data);
+
 /* Saves errno when something cmpl does fails. */
 static gint cmpl_errno;
 
+/* Why isn't this function in glib? */
+static void realloc_concat(char** str, const char* add, const char* sep);
+
 GtkType
 gtk_file_selection_get_type (void)
 {
@@ -368,20 +538,130 @@
 }
 
 static void
+gtk_file_selection_filelist_init(GtkFileSelection *filesel)
+{
+  int i;
+  GList* cl;
+  char** file_titles;
+  GtkTooltips* tooltips;
+
+  tooltips = ((CompletionState*)(filesel->cmpl_state))->tooltips;
+  
+  /* The files clist */
+  file_titles = g_new(char*,
+					  ((CompletionState*)(filesel->cmpl_state))->cc->num_columns+1);
+  
+  i=0;
+  cl= ((CompletionState*)(filesel->cmpl_state))->cc->the_columns;
+  while(cl)
+	{
+	  Column* cur_col = (Column*)cl->data;
+	  file_titles[i++] = cur_col->col_type->col_title;
+	  cl=cl->next;
+	}
+  cl= ((CompletionState*)(filesel->cmpl_state))->cc->the_columns;
+  file_titles[i]="";
+  filesel->file_list = gtk_clist_new_with_titles (i, file_titles);
+  gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
+  gtk_clist_set_row_height(GTK_CLIST(filesel->file_list),
+						   1.2*(filesel->file_list->style->font->ascent+
+								filesel->file_list->style->font->descent));
+  gtk_clist_set_auto_sort(GTK_CLIST(filesel->file_list), FALSE);
+  gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
+					  (GtkSignalFunc) gtk_file_selection_file_button, 
+					  (gpointer) filesel);
+  gtk_signal_connect (GTK_OBJECT (filesel->file_list), "click-column",
+					  (GtkSignalFunc) file_handle_click_column,
+					  (gpointer) filesel);
+  gtk_signal_connect (GTK_OBJECT(filesel->file_list), "button-press-event",
+					  (GtkSignalFunc) handle_popup_menu,
+					  (gpointer) filesel);
+  
+  /* Make special widgets work */
+  i=0;
+  while(cl)
+	{
+	  Column* cur_col = (Column*)cl->data;	  
+	  GtkWidget* label=gtk_label_new(file_titles[i]);
+	  GtkWidget* arrow=gtk_arrow_new(GTK_ARROW_DOWN,
+									 GTK_SHADOW_NONE);
+	  GtkWidget* evbox=gtk_event_box_new();
+	  GtkWidget* hbox=gtk_hbox_new(FALSE, 0);
+	  gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+	  gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
+	  gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
+	  gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.0f);
+	  gtk_widget_show(label); gtk_widget_show(arrow);
+	  gtk_misc_set_padding(GTK_MISC(arrow), 0, 0);
+	  gtk_container_add(GTK_CONTAINER(evbox),hbox);
+	  gtk_widget_show(hbox);
+	  
+	  gtk_clist_set_column_widget(GTK_CLIST(filesel->file_list),
+								  i, evbox);
+	  cur_col->arrow=arrow;
+
+	  /* Tooltips */
+	  gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), evbox,
+						   cur_col->col_type->description,"");
+	  
+	  ++i;	  
+	  cl=cl->next;
+	}
+
+  /* Default sorting column */
+  gtk_arrow_set(
+	   GTK_ARROW(((CompletionState*)
+				  (filesel->cmpl_state))->cc->current_sorting_column->arrow),
+	   (((CompletionState*)
+		(filesel->cmpl_state))->cc->current_sorting_column->sort_ascending)?
+	   GTK_ARROW_DOWN:GTK_ARROW_UP, GTK_SHADOW_IN);
+  
+  g_free(file_titles);
+}
+
+static void
+handle_ok_button (GtkWidget* ok_button, GtkFileSelection* filesel)
+{
+  CompletionState* cmpl_state = (CompletionState*)filesel->cmpl_state;
+  char filename_buf[MAXPATHLEN*2];
+  char* filename = gtk_entry_get_text(filesel->selection_entry);
+  
+  strcpy(filename_buf,cmpl_state->completion_dir->fullname);
+  strcat(filename_buf,"/");
+  strcat(filename_buf,filename);
+
+  mru_prepend(cmpl_state->mru_list, filename_buf);
+  mru_save(cmpl_state->mru_list);
+}
+
+static void
 gtk_file_selection_init (GtkFileSelection *filesel)
 {
   GtkWidget *entry_vbox;
   GtkWidget *label;
-  GtkWidget *list_hbox;
+  GtkWidget *list_pane;
   GtkWidget *confirm_area;
   GtkWidget *pulldown_hbox;
   GtkWidget *scrolled_win;
+  GtkWidget *config_button;
+  
+  GtkTooltips *tooltips;
+  
+  /* Owner --- Don't ask */
 
   char *dir_title [2];
-  char *file_title [2];
-  
+
+#ifdef USE_GNOME
+  gdk_rgb_init();
+#endif
+
   filesel->cmpl_state = cmpl_init_state ();
 
+  /* The tooltip group */
+  tooltips = ((CompletionState*)(filesel->cmpl_state))->tooltips =
+	gtk_tooltips_new();
+  gtk_tooltips_enable(tooltips);
+
   /* The dialog-sized vertical box  */
   filesel->main_vbox = gtk_vbox_new (FALSE, 10);
   gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
@@ -398,21 +678,11 @@
   
   gtk_file_selection_show_fileop_buttons(filesel);
 
-  /* hbox for pulldown menu */
-  pulldown_hbox = gtk_hbox_new (TRUE, 5);
-  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
-  gtk_widget_show (pulldown_hbox);
+  /*  The horizontal pane containing the directory and file listboxes  */
   
-  /* Pulldown menu */
-  filesel->history_pulldown = gtk_option_menu_new ();
-  gtk_widget_show (filesel->history_pulldown);
-  gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown, 
-		      FALSE, FALSE, 0);
-    
-  /*  The horizontal box containing the directory and file listboxes  */
-  list_hbox = gtk_hbox_new (FALSE, 5);
-  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
-  gtk_widget_show (list_hbox);
+  list_pane = GTK_WIDGET(gtk_hpaned_new());
+  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_pane, TRUE, TRUE, 0);
+  gtk_widget_show (list_pane);
 
   /* The directories clist */
   dir_title[0] = _("Directories");
@@ -422,33 +692,49 @@
   gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
 		      (GtkSignalFunc) gtk_file_selection_dir_button, 
 		      (gpointer) filesel);
-  gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
+  gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "click-column",
+					  (GtkSignalFunc) get_dir_selection_click_column,
+					  (gpointer) filesel);
+
+  /* Do the title widget thing */
+  {
+	GtkWidget* label=gtk_label_new("Directories");
+	GtkWidget* arrow=gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_IN);
+	GtkWidget* hbox =gtk_hbox_new(FALSE, 0);
+	GtkWidget* evbox = gtk_event_box_new();
+	
+	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
+	gtk_widget_show(label); gtk_widget_show(arrow);
+	gtk_misc_set_padding(GTK_MISC(arrow), 0, 0);
+	gtk_container_add(GTK_CONTAINER(evbox),hbox);
+	gtk_widget_show(hbox);
+	gtk_clist_set_column_widget(GTK_CLIST(filesel->dir_list),
+								0, evbox);
+	((CompletionState*)filesel->cmpl_state)->dir_arrow=arrow;
+
+	gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), evbox,
+						 "Directories in the current directory","");
+  }
 
   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
 				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
   gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5);
-  gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0);
+  gtk_paned_pack1(GTK_PANED(list_pane), scrolled_win, TRUE, TRUE);
   gtk_widget_show (filesel->dir_list);
   gtk_widget_show (scrolled_win);
 
-  /* The files clist */
-  file_title[0] = _("Files");
-  file_title[1] = NULL;
-  filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title);
-  gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
-  gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
-		      (GtkSignalFunc) gtk_file_selection_file_button, 
-		      (gpointer) filesel);
-  gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
+  gtk_file_selection_filelist_init(filesel);
 
-  scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+  ((CompletionState*)filesel->cmpl_state)->parent_scrolled_win =
+	scrolled_win = gtk_scrolled_window_new (NULL, NULL);
   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
 				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
   gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5);
-  gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0);
+  gtk_paned_pack2(GTK_PANED(list_pane), scrolled_win, TRUE, TRUE);
   gtk_widget_show (filesel->file_list);
   gtk_widget_show (scrolled_win);
 
@@ -472,23 +758,60 @@
   gtk_widget_grab_default (filesel->ok_button);
   gtk_widget_show (filesel->ok_button);
 
+  gtk_signal_connect(GTK_OBJECT(filesel->ok_button), "clicked",
+					 (GtkSignalFunc)handle_ok_button,
+					 filesel);
+
   /*  The Cancel button  */
   filesel->cancel_button = gtk_button_new_with_label (_("Cancel"));
   GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
   gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
   gtk_widget_show (filesel->cancel_button);
 
+  /*  The Configure button */
+  config_button = gtk_button_new_with_label(_("Configure"));
+  gtk_box_pack_start(GTK_BOX(confirm_area), config_button, TRUE, TRUE, 0);
+  GTK_WIDGET_SET_FLAGS (config_button, GTK_CAN_DEFAULT);
+  gtk_widget_show(config_button);
+  gtk_signal_connect(GTK_OBJECT(config_button), "clicked",
+					 (GtkSignalFunc)columnconfig_configure_thyself,
+					 filesel);
+
   /*  The selection entry widget  */
   entry_vbox = gtk_vbox_new (FALSE, 2);
   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
   gtk_widget_show (entry_vbox);
 
+  /*
   filesel->selection_text = label = gtk_label_new ("");
   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
   gtk_widget_show (label);
+  */
+
+  /* hbox for pulldown menu */  
+  pulldown_hbox = gtk_hbox_new (FALSE, 5);
+  gtk_box_pack_start (GTK_BOX (entry_vbox), pulldown_hbox, FALSE, FALSE, 0);
+  gtk_widget_show (pulldown_hbox);
+
+  label = gtk_label_new("Current Directory: ");
+  gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5);
+  gtk_box_pack_start (GTK_BOX(pulldown_hbox), label, FALSE, FALSE, 0);
+  gtk_widget_show (label);
+
+  /* Pulldown menu */
+  filesel->history_pulldown = gtk_option_menu_new ();
+  gtk_widget_show (filesel->history_pulldown);
+  gtk_box_pack_end (GTK_BOX (pulldown_hbox), filesel->history_pulldown, 
+		      TRUE, TRUE, 0);
+
+  ((CompletionState*)filesel->cmpl_state)->mru_box = gtk_combo_new();  
 
-  filesel->selection_entry = gtk_entry_new ();
+  filesel->selection_entry =
+   GTK_COMBO(((CompletionState*)filesel->cmpl_state)->mru_box)->entry;
+
+  gtk_signal_handlers_destroy(filesel->selection_entry);
+  
   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
 		      (GtkSignalFunc) gtk_file_selection_key_press, filesel);
   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
@@ -497,18 +820,20 @@
   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
                              (GtkSignalFunc) gtk_button_clicked,
                              GTK_OBJECT (filesel->ok_button));
-  gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
-  gtk_widget_show (filesel->selection_entry);
-
-  if (!cmpl_state_okay (filesel->cmpl_state))
-    {
-      gchar err_buf[256];
 
-      sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
+  gtk_box_pack_start(GTK_BOX(entry_vbox),
+					 ((CompletionState*)filesel->cmpl_state)->mru_box,
+					 TRUE, TRUE, 0);
+  mru_populate(((CompletionState*)filesel->cmpl_state)->mru_list,
+			   GTK_COMBO(((CompletionState*)filesel->cmpl_state)->mru_box));
+
+  gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips),
+					   ((CompletionState*)filesel->cmpl_state)->mru_box,
+					   "A list of recently-opened files","");  
+  
+  gtk_widget_show(((CompletionState*)filesel->cmpl_state)->mru_box);
 
-      gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
-    }
-  else
+  if (cmpl_state_okay (filesel->cmpl_state))
     {
       gtk_file_selection_populate (filesel, "", FALSE);
     }
@@ -520,7 +845,7 @@
 gtk_file_selection_new (const gchar *title)
 {
   GtkFileSelection *filesel;
-
+  
   filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION);
   gtk_window_set_title (GTK_WINDOW (filesel), title);
 
@@ -595,7 +920,25 @@
     }
 }
 
+static void get_dir_selection_click_column(
+	GtkCList* clist, gint column, gpointer user_data)
+{
+  GtkFileSelection* fs=(GtkFileSelection*)user_data;
+  CompletionState* cs=((CompletionState*)(fs->cmpl_state));
+
+  if(cs->dir_sort_ascending)
+	gtk_clist_set_sort_type(GTK_CLIST(fs->dir_list),GTK_SORT_DESCENDING);
+  else
+	gtk_clist_set_sort_type(GTK_CLIST(fs->dir_list),GTK_SORT_ASCENDING);
+
+  cs->dir_sort_ascending=!cs->dir_sort_ascending;
+
+  gtk_arrow_set(GTK_ARROW(cs->dir_arrow),
+				(cs->dir_sort_ascending)?GTK_ARROW_DOWN:GTK_ARROW_UP,
+				GTK_SHADOW_IN);
 
+  gtk_clist_sort(GTK_CLIST(fs->dir_list));
+}
 
 void
 gtk_file_selection_set_filename (GtkFileSelection *filesel,
@@ -882,7 +1225,8 @@
   path = cmpl_reference_position (cmpl_state);
   
   full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
-  if ( (unlink (full_path) < 0) ) 
+  if ( (remove (full_path) < 0) ) /* Use remove so it can handle
+									 either files or directories */
     {
       buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\":  ", 
 			 g_strerror(errno), NULL);
@@ -1221,16 +1565,23 @@
 			       gpointer user_data)
 {
   GtkFileSelection *fs = NULL;
-  gchar *filename, *temp = NULL;
+  CompletionState* cmpl_state = NULL;
+  gchar *filename;
   
   g_return_if_fail (GTK_IS_CLIST (widget));
 
   fs = user_data;
   g_return_if_fail (fs != NULL);
   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
+
+  cmpl_state = (CompletionState*) fs->cmpl_state;
+
+  /* Get row from our list */
+
+  filename = ((Row*)g_list_nth_data(cmpl_state->row_list,row))->filename;
   
-  gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
-  filename = g_strdup (temp);
+  /*gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);*/  
+  /*filename = g_strdup (temp);*/
 
   if (filename)
     {
@@ -1248,7 +1599,7 @@
       else
 	gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
 
-      g_free (filename);
+      /*g_free (filename);*/
     }
 }
 
@@ -1291,6 +1642,62 @@
     }
 }
 
+#ifdef USE_GNOME
+
+void get_pixmap_from_mime_type(const char* mime_type,
+									GdkPixmap** pixmap_ret, GdkBitmap** mask_ret,
+									GHashTable* cache)
+{
+  const char *image_name;
+  GdkPixbuf *pixbuf, *pixbuf2;
+  PixmapCache *ppc;
+
+  /* Check cache */
+  ppc = g_hash_table_lookup(cache, mime_type);
+  if(!ppc)
+	{
+	  ppc = g_new0(PixmapCache, 1);
+	  image_name=gnome_mime_get_value(mime_type,"icon-filename");
+
+	  if(image_name)
+		pixbuf = gdk_pixbuf_new_from_file(image_name);
+	  else
+		pixbuf = NULL;
+	  if(pixbuf)
+		{
+		  /*
+		  float ratio=((float)gdk_pixbuf_get_height(pixbuf))/
+			((float)desired_height);
+		  int new_width=((float)gdk_pixbuf_get_width(pixbuf))/ 
+			ratio;
+		  */
+
+		  int new_width = gdk_pixbuf_get_width(pixbuf)/2.5;
+		  int desired_height = gdk_pixbuf_get_height(pixbuf)/2.5;
+		  
+		  pixbuf2=gdk_pixbuf_scale_simple(pixbuf,
+										  new_width,desired_height,
+										  GDK_INTERP_TILES);
+		  
+		  gdk_pixbuf_unref(pixbuf);
+		  pixbuf=pixbuf2;
+
+		  gdk_pixbuf_render_pixmap_and_mask(pixbuf, &ppc->map, &ppc->mask, 128);
+		}
+	  g_hash_table_insert(cache, g_strdup(mime_type), ppc);
+	}
+
+  if(ppc->map)
+	gdk_pixmap_ref(ppc->map);  
+  *pixmap_ret=ppc->map;
+  
+  if(ppc->mask)
+	gdk_bitmap_ref(ppc->mask);
+  *mask_ret=ppc->mask;
+}
+
+#endif
+
 static void
 gtk_file_selection_populate (GtkFileSelection *fs,
 			     gchar            *rel_path,
@@ -1301,13 +1708,11 @@
   gchar* filename;
   gint row;
   gchar* rem_path = rel_path;
-  gchar* sel_text;
-  gchar* text[2];
   gint did_recurse = FALSE;
   gint possible_count = 0;
   gint selection_index = -1;
-  gint file_list_width;
   gint dir_list_width;
+  char* text[3];
   
   g_return_if_fail (fs != NULL);
   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
@@ -1326,12 +1731,14 @@
 
   gtk_clist_freeze (GTK_CLIST (fs->dir_list));
   gtk_clist_clear (GTK_CLIST (fs->dir_list));
-  gtk_clist_freeze (GTK_CLIST (fs->file_list));
-  gtk_clist_clear (GTK_CLIST (fs->file_list));
+
+  rowlist_destroy(cmpl_state->row_list);
+  cmpl_state->row_list=NULL;
 
   /* Set the dir_list to include ./ and ../ */
   text[1] = NULL;
   text[0] = "./";
+
   row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
 
   text[0] = "../";
@@ -1340,8 +1747,6 @@
   /*reset the max widths of the lists*/
   dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
   gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
-  file_list_width = 1;
-  gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
 
   while (poss)
     {
@@ -1352,12 +1757,13 @@
           filename = cmpl_this_completion (poss);
 
 	  text[0] = filename;
+	  text[1] = NULL;
 	  
           if (cmpl_is_directory (poss))
             {
               if (strcmp (filename, "./") != 0 &&
                   strcmp (filename, "../") != 0)
-		{
+		{		  
 		  int width = gdk_string_width(fs->dir_list->style->font,
 					       filename);
 		  row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
@@ -1370,24 +1776,29 @@
  		}
 	    }
           else
-	    {
-	      int width = gdk_string_width(fs->file_list->style->font,
-				           filename);
-	      row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
-	      if(width > file_list_width)
-	        {
-	          file_list_width = width;
-	          gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
-					     width);
-	        }
-            }
+	    {		  
+		  char filename_buf[MAXPATHLEN*2];
+		  strcpy(filename_buf,cmpl_state->completion_dir->fullname);
+		  strcat(filename_buf,"/");
+		  strcat(filename_buf,filename);
+
+		  /* Actually create the row */
+		  cmpl_state->row_list = g_list_prepend(cmpl_state->row_list,
+												row_create(filename,
+														   filename_buf,
+														   cmpl_state->cc));
+		}
 	}
-
+	  
       poss = cmpl_next_completion (cmpl_state);
     }
 
+  cmpl_state->row_list = g_list_sort(cmpl_state->row_list,
+									 (GCompareFunc)magic_row_sort_function);
+
+  row_populate_clist(cmpl_state->row_list, GTK_CLIST(fs->file_list));
+
   gtk_clist_thaw (GTK_CLIST (fs->dir_list));
-  gtk_clist_thaw (GTK_CLIST (fs->file_list));
 
   /* File lists are set. */
 
@@ -1442,12 +1853,14 @@
 
       if (fs->selection_entry)
 	{
+	  /*
 	  sel_text = g_strconcat (_("Selection: "),
 				  cmpl_reference_position (cmpl_state),
 				  NULL);
 
 	  gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text);
 	  g_free (sel_text);
+	  */
 	}
 
       if (fs->history_pulldown) 
@@ -1461,14 +1874,16 @@
 static void
 gtk_file_selection_abort (GtkFileSelection *fs)
 {
+  /*
   gchar err_buf[256];
 
   sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
+  */
 
   /*  BEEP gdk_beep();  */
 
-  if (fs->selection_entry)
-    gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
+  /*if (fs->selection_entry)
+    gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);*/
 }
 
 /**********************************************************************/
@@ -1570,102 +1985,1708 @@
 }
 
 /**********************************************************************/
-/*	                 Construction, deletion                       */
+/*                   Built-In Columns                             */
 /**********************************************************************/
 
-static CompletionState*
-cmpl_init_state (void)
-{
-  gchar getcwd_buf[2*MAXPATHLEN];
-  CompletionState *new_state;
+/* ******* Generic ******* */
+/* These are here so that I can add things later without needing to
+   change 10e6 column-specific constructors. All column-specific
+   analogues should use these. */
 
-  new_state = g_new (CompletionState, 1);
+static Column*
+generic_col_constructor(Column* newcol)
+{
+  newcol->sort_ascending=1; /* Perfect example of the above */
+  return newcol;
+}
 
-  /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
-   * and, if that wasn't bad enough, hangs in doing so.
-   */
-#if defined(sun) && !defined(__SVR4)
-  if (!getwd (getcwd_buf))
-#else    
-  if (!getcwd (getcwd_buf, MAXPATHLEN))
-#endif    
-    {
-      /* Oh joy, we can't get the current directory. Um..., we should have
-       * a root directory, right? Right? (Probably not portable to non-Unix)
-       */
-      strcpy (getcwd_buf, "/");
-    }
+static void
+generic_col_destructor(Column* cl)
+{
+  g_free(cl);
+}
 
-tryagain:
+/* All members are assumed to have been dynamically allocated */
+static void
+generic_filecell_destructor(Column* ignored, FileCell* fc)
+{
+  if(fc->text)
+	g_free(fc->text);
+  if(fc->pixmap)
+	gdk_pixmap_unref(fc->pixmap);
+  if(fc->mask)
+	gdk_bitmap_unref(fc->mask);
+}
 
-  new_state->reference_dir = NULL;
-  new_state->completion_dir = NULL;
-  new_state->active_completion_dir = NULL;
-  new_state->directory_storage = NULL;
-  new_state->directory_sent_storage = NULL;
-  new_state->last_valid_char = 0;
-  new_state->updated_text = g_new (gchar, MAXPATHLEN);
-  new_state->updated_text_alloc = MAXPATHLEN;
-  new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
-  new_state->the_completion.text_alloc = MAXPATHLEN;
-  new_state->user_dir_name_buffer = NULL;
-  new_state->user_directories = NULL;
+static FileCell*
+generic_filecell_constructor(FileCell* newfilecell)
+{
+  return newfilecell;
+}
 
-  new_state->reference_dir =  open_dir (getcwd_buf, new_state);
+/* Generic Sorting Function, simply sorts by name */
+static int
+col_generic_sort(Column* currcol, const FileCell* fs1, const FileCell* fs2)
+{
+  if(fs1 && fs2 && fs1->text && fs2->text)	
+	return (currcol->sort_ascending)?\
+	  strcmp(fs1->text,fs2->text):-strcmp(fs1->text,fs2->text);
+  else
+	return 0; /* Can't compare invalid things */
+}
 
-  if (!new_state->reference_dir)
-    {
-      /* Directories changing from underneath us, grumble */
-      strcpy (getcwd_buf, "/");
-      goto tryagain;
-    }
+/* ******* Filecol ******* */
 
-  return new_state;
+/* Process func */
+static FileCell*
+filecol_process(Column* col, const ProcessFileData* pfd)
+{
+  FileCell* newfc = generic_filecell_constructor(g_new0(FileCell,1));
+  if(pfd->filename)
+	newfc->text=g_strdup(pfd->filename);
+  else
+	newfc->text=g_strdup("N/A");
+  return newfc;
 }
 
-static void
-cmpl_free_dir_list(GList* dp0)
-{
-  GList *dp = dp0;
+static Column*
+filecol_constructor(void);
 
-  while (dp) {
-    free_dir (dp->data);
-    dp = dp->next;
-  }
+/* The column type */
+ColumnType filecol_column_type = {"Filename", "Name of the file",
+								  filecol_constructor};
 
-  g_list_free(dp0);
+/* Constructor */
+static Column*
+filecol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &filecol_column_type;
+  newcol->sorter = col_generic_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = filecol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
 }
 
-static void
-cmpl_free_dir_sent_list(GList* dp0)
+/* ******* sizecol ******* */
+
+/* Sizecol needs extra information in the cell,
+   so we define our own struct */
+typedef struct _Sizecol_FileCell
 {
-  GList *dp = dp0;
+  FileCell base;
+  unsigned int size;
+} Sizecol_FileCell;
 
-  while (dp) {
-    free_dir_sent (dp->data);
-    dp = dp->next;
-  }
+/* Process func */
+static FileCell*
+sizecol_process(Column* col, const ProcessFileData* pfd)
+{
+  char buffer[128];
+  struct stat* sb = pfd->statbuf;
+  Sizecol_FileCell* newfc =
+	(Sizecol_FileCell*)generic_filecell_constructor(
+								(FileCell*)g_new0(Sizecol_FileCell,1));
 
-  g_list_free(dp0);
+  if(!sb)
+	{
+	  newfc->size=0;
+	  newfc->base.text="N/A";
+	  return (FileCell*)newfc;
+	}
+
+  if(sb->st_size < 1024)
+	sprintf(buffer, "%luB", sb->st_size);
+  else if(sb->st_size < 1024*1024)
+	sprintf(buffer, "%lukB", sb->st_size/1024);
+  else if(sb->st_size < 1024*1024*1024)
+	sprintf(buffer, "%.3gmB", (double)sb->st_size/(double)(1024*1024));
+  else 
+	sprintf(buffer, "%.3ggB", (double)sb->st_size/(double)(1024*1024*1024));
+
+  newfc->base.text=g_strdup(buffer);
+  newfc->size=sb->st_size;
+  return (FileCell*)newfc;
 }
 
-static void
-cmpl_free_state (CompletionState* cmpl_state)
+static int
+sizecol_sort(Column* currcol, const Sizecol_FileCell* fs1, const Sizecol_FileCell* fs2)
 {
-  cmpl_free_dir_list (cmpl_state->directory_storage);
-  cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
+  if(!(fs1 && fs2))
+	return 0;
+  return (currcol->sort_ascending)?
+	((fs1->size < fs2->size)?
+	 -1:( (fs1->size > fs2->size)?1:0)):	
+	((fs1->size > fs2->size)?
+	 -1:( (fs1->size < fs2->size)?1:0));	
+}
 
-  if (cmpl_state->user_dir_name_buffer)
-    g_free (cmpl_state->user_dir_name_buffer);
-  if (cmpl_state->user_directories)
-    g_free (cmpl_state->user_directories);
-  if (cmpl_state->the_completion.text)
-    g_free (cmpl_state->the_completion.text);
-  if (cmpl_state->updated_text)
-    g_free (cmpl_state->updated_text);
+static Column*
+sizecol_constructor(void);
 
-  g_free (cmpl_state);
-}
+/* The column type */
+ColumnType sizecol_column_type = {"Size", "Size of the file, in scaled units",
+								  sizecol_constructor};
+
+/* Constructor */
+static Column*
+sizecol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &sizecol_column_type;
+  newcol->sorter = (column_sort_func)sizecol_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = sizecol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
+}
+
+/* ******* ownercol ******* */
+
+/* Process func */
+static FileCell*
+ownercol_process(Column* col, const ProcessFileData* pfd)
+{
+  struct passwd* pwbuf;
+
+  FileCell* newfc = generic_filecell_constructor(g_new0(FileCell,1));
+  
+  if(pfd->statbuf)
+	{
+	  pwbuf=getpwuid(pfd->statbuf->st_uid);
+	  newfc->text=g_strdup((pwbuf->pw_name)?(pwbuf->pw_name):"N/A");
+	}
+  else
+	newfc->text=g_strdup("N/A");
+  return newfc;
+}
+
+static Column*
+ownercol_constructor(void);
+
+/* The column type */
+ColumnType ownercol_column_type = {"Owner", "The user who owns the file",
+								  ownercol_constructor};
+
+/* Constructor */
+static Column*
+ownercol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &ownercol_column_type;
+  newcol->sorter = col_generic_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = ownercol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
+}
+
+/* ******* atimecol, mtimecol, and ctimecol ******* */
+
+typedef struct _Timecol_FileCell
+{
+  FileCell base;
+  time_t time;
+} Timecol_FileCell;
+
+/* Process func */
+static FileCell*
+atimecol_process(Column* col, const ProcessFileData* pfd)
+{  
+  struct stat* sb = pfd->statbuf;
+  Timecol_FileCell* newfc =
+	(Timecol_FileCell*)generic_filecell_constructor(
+			  (FileCell*)g_new0(Timecol_FileCell,1));
+  if(!sb)
+	{
+	  newfc->time=0;
+	  newfc->base.text="N/A";
+	  return (FileCell*)newfc;
+	} 
+  newfc->base.text=g_strdup(ctime(&sb->st_atime));
+  newfc->time=sb->st_atime;
+  return (FileCell*)newfc;
+}
+
+static FileCell*
+mtimecol_process(Column* col, const ProcessFileData* pfd)
+{  
+  struct stat* sb = pfd->statbuf;
+  Timecol_FileCell* newfc =
+	(Timecol_FileCell*)generic_filecell_constructor(
+			  (FileCell*)g_new0(Timecol_FileCell,1));
+  if(!sb)
+	{
+	  newfc->time=0;
+	  newfc->base.text="N/A";
+	  return (FileCell*)newfc;
+	} 
+  newfc->base.text=g_strdup(ctime(&sb->st_mtime));
+  newfc->time=sb->st_mtime;
+  return (FileCell*)newfc;
+}
+
+static FileCell*
+ctimecol_process(Column* col, const ProcessFileData* pfd)
+{  
+  struct stat* sb = pfd->statbuf;
+  Timecol_FileCell* newfc =
+	(Timecol_FileCell*)generic_filecell_constructor((FileCell*)
+					  g_new0(Timecol_FileCell,1));
+  if(!sb)
+	{
+	  newfc->time=0;
+	  newfc->base.text="N/A";
+	  return (FileCell*)newfc;
+	} 
+  newfc->base.text=g_strdup(ctime(&sb->st_ctime));
+  newfc->time=sb->st_mtime;
+  return (FileCell*)newfc;
+}
+
+static int
+timecol_sort(Column* currcol, const Timecol_FileCell* fs1, const Timecol_FileCell* fs2)
+{
+  if(!(fs1 && fs2))
+	return 0;
+  return (currcol->sort_ascending)?
+	((fs1->time < fs2->time)?
+	 -1:( (fs1->time > fs2->time)?1:0)):	
+	((fs1->time > fs2->time)?
+	 -1:( (fs1->time < fs2->time)?1:0));	
+}
+
+static Column*
+atimecol_constructor(void);
+static Column*
+mtimecol_constructor(void);
+static Column*
+ctimecol_constructor(void);
+
+/* The column type */
+ColumnType atimecol_column_type = {"Accessed", "Time the file was last accessed",
+								  atimecol_constructor};
+
+ColumnType mtimecol_column_type = {"Modified", "Time the file was last changed",
+								  mtimecol_constructor};
+
+ColumnType ctimecol_column_type = {"Changed",
+	   "Time the file's inode information was last changed (e.g., owner, group, link count, mode, etc.)",
+								   ctimecol_constructor};
+/* Constructor */
+static Column*
+atimecol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &atimecol_column_type;
+  newcol->sorter = (column_sort_func)timecol_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = atimecol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
+}
+static Column*
+mtimecol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &mtimecol_column_type;
+  newcol->sorter = (column_sort_func)timecol_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = mtimecol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
+}
+static Column*
+ctimecol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &ctimecol_column_type;
+  newcol->sorter = (column_sort_func)timecol_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = ctimecol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
+}
+
+/* ******* permcol ******* */
+
+/* Process func */
+static FileCell*
+permcol_process(Column* col, const ProcessFileData* pfd)
+{
+  FileCell* newfc = generic_filecell_constructor(g_new0(FileCell,1));
+  struct stat* sb = pfd->statbuf;  
+  char buffer[10];
+  mode_t perms;
+
+  if(!sb)
+	{
+	  newfc->text=g_strdup("N/A");
+	  return newfc;
+	}
+  perms = sb->st_mode;
+
+  /* Clear buffer */
+  memset(buffer, '-', 9);
+  buffer[9]=0;
+
+  /* 0123456789(10) */
+
+  if(perms & S_IRUSR)
+	buffer[0] = 'r';
+  if(perms & S_IWUSR)
+	buffer[1] = 'w';
+  if(perms & S_IXUSR)
+	buffer[2] = 'x';
+  if(perms & S_ISUID)
+	buffer[2] = 's';
+
+  if(perms & S_IRGRP)
+	buffer[3] = 'r';
+  if(perms & S_IWGRP)
+	buffer[4] = 'w';
+  if(perms & S_IXGRP)
+	buffer[5] = 'x';
+  if(perms & S_ISGID)
+	buffer[5] = 's';
+
+  if(perms & S_IROTH)
+	buffer[6] = 'r';
+  if(perms & S_IWOTH)
+	buffer[7] = 'w';
+  if(perms & S_IXOTH)
+	buffer[8] = 'x';
+  
+  newfc->text=g_strdup(buffer);
+  return newfc;
+}
+
+static Column*
+permcol_constructor(void);
+
+/* The column type */
+ColumnType permcol_column_type = {"Permissions", "ls-like permissions for the file",
+								  permcol_constructor};
+
+/* Constructor */
+static Column*
+permcol_constructor(void)
+{
+  Column* newcol = generic_col_constructor(g_new0(Column,1));
+  newcol->col_type = &permcol_column_type;
+  newcol->sorter = col_generic_sort;
+  newcol->destructor = generic_col_destructor;
+  newcol->filecell_constructor = permcol_process;
+  newcol->filecell_destructor = generic_filecell_destructor;
+  return newcol;
+}
+
+#ifdef USE_GNOME
+
+/* ******* mimecol ******* */
+
+typedef struct _MimeColumn
+{
+  Column base;
+  GHashTable* pixmap_cache;
+  unsigned int* cache_refcount;
+} MimeColumn;
+
+static gboolean
+remove_from_cache(gchar* key, PixmapCache* value, gpointer user_data)
+{
+  if(value->map)
+	gdk_pixmap_unref(value->map);
+  if(value->mask)
+	gdk_bitmap_unref(value->mask);
+
+  g_free(value);
+  g_free(key);
+
+  return TRUE;
+}
+
+/* Process func */
+static FileCell*
+mimecol_process(MimeColumn* col, const ProcessFileData* pfd)
+{
+  const char* mime_type;
+  GdkPixmap* pixmap;
+  GdkBitmap* mask;
+  
+  FileCell* newfc = generic_filecell_constructor(g_new0(FileCell,1));
+  
+  mime_type = gnome_mime_type_of_file(pfd->fullpath);
+  if(!mime_type || !*mime_type)
+	{
+	  newfc->text=g_strdup("N/A");
+	  return newfc;
+	}
+
+  get_pixmap_from_mime_type(mime_type, &pixmap, &mask, col->pixmap_cache);
+
+  newfc->pixmap = pixmap;
+  newfc->mask = mask;
+  newfc->text = g_strdup(mime_type);
+  
+  return newfc;
+}
+
+static void
+mimecol_destructor(MimeColumn* mc)
+{
+  /* Delete hash if needed*/
+  --(*mc->cache_refcount);
+
+  if(*mc->cache_refcount == 0)
+	{
+	  g_hash_table_foreach_remove(mc->pixmap_cache,
+								  (GHRFunc)remove_from_cache,
+								  0);
+	  g_hash_table_destroy(mc->pixmap_cache);
+	}
+
+  generic_col_destructor((Column*)mc);
+}
+
+static Column*
+mimecol_constructor(void);
+
+/* The column type */
+ColumnType mimecol_column_type = {"Type", "The mime type of each file",
+								  mimecol_constructor};
+
+/* Constructor */
+static Column*
+mimecol_constructor(void)
+{
+  static unsigned int cache_refcount=0;
+  static GHashTable* pixmap_cache;
+  
+  MimeColumn* newcol = (MimeColumn*)
+	generic_col_constructor((Column*)g_new0(MimeColumn,1));
+  newcol->base.col_type = &mimecol_column_type;
+  newcol->base.sorter = col_generic_sort;
+  newcol->base.destructor = (delete_column_func) mimecol_destructor;
+  newcol->base.filecell_constructor = (process_file_func) mimecol_process;
+  newcol->base.filecell_destructor = generic_filecell_destructor;
+  newcol->cache_refcount=&cache_refcount;
+
+  if(cache_refcount > 0)
+	newcol->pixmap_cache=pixmap_cache;
+  else
+	pixmap_cache=newcol->pixmap_cache=g_hash_table_new(g_str_hash,g_str_equal);
+  
+  ++cache_refcount;
+  
+  return (Column*)newcol;
+}
+
+#endif
+
+/**********************************************************************/
+/*                   Misc Functions                               */
+/**********************************************************************/
+
+/* Handle popup menu */
+static gboolean
+handle_popup_menu(GtkWidget* widget,
+				  GdkEventButton* event,
+				  GtkFileSelection* fs)
+{
+  int row, col; GtkMenu* menu;
+  GtkMenuItem* menu_item;
+
+  if(!gtk_clist_get_selection_info(GTK_CLIST(fs->file_list),
+											 event->x,
+											 event->y,
+											 &row, &col))	
+	return FALSE;
+
+  if(!fs->selection_entry)
+	return FALSE;
+
+  if(event->button != 3)
+	return FALSE;
+  
+  gtk_clist_select_row(GTK_CLIST(fs->file_list),
+					   row, col);
+
+  menu = (GtkMenu*)gtk_menu_new();  
+  menu_item = (GtkMenuItem*)gtk_menu_item_new_with_label("Open");
+  gtk_signal_connect_object(GTK_OBJECT(menu_item), "activate",
+							(GtkSignalFunc)gtk_button_clicked,
+							GTK_OBJECT(fs->ok_button));
+  gtk_menu_append(menu, GTK_WIDGET(menu_item));
+  gtk_widget_show(GTK_WIDGET(menu_item));
+  
+  menu_item = (GtkMenuItem*)gtk_menu_item_new_with_label("Rename");
+  gtk_signal_connect_object(GTK_OBJECT(menu_item), "activate",
+							(GtkSignalFunc)gtk_button_clicked,
+							GTK_OBJECT(fs->fileop_ren_file));
+  gtk_menu_append(menu, GTK_WIDGET(menu_item));
+  gtk_widget_show(GTK_WIDGET(menu_item));
+  
+  menu_item = (GtkMenuItem*)gtk_menu_item_new_with_label("Delete");
+  gtk_signal_connect_object(GTK_OBJECT(menu_item), "activate",
+							(GtkSignalFunc)gtk_button_clicked,
+							GTK_OBJECT(fs->fileop_del_file));
+  gtk_menu_append(menu, GTK_WIDGET(menu_item));
+  gtk_widget_show(GTK_WIDGET(menu_item));
+
+  gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);
+
+  gtk_widget_show(GTK_WIDGET(menu));
+
+  return TRUE;
+}
+
+/* Returns a STATICALLY ALLOCATED list of available column types, terminated by a NULL */
+static const ColumnType**
+get_available_column_types(void)
+{
+  /* Num of cols right now: 2 */
+  static ColumnType *avail_cols[20]; /* Make this big enough for a lot of columns */
+  static int initialized_yet=0;
+
+  if(!initialized_yet)
+	{
+	  memset(avail_cols, 0, sizeof(ColumnType*)*20);
+	  /* XXX THIS PART MUST BE FIXED WHEN ADDING NEW COLUMNS XXZ*/
+	  avail_cols[0] = &filecol_column_type;
+	  avail_cols[1] = &sizecol_column_type;
+	  avail_cols[3] = &ownercol_column_type;
+	  avail_cols[2] = &atimecol_column_type;
+	  avail_cols[4] = &mtimecol_column_type;
+	  avail_cols[5] = &ctimecol_column_type;
+	  avail_cols[6] = &permcol_column_type;
+#ifdef USE_GNOME
+	  avail_cols[7] = &mimecol_column_type;
+#endif
+	}
+  return (const ColumnType**)avail_cols;
+}
+
+#ifdef USE_GNOME
+static void
+columnconfig_save_config(ColumnConfiguration* cc)
+{
+  char* tmp = columnconfig_get_config(cc);
+  gnome_config_set_string("/gtkfilesel/columns/columnconfig",tmp);
+  gnome_config_sync();
+  free(tmp);
+}
+
+static ColumnConfiguration*
+the_column_configuration(void)
+{
+  char* configval =
+	gnome_config_get_string("/gtkfilesel/columns/columnconfig="\
+		 "0 Filename Type Size Owner Accessed Modified Changed Permissions");
+  ColumnConfiguration* result = create_column_configuration(configval);
+  g_free(configval);  
+  return result;  
+}
+
+static void
+mru_save(char** list)
+{
+  int i;
+  char real_key[300] = "";
+  char* app_name = g_get_prgname();
+
+  for(i=0; i<NUM_MRU_ENTRIES;++i)
+	{
+	  fprintf(stderr,"mru #%d: %s\n", i, list[i]);
+	  snprintf(real_key, 300, "/gtkfilesel/mru_%s/mru_entry_%d=", app_name, i);
+	  gnome_config_set_string(real_key, list[i]?list[i]:"");
+	}
+
+  gnome_config_sync();
+}
+
+static void
+mru_load(char** list)
+{
+  int i;
+  char real_key[300] = "";
+  char* app_name = g_get_prgname();
+
+  for(i=0;i<NUM_MRU_ENTRIES;++i)
+	{
+	  snprintf(real_key, 300, "/gtkfilesel/mru_%s/mru_entry_%d=", app_name, i);
+	  list[i] = gnome_config_get_string(real_key);
+	}
+}
+
+#else
+
+static void
+columnconfig_save_config(ColumnConfiguration* cc)
+{
+  char* tmp = columnconfig_get_config(cc);
+  char* homedir = g_get_home_dir();
+  char buffer[MAXPATHLEN*2];
+  FILE* config_file;
+
+  sprintf(buffer, "%s/%s", homedir, NON_GNOME_CONFIG_FILE);
+  config_file = fopen(buffer, "w");
+
+  if(config_file)
+	{
+	  fputs(tmp, config_file);
+	  fclose(config_file);
+	}
+  
+  free(tmp);
+}
+
+static ColumnConfiguration*
+the_column_configuration(void)
+{
+  char buffer[MAXPATHLEN*2];
+  char buffer2[3000];
+  char* homedir = g_get_home_dir();
+  ColumnConfiguration* result;
+  FILE* config_file;
+  
+  sprintf(buffer, "%s/%s", homedir, NON_GNOME_CONFIG_FILE);
+  config_file = fopen(buffer, "r");
+
+  if(!config_file)
+	return create_column_configuration(
+		"0 Filename Type Size Owner Accessed Modified Changed Permissions");
+
+  result= create_column_configuration(
+	fgets(buffer2, 3000, config_file));
+
+  fclose(config_file);
+
+  return result;
+}
+
+static void
+mru_save(char** list)
+{
+  int i;
+  char filename[MAXPATHLEN*2] = "";
+  char line_buffer[MAXPATHLEN*2];
+  char* app_name = g_get_prgname();
+  FILE* file;
+  char* homedir = g_get_home_dir();
+
+  sprintf(filename, "%s/"NON_GNOME_CONFIG_FILE"_mru_%s",
+		  homedir, app_name);
+
+  file = fopen(filename, "w");
+
+  if(!file)
+	  return;
+
+  for(i=0; i<NUM_MRU_ENTRIES;++i)
+	{
+	  fprintf(stderr,"mru #%d: %s\n", i, list[i]);
+	  snprintf(line_buffer, MAXPATHLEN*2,
+			   "%s\n", list[i]?list[i]:"");
+	  fputs(line_buffer, file);
+	}
+  fclose(file);
+}
+
+static void
+mru_load(char** list)
+{
+  int i;
+  char filename[MAXPATHLEN*2] = "";
+  char line_buffer[MAXPATHLEN*2];
+  char* app_name = g_get_prgname();
+  FILE* file;
+  char* homedir = g_get_home_dir();
+
+  sprintf(filename, "%s/"NON_GNOME_CONFIG_FILE"_mru_%s", homedir, app_name);
+
+  for(i=0;i<NUM_MRU_ENTRIES;++i)
+	list[i] = g_strdup("");
+
+  file = fopen(filename, "r");
+  if(!file)
+	return;
+
+  for(i=0;i<NUM_MRU_ENTRIES && fgets(line_buffer, MAXPATHLEN*2, file);++i)
+	{
+	  if(list[i]) g_free(list[i]);
+	  /* Remove trailing \n */
+	  if(line_buffer[strlen(line_buffer)-1] == '\n')
+		line_buffer[strlen(line_buffer)-1] = 0;
+	  
+	  list[i] = g_strdup(line_buffer);
+	}
+
+  fclose(file);
+}
+
+
+
+#endif
+
+static void
+mru_populate(char** list, GtkCombo* gc)
+{
+  int i;
+  GList* items=NULL;
+  
+  for(i=0;i<NUM_MRU_ENTRIES;++i)
+	{
+	  if(!list[i]) continue;
+	  items = g_list_prepend(items, list[i]);
+	}  
+  items = g_list_reverse(items);
+
+  gtk_combo_set_popdown_strings(gc, items);
+
+  g_list_free(items);
+}
+
+static void
+mru_prepend(char** list, char* item)
+{
+  int i;
+    
+  if(list[NUM_MRU_ENTRIES-1])
+	g_free(list[NUM_MRU_ENTRIES-1]);
+
+  for(i=NUM_MRU_ENTRIES-1;i>0;--i)
+	{
+	  list[i] = list[i-1];
+	}
+  
+  list[0] = g_strdup(item);  
+}
+
+/* Return NULL on error */
+static Column*
+column_from_name(const char* name)
+{
+  const ColumnType** cols=get_available_column_types();
+  while(*cols)
+	{
+	  if(!strcmp(name,(*cols)->col_title))
+		{
+		  return ((*cols)->constructor) ? (*cols)->constructor() : NULL;
+		}
+	  ++cols;
+	}
+
+  return NULL;
+}
+
+/* First element the sorting col */
+static ColumnConfiguration*
+create_column_configuration(const char* savedconfig)
+{
+  /* Parse column configuration */
+  int sort_ascending=1;
+  int sortcol=0;
+  char* currtok=0;
+  ColumnConfiguration* newcc = g_new0(ColumnConfiguration,1);
+  char* copy = g_malloc(strlen(savedconfig)+1);
+  memcpy(copy,savedconfig,strlen(savedconfig)+1);
+  
+
+  /* Strtok is actually useful */
+  currtok = strtok(copy, " ");
+  
+  g_assert(currtok);
+
+  if(*currtok == '-')
+	  sort_ascending=0;
+
+  sortcol = atoi(currtok);
+
+  currtok = strtok(NULL, " ");
+  
+  while(currtok)
+	{
+	  Column* newcol=column_from_name(currtok);
+	  if(newcol)
+		{
+		newcc->the_columns = g_list_append(newcc->the_columns,newcol);
+		++newcc->num_columns;
+		}
+	  
+	  currtok = strtok(NULL, " ");
+	}
+  g_free(copy);
+
+  if(sortcol < 0)
+	{
+	  sort_ascending = 0;
+	  sortcol = -sortcol;
+	}
+
+  /* Set sorting column */
+  g_assert(sortcol < newcc->num_columns); /* Sanity check */
+  
+  newcc->sorting_ordinal = sortcol;
+  newcc->current_sorting_column = g_list_nth_data(newcc->the_columns,
+												  sortcol);
+  newcc->current_sorting_column->sort_ascending = sort_ascending;
+  
+  return newcc;
+}
+
+static void
+destroy_column_configuration(ColumnConfiguration* cc)
+{
+  GList* cl=cc->the_columns;
+
+  /* Walk columns list */
+  while(cl)
+	{
+	  if(cl->data)
+		((Column*)(cl->data))->destructor((Column*)cl->data);
+	  cl=cl->next;
+	}
+
+  /* Delete the list */
+  g_list_free(cc->the_columns);
+
+  /* Delete the thing itself */
+  g_free(cc);
+}
+
+/* 0-based index */
+static void
+columnconfig_set_sorting_column(ColumnConfiguration* cc,
+											int column)
+{
+  Column *oldcol, *newcol;
+
+  if(column >= cc->num_columns)
+	{
+	  g_warning("Invalid sorting columns %d", column);
+	  return;
+	}
+
+  oldcol = (Column*)g_list_nth_data(cc->the_columns, cc->sorting_ordinal);
+  newcol = (Column*)g_list_nth_data(cc->the_columns, column);
+
+  /* Toggle if needed */
+  if(oldcol == newcol)
+	  oldcol->sort_ascending = !oldcol->sort_ascending;
+  else
+	  newcol->sort_ascending=1;
+
+  /* Set arrows as appropriate */
+  if(oldcol->arrow && newcol->arrow)
+	{
+	  gtk_arrow_set(GTK_ARROW(oldcol->arrow),
+					GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+	  
+	  gtk_arrow_set(GTK_ARROW(newcol->arrow),
+					(newcol->sort_ascending)?GTK_ARROW_DOWN:GTK_ARROW_UP,
+					GTK_SHADOW_IN);
+	}
+				
+  cc->sorting_ordinal=column;
+  cc->current_sorting_column=newcol;
+}
+
+static int
+magic_row_sort_function(const Row* row1, const Row* row2)
+{
+  ColumnConfiguration* cc = row1->column_config;
+  return cc->current_sorting_column->sorter(
+  	cc->current_sorting_column,
+	row1->cells[cc->sorting_ordinal],
+	row2->cells[cc->sorting_ordinal]);
+}
+
+static Row*
+row_create(const char* filename, const char* fullpath,
+		   ColumnConfiguration* cc)
+{  
+  ProcessFileData pfd;
+  FileCell** newcells;
+  GList* cl;
+  struct stat statbuf;
+  Row* newrow;
+  int i=0;
+
+  /* Set up pfd structure */
+  memset(&pfd, 0, sizeof(ProcessFileData));
+  pfd.filename=filename;
+  pfd.fullpath=fullpath;  
+  if(stat(pfd.fullpath,&statbuf)!=0)
+	pfd.statbuf=NULL;
+  else
+	pfd.statbuf=&statbuf;
+
+  /* Actual row creation */
+  newrow = g_new(Row,1);
+  newrow->column_config = cc;
+  newcells = g_new(FileCell*, cc->num_columns);
+  newrow->cells=newcells;
+
+  /* Set the row filename */
+  newrow->filename = g_strdup(filename);
+
+  /* Populate */
+  cl = cc->the_columns;
+  while(cl)
+	{
+	  newcells[i++] = ((Column*)cl->data)->filecell_constructor(
+		 ((Column*)cl->data), &pfd);
+	  
+	  cl=cl->next;
+	}
+
+  return newrow;
+}
+
+static void
+row_destroy(Row* r)
+{
+  int i=0;
+  ColumnConfiguration* cc = r->column_config;
+  GList* cl=cc->the_columns;
+
+  /* Free cells */
+  while(cl)
+	{
+	  Column* col = (Column*)cl->data;
+	  col->filecell_destructor(col, r->cells[i++]);
+	  cl=cl->next;
+	}
+
+  /* Free the row filename */
+  g_free(r->filename);
+
+  /* Free the row */
+  g_free(r);
+}
+
+static void
+row_populate_clist(GList* rows, GtkCList* cl)
+{
+  int i=0;
+  int rownum=0;
+  char** tmpbuf;
+  ColumnConfiguration* cc;
+  
+  gtk_clist_freeze(cl);
+  gtk_clist_clear(cl);
+
+  if(!rows)
+	{
+	  gtk_clist_thaw(cl);
+	  return; /* Why bother if empty? */
+	}
+
+  cc=((Row*)(rows->data))->column_config;
+
+  /* Set up dummy text */
+  tmpbuf=g_new0(char*,cc->num_columns);
+  for(i=0; i < cc->num_columns; ++i)
+	tmpbuf[i] = "";
+
+  /* Fill data */
+  while(rows)
+	{
+	  Row* cur_row = (Row*)(rows->data);
+
+	  /* Add row */
+	  rownum = gtk_clist_append(cl, tmpbuf);
+	  
+	  for(i=0; i < cc->num_columns; ++i)
+		{		  
+		  FileCell* cur_cell = cur_row->cells[i];
+
+		  if(cur_cell->text && cur_cell->pixmap)
+			{
+			  gtk_clist_set_pixtext(cl,
+				rownum,
+				i, cur_cell->text, 4 /* Arbitrary */,
+				cur_cell->pixmap, cur_cell->mask);									
+			}
+		  else if(cur_cell->text)
+			{
+			  gtk_clist_set_text(cl,rownum,i,cur_cell->text);
+			}
+		  else if(cur_cell->pixmap)
+			{
+			  gtk_clist_set_pixmap(cl, rownum, i, cur_cell->pixmap,
+								   cur_cell->mask);
+			}		  
+		}
+	  rows=rows->next;
+	}
+  g_free(tmpbuf);
+
+  /* Set optimal column widths */
+  for(i=0;i<cc->num_columns;++i)
+	{
+	  gtk_clist_set_column_width(cl,i,
+		 gtk_clist_optimal_column_width(cl, i));
+	}
+  
+  gtk_clist_thaw(cl);
+}
+
+static void
+rowlist_destroy(GList* rows_orig)
+{
+  GList* rows = rows_orig;
+  while(rows)
+	{
+	  row_destroy((Row*)rows->data);
+	  rows=rows->next;
+	}
+  if(rows_orig)
+	g_list_free(rows_orig);
+}
+
+static void
+file_handle_click_column(GtkCList* clist,
+				  gint column,
+				  GtkFileSelection* fs)
+{
+  CompletionState* cmpl_state = (CompletionState*)fs->cmpl_state;
+  ColumnConfiguration* cc = cmpl_state->cc;
+
+  columnconfig_set_sorting_column(cc, column);
+
+  /* Resort */
+  cmpl_state->row_list = g_list_sort(cmpl_state->row_list,
+									 (GCompareFunc)magic_row_sort_function);
+
+  /* Repopulate */
+  row_populate_clist(cmpl_state->row_list, GTK_CLIST(fs->file_list));
+}
+
+/**********************************************************************/
+/*	                 Configuration Dialog                         */
+/**********************************************************************/
+
+typedef struct _ColumnConfigure_Dialog
+{
+  GtkWidget* ok_button;
+  GtkWidget* cancel_button;
+  GtkWidget* hbox;
+  GtkDialog* dialog;
+  GtkWidget* insert_button;
+  GtkWidget* delete_button;
+  GtkWidget* config_button;
+  GtkWidget* up_button;
+  GtkWidget* down_button;
+  GtkWidget* sort_button;
+
+  ColumnConfiguration* cc;
+  GtkFileSelection* fs;
+  CompletionState* cmpl_state;
+
+  GtkCList* columns_list;
+  GtkCList* avail_cols;
+
+  int cur_row_use;
+  int cur_row_avail;
+  int num_avail;
+  
+} ColumnConfigure_Dialog;
+
+static void
+columnconfig_configure_destroy(GtkDialog* dialog,
+							   ColumnConfigure_Dialog* cd)
+{
+  char* tmp = columnconfig_get_config(cd->cc);
+  fprintf(stderr, "As destroying new colconfig: %s\n", tmp);
+  destroy_column_configuration(cd->cc);
+  g_free(cd);
+  g_free(tmp);
+}
+
+static void
+realloc_concat(char** str, const char* add, const char* sep)
+{
+  char* tmp;
+
+  g_return_if_fail(add != NULL && str != NULL && *str != NULL);
+
+  tmp = *str;
+  if(!sep)
+	sep="";
+  
+  *str = g_strconcat(*str, sep, add, NULL);
+  
+  g_free(tmp);  
+}
+
+static char*
+columnconfig_get_config(ColumnConfiguration* cc)
+{
+  GList* cl;
+  Column* curcol;
+  char* str=0;
+
+  cl = cc->the_columns;
+
+  if(cc->current_sorting_column->sort_ascending)
+	str = g_strdup_printf("%d ", cc->sorting_ordinal);
+  else
+	str = g_strdup_printf("-%d ", cc->sorting_ordinal);
+  
+  while(cl)
+	{
+	  curcol = (Column*)cl->data;
+
+	  realloc_concat(&str, curcol->col_type->col_title, " ");
+
+	  if(curcol->get_config)
+		{
+		  char* tmp = curcol->get_config(curcol);
+		  if(tmp && !*tmp)
+			{
+			  realloc_concat(&str, tmp, "|");
+			}
+		}		
+	  
+	  cl=cl->next;
+	}
+
+  return str;
+}
+
+static void
+columnconfig_select_row_use(GtkCList* clist,
+							gint row, gint col,
+							GdkEventButton* event,
+							ColumnConfigure_Dialog* cd)
+{
+  cd->cur_row_use = row;
+}
+
+static void
+columnconfig_unselect_row_use(GtkCList* clist,
+							ColumnConfigure_Dialog* cd)
+{
+  cd->cur_row_use = -1;
+}
+
+static void
+columnconfig_unselect_row_use2(GtkCList* clist,
+							   gint row, gint col,
+							   GdkEventButton* event,
+							   ColumnConfigure_Dialog* cd)
+{
+  columnconfig_unselect_row_use(clist, cd);
+}
+
+static void
+columnconfig_unselect_row_avail(GtkCList* clist,
+							ColumnConfigure_Dialog* cd)
+{
+  cd->cur_row_avail = -1;
+}
+
+static void
+columnconfig_select_row_avail(GtkCList* clist,
+							gint row, gint col,
+							GdkEventButton* event,
+							ColumnConfigure_Dialog* cd)
+{
+  cd->cur_row_avail = row;
+}
+
+static void
+columnconfig_unselect_row_avail2(GtkCList* clist,
+							   gint row, gint col,
+							   GdkEventButton* event,
+							   ColumnConfigure_Dialog* cd)
+{
+  columnconfig_unselect_row_avail(clist, cd);
+}
+
+static void
+columnconfig_populate_use_list(ColumnConfigure_Dialog* cd)
+{
+  GList* cl=cd->cc->the_columns;
+
+  gtk_clist_freeze(cd->columns_list);
+  gtk_clist_clear(cd->columns_list);
+    
+  while(cl)
+	{
+	  Column* curcol = (Column*)cl->data;
+	  char* text[2];
+	  text[1] = curcol->col_type->description;
+	  
+	  if(curcol == cd->cc->current_sorting_column)			
+		text[0] = g_strconcat(curcol->col_type->col_title,
+							  (curcol->sort_ascending)?
+							  " ^":" V", NULL);
+	  else
+		text[0] = g_strdup(curcol->col_type->col_title);			  
+	  
+	  gtk_clist_append(cd->columns_list, text);
+	  
+	  g_free(text[0]);
+	
+	  cl=cl->next;
+	}
+  
+  gtk_clist_thaw(cd->columns_list);
+  
+  if(cd->cur_row_use >= g_list_length(cd->cc->the_columns))
+	gtk_clist_select_row(cd->columns_list, g_list_length(cd->cc->the_columns)-1,0);  
+  else if (cd->cur_row_use >= 0)
+	gtk_clist_select_row(cd->columns_list, cd->cur_row_use,0);
+  else
+	gtk_clist_unselect_all(cd->columns_list);
+	
+}
+
+static void
+columnconfig_up_button(GtkWindow* w, ColumnConfigure_Dialog* cd)
+{
+  GList* node;
+  gpointer tmp;
+  
+  if(cd->cur_row_use == -1) return;
+  if(g_list_length(cd->cc->the_columns)<2) return;
+
+  g_return_if_fail(cd->cur_row_use < g_list_length(cd->cc->the_columns));
+
+  node = g_list_nth(cd->cc->the_columns,
+					cd->cur_row_use);
+  g_return_if_fail(node != NULL);
+
+  if(!node->prev)
+	return;
+
+  tmp=node->prev->data;
+  node->prev->data=node->data;
+  node->data=tmp;
+
+  cd->cur_row_use--;
+  columnconfig_populate_use_list(cd);
+}
+
+static void
+columnconfig_down_button(GtkWindow* w, ColumnConfigure_Dialog* cd)
+{
+  GList* node;
+  gpointer tmp;
+  
+  if(cd->cur_row_use == -1) return;
+  if(g_list_length(cd->cc->the_columns)<2) return;
+
+  g_return_if_fail(cd->cur_row_use < g_list_length(cd->cc->the_columns));
+
+  node = g_list_nth(cd->cc->the_columns,
+					cd->cur_row_use);
+  g_return_if_fail(node != NULL);
+
+  if(!node->next)
+	return;
+
+  tmp=node->next->data;
+  node->next->data=node->data;
+  node->data=tmp;
+
+  cd->cur_row_use++;
+  columnconfig_populate_use_list(cd);
+}
+
+static void
+columnconfig_insert_button(GtkWindow* w, ColumnConfigure_Dialog* cd)
+{
+  Column* newcol;
+  char* name;
+
+  if(cd->cur_row_avail==-1)
+	return;
+
+  if(cd->num_avail < 1)
+	return;
+
+  g_return_if_fail(cd->cur_row_avail < cd->num_avail);
+
+  gtk_clist_get_text(cd->avail_cols,
+					 cd->cur_row_avail, 0,
+					 &name); 
+  
+  newcol = column_from_name(name);
+  g_return_if_fail(newcol != NULL);
+
+  g_list_insert(cd->cc->the_columns, newcol, cd->cur_row_use);
+
+  columnconfig_populate_use_list(cd);
+}
+
+static void
+columnconfig_delete_button(GtkWidget* w, ColumnConfigure_Dialog* cd)
+{
+  Column* curcol;
+  GList* node;
+  
+  if(cd->cur_row_use == -1)
+	return;
+
+  if(g_list_length(cd->cc->the_columns)<2)
+	return;
+
+  node = g_list_nth(cd->cc->the_columns,
+					cd->cur_row_use);
+
+  g_return_if_fail(node != NULL);
+
+  curcol = (Column*)node->data;
+
+  cd->cc->the_columns = g_list_remove_link(cd->cc->the_columns, node);
+
+  if(cd->cc->current_sorting_column == curcol)
+	{
+	  cd->cc->current_sorting_column = (Column*)
+		cd->cc->the_columns->data;
+	  cd->cc->sorting_ordinal = 0;
+	}
+
+  curcol->destructor(curcol);
+
+  g_list_free_1(node);
+
+  columnconfig_populate_use_list(cd);
+}
+
+static void
+columnconfig_sort_button(GtkWidget* w, ColumnConfigure_Dialog* cd)
+{
+  if(cd->cur_row_use==-1) return;
+  g_return_if_fail(cd->cur_row_use < g_list_length(cd->cc->the_columns));
+
+  columnconfig_set_sorting_column(cd->cc, cd->cur_row_use);
+  columnconfig_populate_use_list(cd);
+}
+
+static void
+columnconfig_commit(GtkWidget* w, ColumnConfigure_Dialog* cd)
+{
+  ColumnConfiguration* freshcc;
+
+  char* tmp = columnconfig_get_config(cd->cc);
+  freshcc = create_column_configuration(tmp);
+  g_free(tmp);
+
+  rowlist_destroy(cd->cmpl_state->row_list);
+  cd->cmpl_state->row_list=NULL;
+
+  gtk_widget_destroy(GTK_WIDGET(cd->fs->file_list));
+  destroy_column_configuration(cd->cmpl_state->cc);
+
+  cd->cmpl_state->cc = freshcc;
+
+  gtk_file_selection_filelist_init(cd->fs);
+
+  gtk_container_add(GTK_CONTAINER(cd->cmpl_state->parent_scrolled_win),
+					 cd->fs->file_list);
+
+  gtk_widget_show(GTK_WIDGET(cd->fs->file_list));
+
+  gtk_file_selection_populate(cd->fs, "", FALSE);
+ 
+  gtk_widget_destroy(GTK_WIDGET(cd->dialog));
+
+  columnconfig_save_config(freshcc);
+}
+
+/* All configuration begins here */
+static void
+columnconfig_configure_thyself(GtkWidget* w, GtkFileSelection* fs)
+{
+  ColumnConfigure_Dialog* cd = g_new(ColumnConfigure_Dialog,1);
+  char* titles[2] = {_("Column"), _("Description")};
+  char* tmp;
+  const ColumnType** cols = get_available_column_types();
+  GtkWidget *vbox1, *vbox2, *vbox3, *label, *secbox;
+  GtkScrolledWindow *sw1, *sw2;
+  
+  cd->fs=fs;
+  cd->cur_row_use=-1;
+  cd->cmpl_state=(CompletionState*)fs->cmpl_state;
+
+  sw1 = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
+  sw2 = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
+  gtk_scrolled_window_set_policy(sw1,
+								 GTK_POLICY_AUTOMATIC,
+								 GTK_POLICY_AUTOMATIC);
+  gtk_scrolled_window_set_policy(sw2,
+								 GTK_POLICY_AUTOMATIC,
+								 GTK_POLICY_AUTOMATIC);
+  
+  vbox1 = gtk_vbox_new(FALSE, 0);
+  label = gtk_label_new(_("Available Columns:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 1.0f);
+  gtk_box_pack_start (GTK_BOX(vbox1), label, FALSE, FALSE, 0);
+  
+  vbox2 = gtk_vbox_new(FALSE, 5);
+  secbox = gtk_vbox_new(TRUE, 5);
+  gtk_box_pack_start (GTK_BOX(vbox2), secbox, TRUE, FALSE, 0);
+  
+  cd->insert_button = gtk_button_new_with_label("<--");
+  gtk_signal_connect(GTK_OBJECT(cd->insert_button),
+					 "clicked", (GtkSignalFunc)
+					 columnconfig_insert_button, cd);
+  
+  cd->delete_button = gtk_button_new_with_label(_("Delete"));
+  gtk_signal_connect(GTK_OBJECT(cd->delete_button),
+					 "clicked", (GtkSignalFunc)
+					 columnconfig_delete_button, cd);  
+  
+  cd->config_button = gtk_button_new_with_label(_("Configure"));
+  
+  cd->sort_button = gtk_button_new_with_label(_("Sort"));
+  gtk_signal_connect(GTK_OBJECT(cd->sort_button),
+					 "clicked", (GtkSignalFunc)
+					 columnconfig_sort_button, cd);
+  
+  cd->up_button = gtk_button_new();
+  gtk_signal_connect(GTK_OBJECT(cd->up_button),
+					 "clicked", (GtkSignalFunc)
+					 columnconfig_up_button, cd);
+  
+  cd->down_button = gtk_button_new();
+  gtk_signal_connect(GTK_OBJECT(cd->down_button),
+					 "clicked", (GtkSignalFunc)
+					 columnconfig_down_button, cd);  
+
+  gtk_box_pack_start(GTK_BOX(secbox), cd->up_button, FALSE, FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(secbox), cd->down_button, FALSE, FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(secbox), gtk_label_new(" "), FALSE, FALSE, 0);
+  
+  gtk_box_pack_start(GTK_BOX(secbox), cd->insert_button, FALSE, FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(secbox), cd->delete_button, FALSE, FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(secbox), cd->sort_button, FALSE, FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(secbox), cd->config_button, FALSE, FALSE, 0);
+  
+  gtk_container_add(GTK_CONTAINER(cd->up_button), gtk_arrow_new(GTK_ARROW_UP,
+												 GTK_SHADOW_IN));
+  gtk_container_add(GTK_CONTAINER(cd->down_button), gtk_arrow_new(GTK_ARROW_DOWN,
+												   GTK_SHADOW_IN));  
+  
+  gtk_widget_set_sensitive(GTK_WIDGET(cd->config_button), FALSE);
+  
+  vbox3 = gtk_vbox_new(FALSE, 0);
+  label = gtk_label_new(_("Columns in use:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 1.0f);
+  gtk_box_pack_start (GTK_BOX(vbox3), label, FALSE, FALSE, 0);
+  
+  cd->dialog=GTK_DIALOG(gtk_dialog_new());
+  
+  /* Copy old columnconfiguration */  
+  tmp=columnconfig_get_config(cd->cmpl_state->cc);
+  cd->cc = create_column_configuration(tmp);
+  g_free(tmp);
+  cd->num_avail=0;
+  
+  cd->ok_button = gtk_button_new_with_label(_("OK"));
+  gtk_box_pack_start(GTK_BOX((GTK_DIALOG(cd->dialog)->action_area)),
+					cd->ok_button, FALSE, FALSE, 0);
+  cd->cancel_button = gtk_button_new_with_label(_("Cancel"));
+  gtk_container_add(GTK_CONTAINER((GTK_DIALOG(cd->dialog)->action_area)),
+					cd->cancel_button);
+  gtk_signal_connect(GTK_OBJECT(cd->dialog),"destroy",
+					 columnconfig_configure_destroy, cd);
+
+  /* Initial hbox */
+  cd->hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start (GTK_BOX(cd->dialog->vbox),
+					  cd->hbox, TRUE, TRUE, 0);
+
+  gtk_box_pack_start(GTK_BOX(cd->hbox),vbox3, TRUE, TRUE, 5);
+  gtk_box_pack_start(GTK_BOX(cd->hbox),vbox2, FALSE, FALSE, 5);
+  gtk_box_pack_start(GTK_BOX(cd->hbox),vbox1, TRUE, TRUE, 5);
+
+  /* Available Columns */
+  cd->avail_cols = GTK_CLIST(gtk_clist_new_with_titles(2, titles));
+  gtk_container_add(GTK_CONTAINER(sw1), GTK_WIDGET(cd->avail_cols));
+  gtk_box_pack_start (GTK_BOX(vbox1), GTK_WIDGET(sw1),
+					  TRUE, TRUE, 5);
+  gtk_signal_connect(GTK_OBJECT(cd->avail_cols),
+					 "select_row", (GtkSignalFunc)columnconfig_select_row_avail,
+					 cd);
+  gtk_signal_connect(GTK_OBJECT(cd->avail_cols),
+					 "unselect_row", (GtkSignalFunc)columnconfig_unselect_row_avail2,
+					 cd);
+  gtk_signal_connect(GTK_OBJECT(cd->avail_cols),
+					 "unselect_all", (GtkSignalFunc)columnconfig_unselect_row_avail,
+					 cd);
+  while(*cols)
+	{
+	  char* text[2] = {(*cols)->col_title,
+					   (*cols)->description};
+	  gtk_clist_append(cd->avail_cols, text);
+	  ++cols;
+	  ++(cd->num_avail);
+	}
+  
+  gtk_clist_set_column_width(cd->avail_cols,0,
+							 gtk_clist_optimal_column_width(cd->avail_cols, 0));
+  gtk_clist_set_column_width(cd->avail_cols,1,
+							 gtk_clist_optimal_column_width(cd->avail_cols, 1));
+  gtk_widget_set_usize(GTK_WIDGET(cd->avail_cols), 300, 300);
+
+
+  /* Columns in use */
+  cd->columns_list = GTK_CLIST(gtk_clist_new_with_titles(2, titles));
+  gtk_container_add(GTK_CONTAINER(sw2), GTK_WIDGET(cd->columns_list));
+  gtk_box_pack_start (GTK_BOX(vbox3), GTK_WIDGET(sw2),
+					  TRUE, TRUE, 5);
+
+  gtk_signal_connect(GTK_OBJECT(cd->columns_list),
+					 "select_row", (GtkSignalFunc)columnconfig_select_row_use,
+					 cd);
+  gtk_signal_connect(GTK_OBJECT(cd->columns_list),
+					 "unselect_row", (GtkSignalFunc)columnconfig_unselect_row_use2,
+					 cd);
+  gtk_signal_connect(GTK_OBJECT(cd->columns_list),
+					 "unselect_all", (GtkSignalFunc)columnconfig_unselect_row_use,
+					 cd);
+
+  columnconfig_populate_use_list(cd);
+
+
+  gtk_clist_set_column_width(cd->columns_list,0,
+							 gtk_clist_optimal_column_width(cd->columns_list, 0));
+  gtk_clist_set_column_width(cd->columns_list,1,
+							 gtk_clist_optimal_column_width(cd->columns_list, 1));
+  gtk_widget_set_usize(GTK_WIDGET(cd->columns_list), 300, 300);
+
+  gtk_signal_connect_object (GTK_OBJECT (cd->cancel_button), "clicked",
+							 (GtkSignalFunc) gtk_widget_destroy, 
+							 (gpointer) cd->dialog);
+  gtk_signal_connect (GTK_OBJECT (cd->ok_button), "clicked",
+			     (GtkSignalFunc) columnconfig_commit, 
+			     cd);
+
+  gtk_widget_show_all(GTK_WIDGET(cd->dialog));
+  gtk_window_set_modal(GTK_WINDOW(cd->dialog), TRUE);
+}
+
+
+/**********************************************************************/
+/*	                 Construction, deletion                       */
+/**********************************************************************/
+
+static CompletionState*
+cmpl_init_state (void)
+{
+  gchar getcwd_buf[2*MAXPATHLEN];
+  CompletionState *new_state;
+  int i;
+
+  new_state = g_new (CompletionState, 1);
+
+  /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
+   * and, if that wasn't bad enough, hangs in doing so.
+   */
+#if defined(sun) && !defined(__SVR4)
+  if (!getwd (getcwd_buf))
+#else    
+  if (!getcwd (getcwd_buf, MAXPATHLEN))
+#endif    
+    {
+      /* Oh joy, we can't get the current directory. Um..., we should have
+       * a root directory, right? Right? (Probably not portable to non-Unix)
+       */
+      strcpy (getcwd_buf, "/");
+    }
+
+tryagain:
+
+  new_state->reference_dir = NULL;
+  new_state->completion_dir = NULL;
+  new_state->active_completion_dir = NULL;
+  new_state->directory_storage = NULL;
+  new_state->directory_sent_storage = NULL;
+  new_state->last_valid_char = 0;
+  new_state->updated_text = g_new (gchar, MAXPATHLEN);
+  new_state->updated_text_alloc = MAXPATHLEN;
+  new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
+  new_state->the_completion.text_alloc = MAXPATHLEN;
+  new_state->user_dir_name_buffer = NULL;
+  new_state->user_directories = NULL;
+
+  new_state->reference_dir =  open_dir (getcwd_buf, new_state);
+
+  if (!new_state->reference_dir)
+    {
+      /* Directories changing from underneath us, grumble */
+      strcpy (getcwd_buf, "/");
+      goto tryagain;
+    }
+
+  new_state->cc = the_column_configuration();
+  new_state->row_list = NULL; /* Empty, for now */
+
+  new_state->dir_sort_ascending=1;
+
+  mru_load(new_state->mru_list);
+  
+  return new_state;
+}
+
+static void
+cmpl_free_dir_list(GList* dp0)
+{
+  GList *dp = dp0;
+
+  while (dp) {
+    free_dir (dp->data);
+    dp = dp->next;
+  }
+
+  g_list_free(dp0);
+}
+
+static void
+cmpl_free_dir_sent_list(GList* dp0)
+{
+  GList *dp = dp0;
+
+  while (dp) {
+    free_dir_sent (dp->data);
+    dp = dp->next;
+  }
+
+  g_list_free(dp0);
+}
+
+static void
+cmpl_free_state (CompletionState* cmpl_state)
+{
+  int i;
+  cmpl_free_dir_list (cmpl_state->directory_storage);
+  cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
+
+  if (cmpl_state->user_dir_name_buffer)
+    g_free (cmpl_state->user_dir_name_buffer);
+  if (cmpl_state->user_directories)
+    g_free (cmpl_state->user_directories);
+  if (cmpl_state->the_completion.text)
+    g_free (cmpl_state->the_completion.text);
+  if (cmpl_state->updated_text)
+    g_free (cmpl_state->updated_text);
+
+  rowlist_destroy(cmpl_state->row_list);
+  destroy_column_configuration(cmpl_state->cc);
+
+  for(i=0;i<NUM_MRU_ENTRIES;++i)
+	if(cmpl_state->mru_list[i])
+	  g_free(cmpl_state->mru_list[i]);
+
+  g_free (cmpl_state);
+}
 
 static void
 free_dir(CompletionDir* dir)
@@ -2750,13 +4771,4 @@
 cmpl_state_okay(CompletionState* cmpl_state)
 {
   return  cmpl_state && cmpl_state->reference_dir;
-}
-
-static gchar*
-cmpl_strerror(gint err)
-{
-  if(err == CMPL_ERRNO_TOO_LONG)
-    return "Name too long";
-  else
-    return g_strerror (err);
 }




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]