keep filename when browsing directories in file selection dialog
[claws.git] / src / filesel.c
index 27093fc55d3956a0d2394d06317c41ba70a74546..44f77a48461719f9d0b3756508d4f73c83f6e57f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999,2000 Hiroyuki Yamamoto
+ * Copyright (C) 1999-2001 Hiroyuki Yamamoto
  *
  * 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
@@ -23,6 +23,8 @@
 #include <gtk/gtkfilesel.h>
 #include <gtk/gtkmain.h>
 #include <gtk/gtksignal.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkentry.h>
 
 #include "main.h"
 #include "filesel.h"
 
 static GtkWidget *filesel;
 static gboolean filesel_ack;
+static gchar *filesel_oldfilename = NULL;
 
-static void filesel_create(const gchar *title);
+static void filesel_create(const gchar *title, gboolean multiple_files);
 static void filesel_ok_cb(GtkWidget *widget, gpointer data);
 static void filesel_cancel_cb(GtkWidget *widget, gpointer data);
+static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data);
 static void key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data);
 
+static void filesel_file_list_select_row_multi(GtkCList *clist, gint row, gint col,
+                                              GdkEventButton *event, gpointer userdata);
+static void filesel_file_list_select_row_single(GtkCList *clist, gint row, gint col,
+                                               GdkEventButton *event, gpointer userdata);
+
+static void filesel_dir_list_select_row_multi(GtkCList *clist, gint row, gint col,
+                                             GdkEventButton *event, gpointer userdata);
+static void filesel_dir_list_select_row_single(GtkCList *clist, gint row, gint col,
+                                              GdkEventButton *event, gpointer userdata);
+
+static GList *filesel_get_multiple_filenames(void);
+
 gchar *filesel_select_file(const gchar *title, const gchar *file)
 {
        static gchar *filename = NULL;
        static gchar *cwd = NULL;
 
-       filesel_create(title);
+       filesel_create(title, FALSE);
 
        manage_window_set_transient(GTK_WINDOW(filesel));
 
@@ -56,9 +72,11 @@ gchar *filesel_select_file(const gchar *title, const gchar *file)
 
        gtk_file_selection_set_filename(GTK_FILE_SELECTION(filesel), cwd);
 
-       if (file)
+       if (file) {
                gtk_file_selection_set_filename(GTK_FILE_SELECTION(filesel),
                                                file);
+               filesel_oldfilename = g_strdup(file);
+       }
 
        gtk_widget_show(filesel);
 
@@ -87,7 +105,46 @@ gchar *filesel_select_file(const gchar *title, const gchar *file)
        return filename;
 }
 
-static void filesel_create(const gchar *title)
+GList *filesel_select_multiple_files(const gchar *title, const gchar *file)
+{
+       /* ALF - sorry for the exuberant code duping... need to 
+        * be cleaned up. */
+       static gchar *filename = NULL;
+       static gchar *cwd = NULL;
+       GList        *list = NULL;
+
+       filesel_create(title, TRUE);
+
+       manage_window_set_transient(GTK_WINDOW(filesel));
+
+       if (filename) {
+               g_free(filename);
+               filename = NULL;
+       }
+
+       if (!cwd)
+               cwd = g_strconcat(startup_dir, G_DIR_SEPARATOR_S, NULL);
+
+       gtk_file_selection_set_filename(GTK_FILE_SELECTION(filesel), cwd);
+
+       if (file)
+               gtk_file_selection_set_filename(GTK_FILE_SELECTION(filesel),
+                                               file);
+       gtk_widget_show(filesel);
+
+       gtk_main();
+
+       if (filesel_ack)
+               list = filesel_get_multiple_filenames();                
+
+       manage_window_focus_out(filesel, NULL, NULL);
+       gtk_widget_destroy(filesel);
+       GTK_EVENTS_FLUSH();
+
+       return list;
+}
+
+static void filesel_create(const gchar *title, gboolean multiple_files)
 {
        filesel = gtk_file_selection_new(title);
        gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button),
@@ -98,8 +155,7 @@ static void filesel_create(const gchar *title)
                 "clicked", GTK_SIGNAL_FUNC(filesel_cancel_cb),
                 NULL);
        gtk_signal_connect(GTK_OBJECT(filesel), "delete_event",
-                          GTK_SIGNAL_FUNC(filesel_cancel_cb),
-                          NULL);
+                          GTK_SIGNAL_FUNC(delete_event), NULL);
        gtk_signal_connect(GTK_OBJECT(filesel), "key_press_event",
                           GTK_SIGNAL_FUNC(key_pressed), NULL);
        gtk_signal_connect(GTK_OBJECT(filesel), "focus_in_event",
@@ -108,22 +164,171 @@ static void filesel_create(const gchar *title)
                           GTK_SIGNAL_FUNC(manage_window_focus_out), NULL);
 
        gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
+
+       if (multiple_files) {
+               gtk_clist_set_selection_mode
+                       (GTK_CLIST(GTK_FILE_SELECTION(filesel)->file_list),
+                        GTK_SELECTION_MULTIPLE);
+               gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->file_list),
+                                  "select_row", 
+                                  GTK_SIGNAL_FUNC(filesel_file_list_select_row_multi),
+                                  NULL);
+               gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->file_list),
+                                  "unselect_row",
+                                  GTK_SIGNAL_FUNC(filesel_file_list_select_row_multi),
+                                  NULL);
+               gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->dir_list),
+                                  "select_row",
+                                  GTK_SIGNAL_FUNC(filesel_dir_list_select_row_multi),
+                                  NULL);
+       } else {
+               gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->file_list),
+                                  "select_row", 
+                                  GTK_SIGNAL_FUNC(filesel_file_list_select_row_single),
+                                  NULL);
+               gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(filesel)->dir_list),
+                                  "select_row",
+                                  GTK_SIGNAL_FUNC(filesel_dir_list_select_row_single),
+                                  NULL);
+       }
 }
 
 static void filesel_ok_cb(GtkWidget *widget, gpointer data)
 {
        filesel_ack = TRUE;
+       g_free(filesel_oldfilename);
        gtk_main_quit();
 }
 
 static void filesel_cancel_cb(GtkWidget *widget, gpointer data)
 {
        filesel_ack = FALSE;
+       g_free(filesel_oldfilename);
        gtk_main_quit();
 }
 
+static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
+{
+       filesel_cancel_cb(NULL, NULL);
+       g_free(filesel_oldfilename);
+       return TRUE;
+}
+
 static void key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
        if (event && event->keyval == GDK_Escape)
                filesel_cancel_cb(NULL, NULL);
 }
+
+/* handle both "select_row" and "unselect_row". note that we're using the
+ * entry box to put there the selected file names in. we're not using these
+ * entry box to get the selected file names. instead we use the clist selection.
+ * the entry box is used only to retrieve dir name. */
+static void filesel_file_list_select_row_multi(GtkCList *clist, gint row, gint col,
+                                              GdkEventButton *event, gpointer userdata)
+{
+       /* simple implementation in which we clear the file entry and refill it */
+       GList    *list  = clist->selection;
+       GtkEntry *entry = GTK_ENTRY(GTK_FILE_SELECTION(filesel)->selection_entry);
+
+       gtk_editable_delete_text(GTK_EDITABLE(entry), 0, -1);
+
+#define INVALID_FILENAME_CHARS     " "
+       for (; list; list = list->next) {
+               gint row = GPOINTER_TO_INT(list->data);
+               gchar *text = NULL, *tmp;
+
+               if (!gtk_clist_get_text(clist, row, 0, &text))
+                       break;
+
+               /* NOTE: quick glance in source code of GtkCList
+                * reveals we should not free the returned 'text' */
+               
+               tmp = g_strconcat(text, " ", NULL);
+               text = tmp;
+               gtk_entry_append_text(entry, text); 
+               g_free(text);
+       }
+#undef INVALID_FILENAME_CHARS
+}
+
+static void filesel_dir_list_select_row_multi(GtkCList *clist, gint row, gint col,
+                                             GdkEventButton *event, gpointer userdata)
+{
+       GtkEntry *entry     = GTK_ENTRY(GTK_FILE_SELECTION(filesel)->selection_entry);
+       GtkCList *file_list = GTK_CLIST(GTK_FILE_SELECTION(filesel)->file_list);
+
+       /* if dir list is selected we clean everything */
+       gtk_editable_delete_text(GTK_EDITABLE(entry), 0, -1);
+       gtk_clist_unselect_all(file_list);
+}
+
+static void filesel_file_list_select_row_single(GtkCList *clist, gint row, gint col,
+                                               GdkEventButton *event, gpointer userdata)
+{
+       GtkEntry *entry = GTK_ENTRY(GTK_FILE_SELECTION(filesel)->selection_entry);
+
+       g_free(filesel_oldfilename);
+       filesel_oldfilename = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
+}
+
+static void filesel_dir_list_select_row_single(GtkCList *clist, gint row, gint col,
+                                              GdkEventButton *event, gpointer userdata)
+{
+       GtkEntry *entry     = GTK_ENTRY(GTK_FILE_SELECTION(filesel)->selection_entry);
+
+       gtk_editable_delete_text(GTK_EDITABLE(entry), 0, -1);
+       if(filesel_oldfilename) {
+               gtk_entry_append_text(entry, filesel_oldfilename);
+       }
+}
+
+static GList *filesel_get_multiple_filenames(void)
+{
+       /* as noted before we are not using the entry text when selecting
+        * multiple files. to much hassle to parse out invalid chars (chars
+        * that need to be escaped). instead we use the file_list. the
+        * entry is only useful for extracting the current directory. */
+       GtkCList *file_list  = GTK_CLIST(GTK_FILE_SELECTION(filesel)->file_list);
+       GtkEntry *file_entry = GTK_ENTRY(GTK_FILE_SELECTION(filesel)->selection_entry);
+       GList    *list = NULL, *sel_list;
+       gchar    *cwd, *tmp;     
+       gboolean  separator;
+
+       g_return_val_if_fail(file_list->selection != NULL, NULL);
+
+       tmp = gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
+       tmp = g_strdup(tmp);
+       cwd = g_dirname(tmp);
+       g_free(tmp);
+       g_return_val_if_fail(cwd != NULL, NULL);
+
+       /* only quick way to check the end of a multi byte string for our
+        * separator... */
+       g_strreverse(cwd);
+       separator = 0 == g_strncasecmp(cwd, G_DIR_SEPARATOR_S, strlen(G_DIR_SEPARATOR_S))
+               ?  TRUE : FALSE;
+       g_strreverse(cwd);
+
+       /* fetch the selected file names */
+       for (sel_list = file_list->selection; sel_list; sel_list = sel_list->next) {
+               gint   sel = GPOINTER_TO_INT(sel_list->data);
+               gchar *sel_text = NULL;
+               gchar *fname = NULL;
+               
+               gtk_clist_get_text(file_list, sel, 0, &sel_text);
+               if (!sel_text) continue;
+               sel_text = g_strdup(sel_text);
+
+               if (separator)
+                       fname = g_strconcat(cwd, sel_text, NULL);
+               else
+                       fname = g_strconcat(cwd, G_DIR_SEPARATOR_S, sel_text, NULL);
+               
+               list = g_list_append(list, fname);
+               g_free(sel_text);
+       }
+       
+       return list;
+}
+