2005-01-05 [colin] 0.9.13cvs27.2
[claws.git] / src / foldersel.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  * Copyright (C) 2004 Alfons Hoogervorst
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20
21 #include "defs.h"
22
23 #include <glib.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <gtk/gtkmain.h>
26 #include <gtk/gtkwidget.h>
27 #include <gtk/gtkwindow.h>
28 #include <gtk/gtkvbox.h>
29 #include <gtk/gtkscrolledwindow.h>
30 #include <gtk/gtkentry.h>
31 #include <gtk/gtkhbbox.h>
32 #include <gtk/gtksignal.h>
33
34 #include <gtk/gtktreestore.h>
35 #include <gtk/gtktreeview.h>
36
37 #include <stdio.h>
38 #include <unistd.h>
39 #include <string.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <fcntl.h>
43 #include <errno.h>
44
45 #include "intl.h"
46 #include "main.h"
47 #include "utils.h"
48 #include "gtkutils.h"
49 #include "stock_pixmap.h"
50 #include "foldersel.h"
51 #include "alertpanel.h"
52 #include "manage_window.h"
53 #include "folder.h"
54
55 enum {
56         FOLDERSEL_FOLDERNAME,
57         FOLDERSEL_FOLDERITEM,
58         FOLDERSEL_FOLDERPIXBUF,
59         FOLDERSEL_FOLDEREXPANDER,
60         N_FOLDERSEL_COLUMNS
61 };
62
63 static GdkPixbuf *folder_pixbuf;
64 static GdkPixbuf *folderopen_pixbuf;
65
66 static GtkWidget *window;
67 static GtkTreeView *tree_view;
68 static GtkWidget *entry;
69 static GtkWidget *ok_button;
70 static GtkWidget *cancel_button;
71
72 static FolderItem *folder_item;
73 static FolderItem *selected_item;
74
75 static gboolean cancelled;
76 static gboolean finished;
77
78 static GtkTreeStore *tree_store;
79
80 static void foldersel_update_tree_store         (Folder * folder, FolderSelectionType type);
81 static void foldersel_create                    (void);
82 static void foldersel_init_tree_view_images     (void);
83
84 static gboolean foldersel_selected(GtkTreeSelection *selector,
85                                    GtkTreeModel *model, 
86                                    GtkTreePath *path,
87                                    gboolean already_selected,
88                                    gpointer data);
89
90 static void foldersel_ok        (GtkButton      *button,
91                                  gpointer        data);
92 static void foldersel_cancel    (GtkButton      *button,
93                                  gpointer        data);
94 static void foldersel_activated (void);
95 static gint delete_event        (GtkWidget      *widget,
96                                  GdkEventAny    *event,
97                                  gpointer        data);
98 static gboolean key_pressed     (GtkWidget      *widget,
99                                  GdkEventKey    *event,
100                                  gpointer        data);
101
102 static gint foldersel_folder_name_compare(GtkTreeModel *model, 
103                                           GtkTreeIter *a, 
104                                           GtkTreeIter *b, 
105                                           gpointer context)
106 {
107         gchar *str_a = NULL, *str_b = NULL;
108         gint res = 0;
109         FolderItem *fld_a = NULL, *fld_b = NULL;
110         GtkTreeIter parent;
111
112         gtk_tree_model_get(model, a, 
113                            FOLDERSEL_FOLDERITEM, &fld_a,
114                            -1);
115         gtk_tree_model_get(model, b, 
116                            FOLDERSEL_FOLDERITEM, &fld_b,
117                            -1);
118
119         /* no sort for root folder */
120         if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a))
121                 return 0;
122
123         /* if both a and b are special folders, sort them according to 
124          * their types (which is in-order). Note that this assumes that
125          * there are no multiple folders of a special type. */
126         if (fld_a->stype != F_NORMAL  && fld_b->stype != F_NORMAL)
127                 return fld_a->stype < fld_b->stype ? -1 : 1;
128
129         /* if b is normal folder, and a is not, b is smaller (ends up 
130          * lower in the list) */ 
131         if (fld_a->stype != F_NORMAL && fld_b->stype == F_NORMAL)
132                 return -1;
133
134         /* if b is special folder, and a is not, b is larger (ends up
135          * higher in the list) */
136         if (fld_a->stype == F_NORMAL && fld_b->stype != F_NORMAL)        
137                 return 1;
138         
139         /* XXX g_utf8_collate_key() comparisons may speed things
140          * up when having large lists of folders */
141         gtk_tree_model_get(model, a, 
142                            FOLDERSEL_FOLDERNAME, &str_a, 
143                            -1);
144         gtk_tree_model_get(model, b, 
145                            FOLDERSEL_FOLDERNAME, &str_b, 
146                            -1);
147
148         /* otherwise just compare the folder names */           
149         res = g_utf8_collate(str_a, str_b);
150
151         g_free(str_a);
152         g_free(str_b);
153
154         return res;
155 }
156
157 typedef struct FolderItemSearch {
158         FolderItem  *item;
159         GtkTreePath *path;
160         GtkTreeIter  iter;
161 } FolderItemSearch;
162
163 static gboolean tree_view_folder_item_func(GtkTreeModel         *model,
164                                            GtkTreePath          *path,
165                                            GtkTreeIter          *iter,
166                                            FolderItemSearch     *data)
167 {
168         FolderItem *item = NULL;
169
170         gtk_tree_model_get(model, iter, FOLDERSEL_FOLDERITEM, &item, -1);
171         
172         if (data->item == item) {
173                 data->path = path;
174                 data->iter = *iter;
175                 return TRUE;
176         }
177         
178         return FALSE;
179 }
180
181 FolderItem *foldersel_folder_sel(Folder *cur_folder, FolderSelectionType type,
182                                  const gchar *default_folder)
183 {
184         GtkCTreeNode *node;
185
186         selected_item = NULL;
187
188         if (!window)
189                 foldersel_create();
190         else
191                 gtk_widget_show(window);
192
193         /* update the tree */           
194         foldersel_update_tree_store(cur_folder, type);
195         gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
196
197         gtk_tree_view_expand_all(tree_view); 
198         
199         manage_window_set_transient(GTK_WINDOW(window));
200         
201         gtk_widget_grab_focus(ok_button);
202         gtk_widget_grab_focus(GTK_WIDGET(tree_view));
203
204         /* select current */
205         if (folder_item) {
206                 FolderItemSearch fis;
207
208                 fis.item = folder_item;
209                 fis.path = NULL;
210
211                 /* find matching model entry */
212                 gtk_tree_model_foreach
213                         (GTK_TREE_MODEL(tree_store), 
214                          (GtkTreeModelForeachFunc)tree_view_folder_item_func,
215                          &fis);
216                 
217                 if (fis.path) {
218                         GtkTreeSelection *selector;
219                 
220                         selector = gtk_tree_view_get_selection(tree_view);
221                         gtk_tree_selection_select_iter(selector, &fis.iter);
222                         gtk_tree_view_set_cursor(tree_view, fis.path,
223                                                  NULL, FALSE);
224                         
225                 }
226         }
227
228         cancelled = finished = FALSE;
229
230         while (finished == FALSE)
231                 gtk_main_iteration();
232
233         gtk_widget_hide(window);
234         gtk_entry_set_text(GTK_ENTRY(entry), "");
235
236         if (!cancelled &&
237             selected_item && selected_item->path) {
238                 folder_item = selected_item;
239                 return folder_item;
240         } else
241                 return NULL;
242 }
243
244 static void foldersel_insert_gnode_in_store(GtkTreeStore *store, GNode *node, GtkTreeIter *parent)
245 {
246         FolderItem *item;
247         GtkTreeIter child;
248         gchar *name;
249         GNode *iter;
250
251         g_return_if_fail(node);
252         g_return_if_fail(node->data);
253         g_return_if_fail(store);
254
255         item = FOLDER_ITEM(node->data);
256
257         /* if parent == NULL, top level */
258         gtk_tree_store_append(store, &child, parent);
259
260         /* insert this node */
261         name = folder_item_get_name(item);
262         gtk_tree_store_set(store, &child,
263                            FOLDERSEL_FOLDERNAME, name,
264                            FOLDERSEL_FOLDERITEM, item,
265                            FOLDERSEL_FOLDERPIXBUF, folder_pixbuf,
266                            FOLDERSEL_FOLDEREXPANDER, node->children ? TRUE : FALSE,
267                            -1);
268         g_free(name);
269         
270         /* insert its children (this node as parent) */
271         for (iter = node->children; iter != NULL; iter = iter->next)
272                 foldersel_insert_gnode_in_store(store, iter, &child);
273         
274 }
275
276 static void foldersel_update_tree_store(Folder *cur_folder, FolderSelectionType type)
277 {
278         GList *list;
279
280         gtk_tree_store_clear(tree_store);               
281
282         /* insert folder data */
283         for (list = folder_get_list(); list != NULL; list = list->next) {
284                 Folder *folder;
285
286                 folder = FOLDER(list->data);
287                 g_return_if_fail(folder);
288                 if (type != FOLDER_SEL_ALL) {
289                         if (FOLDER_TYPE(folder) == F_NEWS)
290                                 continue;
291                 }
292
293                 foldersel_insert_gnode_in_store(tree_store, folder->node, NULL);
294         }
295
296         /* sort */
297         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(tree_store), 
298                                              FOLDERSEL_FOLDERNAME,
299                                              GTK_SORT_ASCENDING);
300 }
301
302 static void foldersel_create(void)
303 {
304         GtkWidget *vbox;
305         GtkWidget *scrolledwin;
306         GtkWidget *confirm_area;
307         GtkTreeViewColumn *column;
308         GtkCellRenderer *renderer;
309         GtkTreeSelection *selector;
310
311         /* create and initialize tree store */
312         tree_store = gtk_tree_store_new(N_FOLDERSEL_COLUMNS,
313                                         G_TYPE_STRING,
314                                         G_TYPE_POINTER,
315                                         GDK_TYPE_PIXBUF,
316                                         G_TYPE_BOOLEAN,
317                                         -1);
318
319         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(tree_store),
320                                         FOLDERSEL_FOLDERNAME,   
321                                         foldersel_folder_name_compare, 
322                                         NULL, NULL);
323
324         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
325         gtk_window_set_title(GTK_WINDOW(window), _("Select folder"));
326         gtk_container_set_border_width(GTK_CONTAINER(window), 4);
327         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
328         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
329         gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
330         g_signal_connect(G_OBJECT(window), "delete_event",
331                          G_CALLBACK(delete_event), NULL);
332         g_signal_connect(G_OBJECT(window), "key_press_event",
333                          G_CALLBACK(key_pressed), NULL);
334         MANAGE_WINDOW_SIGNALS_CONNECT(window);
335
336         vbox = gtk_vbox_new(FALSE, 4);
337         gtk_container_add(GTK_CONTAINER(window), vbox);
338
339         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
340         gtk_widget_set_size_request(window, 300, 360);
341         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
342                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
343         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
344
345         tree_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model
346                 (GTK_TREE_MODEL(tree_store)));
347         gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(scrolledwin),
348                                             gtk_tree_view_get_vadjustment
349                                                 (tree_view));
350         gtk_tree_view_set_headers_visible(tree_view, FALSE);
351         gtk_tree_view_set_rules_hint(tree_view, TRUE);                                         
352         
353         selector = gtk_tree_view_get_selection(tree_view);
354         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
355         gtk_tree_selection_set_select_function(selector, foldersel_selected,
356                                                NULL, NULL);
357
358         gtk_widget_show(GTK_WIDGET(tree_view));
359
360         /* now safe to initialize images for tree view */
361         foldersel_init_tree_view_images();
362         
363         gtk_container_add(GTK_CONTAINER(scrolledwin), GTK_WIDGET(tree_view));
364         
365         column = gtk_tree_view_column_new();
366
367         renderer = gtk_cell_renderer_pixbuf_new();
368         gtk_tree_view_column_pack_start(column, renderer, FALSE);
369
370         /* tell renderer to act as an expander and use the
371          * appropriate image if it's not an expander (i.e.
372          * a folder without children) */
373         g_object_set(renderer,
374                      "pixbuf-expander-open", folderopen_pixbuf,
375                      "pixbuf-expander-closed", folder_pixbuf,
376                      NULL);
377         gtk_tree_view_column_set_attributes(column, renderer,
378                    "is-expander", FOLDERSEL_FOLDEREXPANDER,
379                    "pixbuf",      FOLDERSEL_FOLDERPIXBUF,
380                    NULL);
381                              
382         /* create text renderer */
383         renderer = gtk_cell_renderer_text_new();
384         gtk_tree_view_column_pack_start(column, renderer, TRUE);
385         gtk_tree_view_column_set_attributes(column, renderer,
386                                             "text", FOLDERSEL_FOLDERNAME,
387                                             NULL);
388         
389         gtk_tree_view_append_column(tree_view, column);
390                                             
391         entry = gtk_entry_new();
392         gtk_entry_set_editable(GTK_ENTRY(entry), FALSE);
393         gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
394         g_signal_connect(G_OBJECT(entry), "activate",
395                          G_CALLBACK(foldersel_activated), NULL);
396
397         gtkut_button_set_create_stock(&confirm_area,
398                                       &ok_button,       GTK_STOCK_OK,
399                                       &cancel_button,   GTK_STOCK_CANCEL,
400                                       NULL,     NULL);
401
402         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
403         gtk_widget_grab_default(ok_button);
404
405         g_signal_connect(G_OBJECT(ok_button), "clicked",
406                          G_CALLBACK(foldersel_ok), NULL);
407         g_signal_connect(G_OBJECT(cancel_button), "clicked",
408                          G_CALLBACK(foldersel_cancel), NULL);
409
410         gtk_widget_show_all(window);
411 }
412
413 static void foldersel_init_tree_view_images(void)
414 {
415         stock_pixbuf_gdk(GTK_WIDGET(tree_view), STOCK_PIXMAP_DIR_CLOSE,
416                          &folder_pixbuf);
417         stock_pixbuf_gdk(GTK_WIDGET(tree_view), STOCK_PIXMAP_DIR_OPEN,
418                          &folderopen_pixbuf);
419 }
420
421 static gboolean foldersel_selected(GtkTreeSelection *selector,
422                                    GtkTreeModel *model, 
423                                    GtkTreePath *path,
424                                    gboolean currently_selected,
425                                    gpointer data)
426 {
427         GtkTreeIter iter;
428         FolderItem *item = NULL;
429
430         if (currently_selected)
431                 return TRUE;
432         
433         if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
434                 return TRUE;
435
436         gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter, 
437                            FOLDERSEL_FOLDERITEM, &item,
438                            -1);
439         
440         selected_item = item;
441         if (selected_item && selected_item->path) {
442                 gchar *id;
443                 id = folder_item_get_identifier(selected_item);
444                 gtk_entry_set_text(GTK_ENTRY(entry), id);
445                 g_free(id);
446         } else
447                 gtk_entry_set_text(GTK_ENTRY(entry), "");
448         
449         return TRUE;
450 }
451
452 static void foldersel_ok(GtkButton *button, gpointer data)
453 {
454         finished = TRUE;
455 }
456
457 static void foldersel_cancel(GtkButton *button, gpointer data)
458 {
459         cancelled = TRUE;
460         finished = TRUE;
461 }
462
463 static void foldersel_activated(void)
464 {
465         gtk_button_clicked(GTK_BUTTON(ok_button));
466 }
467
468 static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
469 {
470         foldersel_cancel(NULL, NULL);
471         return TRUE;
472 }
473
474 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
475 {
476         if (event && event->keyval == GDK_Escape)
477                 foldersel_cancel(NULL, NULL);
478         return FALSE;
479 }
480