cc90abe5d5263811ae3791142fa538d7c619c0a6
[claws.git] / src / grouplistdialog.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtkmain.h>
29 #include <gtk/gtkwidget.h>
30 #include <gtk/gtkdialog.h>
31 #include <gtk/gtkwindow.h>
32 #include <gtk/gtksignal.h>
33 #include <gtk/gtkvbox.h>
34 #include <gtk/gtkhbox.h>
35 #include <gtk/gtklabel.h>
36 #include <gtk/gtkentry.h>
37 #include <gtk/gtkctree.h>
38 #include <gtk/gtkscrolledwindow.h>
39 #include <gtk/gtkbutton.h>
40 #include <gtk/gtkhbbox.h>
41 #include <string.h>
42 #include <fnmatch.h>
43
44 #include "intl.h"
45 #include "grouplistdialog.h"
46 #include "manage_window.h"
47 #include "gtkutils.h"
48 #include "utils.h"
49 #include "news.h"
50 #include "folder.h"
51 #include "alertpanel.h"
52 #include "recv.h"
53 #include "socket.h"
54
55 #define GROUPLIST_DIALOG_WIDTH          480
56 #define GROUPLIST_DIALOG_HEIGHT         400
57 #define GROUPLIST_COL_NAME_WIDTH        250
58
59 static gboolean ack;
60 static gboolean locked;
61
62 static GtkWidget *dialog;
63 static GtkWidget *entry;
64 static GtkWidget *ctree;
65 static GtkWidget *status_label;
66 static GtkWidget *ok_button;
67 static GSList *group_list;
68 static Folder *news_folder;
69
70 static GSList *subscribed;
71
72 static void grouplist_dialog_create     (void);
73 static void grouplist_dialog_set_list   (gchar          *pattern);
74 static void grouplist_clear             (void);
75 static void grouplist_recv_func         (SockInfo       *sock,
76                                          gint            count,
77                                          gint            read_bytes,
78                                          gpointer        data);
79
80 static void ok_clicked          (GtkWidget      *widget,
81                                  gpointer        data);
82 static void cancel_clicked      (GtkWidget      *widget,
83                                  gpointer        data);
84 static void refresh_clicked     (GtkWidget      *widget,
85                                  gpointer        data);
86 static void key_pressed         (GtkWidget      *widget,
87                                  GdkEventKey    *event,
88                                  gpointer        data);
89 static void ctree_selected      (GtkCTree       *ctree,
90                                  GtkCTreeNode   *node,
91                                  gint            column,
92                                  gpointer        data);
93 static void ctree_unselected    (GtkCTree       *ctree,
94                                  GtkCTreeNode   *node,
95                                  gint            column,
96                                  gpointer        data);
97 static void entry_activated     (GtkEditable    *editable);
98
99 GSList *grouplist_dialog(Folder *folder)
100 {
101         GNode *node;
102         FolderItem *item;
103
104         if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL;
105
106         if (!dialog)
107                 grouplist_dialog_create();
108
109         news_folder = folder;
110
111         gtk_widget_show(dialog);
112         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
113         manage_window_set_transient(GTK_WINDOW(dialog));
114         gtk_widget_grab_focus(ok_button);
115         gtk_widget_grab_focus(ctree);
116         GTK_EVENTS_FLUSH();
117
118         subscribed = NULL;
119         for (node = folder->node->children; node != NULL; node = node->next) {
120                 item = FOLDER_ITEM(node->data);
121                 subscribed = g_slist_append(subscribed, g_strdup(item->name));
122         }
123
124         grouplist_dialog_set_list(NULL);
125
126         gtk_main();
127
128         manage_window_focus_out(dialog, NULL, NULL);
129         gtk_widget_hide(dialog);
130
131         if (!ack) {
132                 slist_free_strings(subscribed);
133                 g_slist_free(subscribed);
134                 subscribed = NULL;
135
136                 for (node = folder->node->children; node != NULL;
137                      node = node->next) {
138                         item = FOLDER_ITEM(node->data);
139                         subscribed = g_slist_append(subscribed,
140                                                     g_strdup(item->name));
141                 }
142         }
143
144         grouplist_clear();
145
146         return subscribed;
147 }
148
149 static void grouplist_dialog_create(void)
150 {
151         GtkWidget *vbox;
152         GtkWidget *hbox;
153         GtkWidget *msg_label;
154         GtkWidget *confirm_area;
155         GtkWidget *cancel_button;       
156         GtkWidget *refresh_button;      
157         GtkWidget *scrolledwin;
158         gchar *titles[3];
159         gint i;
160
161         dialog = gtk_dialog_new();
162         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, TRUE, FALSE);
163         gtk_widget_set_usize(dialog,
164                              GROUPLIST_DIALOG_WIDTH, GROUPLIST_DIALOG_HEIGHT);
165         gtk_container_set_border_width
166                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
167         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
168         gtk_window_set_title(GTK_WINDOW(dialog), _("Subscribe to newsgroup"));
169         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
170                            GTK_SIGNAL_FUNC(cancel_clicked), NULL);
171         gtk_signal_connect(GTK_OBJECT(dialog), "key_press_event",
172                            GTK_SIGNAL_FUNC(key_pressed), NULL);
173         gtk_signal_connect(GTK_OBJECT(dialog), "focus_in_event",
174                            GTK_SIGNAL_FUNC(manage_window_focus_in), NULL);
175         gtk_signal_connect(GTK_OBJECT(dialog), "focus_out_event",
176                            GTK_SIGNAL_FUNC(manage_window_focus_out), NULL);
177
178         gtk_widget_realize(dialog);
179
180         vbox = gtk_vbox_new(FALSE, 8);
181         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
182         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
183
184         hbox = gtk_hbox_new(FALSE, 0);
185         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
186
187         msg_label = gtk_label_new(_("Input subscribing newsgroup:"));
188         gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0);
189
190         entry = gtk_entry_new();
191         gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
192         gtk_signal_connect(GTK_OBJECT(entry), "activate",
193                            GTK_SIGNAL_FUNC(entry_activated), NULL);
194
195         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
196         gtk_box_pack_start(GTK_BOX (vbox), scrolledwin, TRUE, TRUE, 0);
197         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwin),
198                                        GTK_POLICY_AUTOMATIC,
199                                        GTK_POLICY_AUTOMATIC);
200
201         titles[0] = _("Newsgroup name");
202         titles[1] = _("Messages");
203         titles[2] = _("Type");
204         ctree = gtk_ctree_new_with_titles(3, 0, titles);
205         gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
206         gtk_clist_set_column_width
207                 (GTK_CLIST(ctree), 0, GROUPLIST_COL_NAME_WIDTH);
208         gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_MULTIPLE);
209         gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED);
210         gtk_ctree_set_expander_style(GTK_CTREE(ctree),
211                                      GTK_CTREE_EXPANDER_SQUARE);
212         for (i = 0; i < 3; i++)
213                 GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button,
214                                        GTK_CAN_FOCUS);
215         gtk_signal_connect(GTK_OBJECT(ctree), "tree_select_row",
216                            GTK_SIGNAL_FUNC(ctree_selected), NULL);
217         gtk_signal_connect(GTK_OBJECT(ctree), "tree_unselect_row",
218                            GTK_SIGNAL_FUNC(ctree_unselected), NULL);
219
220         hbox = gtk_hbox_new(FALSE, 0);
221         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
222
223         status_label = gtk_label_new("");
224         gtk_box_pack_start(GTK_BOX(hbox), status_label, FALSE, FALSE, 0);
225
226         gtkut_button_set_create(&confirm_area,
227                                 &ok_button,      _("OK"),
228                                 &cancel_button,  _("Cancel"),
229                                 &refresh_button, _("Refresh"));
230         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
231                           confirm_area);
232         gtk_widget_grab_default(ok_button);
233
234         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
235                            GTK_SIGNAL_FUNC(ok_clicked), NULL);
236         gtk_signal_connect(GTK_OBJECT(cancel_button), "clicked",
237                            GTK_SIGNAL_FUNC(cancel_clicked), NULL);
238         gtk_signal_connect(GTK_OBJECT(refresh_button), "clicked",
239                            GTK_SIGNAL_FUNC(refresh_clicked), NULL);
240
241         gtk_widget_show_all(GTK_DIALOG(dialog)->vbox);
242 }
243
244 static GHashTable *last_node_table;
245 static GHashTable *branch_node_table;
246
247 static void grouplist_hash_init(void)
248 {
249         last_node_table = g_hash_table_new(g_str_hash, g_str_equal);
250         branch_node_table = g_hash_table_new(g_str_hash, g_str_equal);
251 }
252
253 static void grouplist_hash_done(void)
254 {
255         hash_free_strings(branch_node_table);
256         hash_free_strings(last_node_table);
257
258         g_hash_table_destroy(branch_node_table);
259         g_hash_table_destroy(last_node_table);
260 }
261
262 static GtkCTreeNode *grouplist_hash_get_branch_node(const gchar *name)
263 {
264         return g_hash_table_lookup(branch_node_table, name);
265 }
266
267 static void grouplist_hash_set_branch_node(const gchar *name,
268                                            GtkCTreeNode *node)
269 {
270         g_hash_table_insert(branch_node_table, g_strdup(name), node);
271 }
272
273 static GtkCTreeNode *grouplist_hash_get_last_node(const gchar *name)
274 {
275         return g_hash_table_lookup(last_node_table, name);
276 }
277
278 static void grouplist_hash_set_last_node(const gchar *name,
279                                          GtkCTreeNode *node)
280 {
281         gchar *key;
282         gpointer value;
283
284         if (g_hash_table_lookup_extended(last_node_table, name,
285                                          (gpointer *)&key, &value)) {
286                 g_hash_table_remove(last_node_table, name);
287                 g_free(key);
288         }
289
290         g_hash_table_insert(last_node_table, g_strdup(name), node);
291 }
292
293 static gchar *grouplist_get_parent_name(const gchar *name)
294 {
295         gchar *p;
296
297         p = strrchr(name, '.');
298         if (!p)
299                 return g_strdup("");
300         return g_strndup(name, p - name);
301 }
302
303 static GtkCTreeNode *grouplist_create_parent(const gchar *name)
304 {
305         GtkCTreeNode *parent;
306         GtkCTreeNode *node;
307         gchar *cols[3];
308         gchar *parent_name;
309
310         if (*name == '\0') return NULL;
311         node = grouplist_hash_get_branch_node(name);
312         if (node != NULL) return node;
313
314         cols[0] = (gchar *)name;
315         cols[1] = cols[2] = "";
316
317         parent_name = grouplist_get_parent_name(name);
318         parent = grouplist_create_parent(parent_name);
319
320         node = grouplist_hash_get_last_node(parent_name);
321         node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node,
322                                      cols, 0, NULL, NULL, NULL, NULL,
323                                      FALSE, FALSE);
324         gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, FALSE);
325         grouplist_hash_set_last_node(parent_name, node);
326         grouplist_hash_set_branch_node(name, node);
327
328         g_free(parent_name);
329
330         return node;
331 }
332
333 static GtkCTreeNode *grouplist_create_branch(NewsGroupInfo *ginfo)
334 {
335         GtkCTreeNode *node;
336         GtkCTreeNode *parent;
337         gchar *name = (gchar *)ginfo->name;
338         gchar *parent_name;
339         gchar *count_str;
340         gchar *cols[3];
341         gint count;
342
343         count = ginfo->last - ginfo->first;
344         if (count < 0)
345                 count = 0;
346         count_str = itos(count);
347
348         cols[0] = ginfo->name;
349         cols[1] = count_str;
350         if (ginfo->type == 'y')
351                 cols[2] = "";
352         else if (ginfo->type == 'm')
353                 cols[2] = _("moderated");
354         else if (ginfo->type == 'n')
355                 cols[2] = _("readonly");
356         else
357                 cols[2] = _("unknown");
358
359         parent_name = grouplist_get_parent_name(name);
360         parent = grouplist_create_parent(parent_name);
361         node = grouplist_hash_get_branch_node(name);
362         if (node) {
363                 gtk_ctree_set_node_info(GTK_CTREE(ctree), node, cols[0], 0,
364                                         NULL, NULL, NULL, NULL, FALSE, FALSE);
365                 gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 1, cols[1]);
366                 gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 2, cols[2]);
367         } else {
368                 node = grouplist_hash_get_last_node(parent_name);
369                 node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node,
370                                              cols, 0, NULL, NULL, NULL, NULL,
371                                              TRUE, FALSE);
372         }
373         gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, TRUE);
374         if (node)
375                 gtk_ctree_node_set_row_data(GTK_CTREE(ctree), node, ginfo);
376
377         grouplist_hash_set_last_node(parent_name, node);
378
379         g_free(parent_name);
380
381         return node;
382 }
383
384 static void grouplist_dialog_set_list(gchar *pattern)
385 {
386         GSList *cur;
387         GtkCTreeNode *node;
388
389         if (locked) return;
390         locked = TRUE;
391
392         if (!pattern || *pattern == '\0')
393                 pattern = "*";
394
395         grouplist_clear();
396
397         recv_set_ui_func(grouplist_recv_func, NULL);
398         group_list = news_get_group_list(news_folder);
399         group_list = g_slist_reverse(group_list);
400         recv_set_ui_func(NULL, NULL);
401         if (group_list == NULL) {
402                 alertpanel_error(_("Can't retrieve newsgroup list."));
403                 locked = FALSE;
404                 return;
405         }
406
407         grouplist_hash_init();
408
409         gtk_clist_freeze(GTK_CLIST(ctree));
410
411         gtk_signal_handler_block_by_func(GTK_OBJECT(ctree),
412                                          GTK_SIGNAL_FUNC(ctree_selected),
413                                          NULL);
414
415         for (cur = group_list; cur != NULL ; cur = cur->next) {
416                 NewsGroupInfo *ginfo = (NewsGroupInfo *)cur->data;
417
418                 if (fnmatch(pattern, ginfo->name, 0) == 0) {
419                         node = grouplist_create_branch(ginfo);
420                         if (g_slist_find_custom(subscribed, ginfo->name,
421                                                 (GCompareFunc)g_strcasecmp)
422                             != NULL)
423                                 gtk_ctree_select(GTK_CTREE(ctree), node);
424                 }
425         }
426
427         gtk_signal_handler_unblock_by_func(GTK_OBJECT(ctree),
428                                            GTK_SIGNAL_FUNC(ctree_selected),
429                                            NULL);
430
431         gtk_clist_thaw(GTK_CLIST(ctree));
432
433         grouplist_hash_done();
434
435         gtk_label_set_text(GTK_LABEL(status_label), _("Done."));
436
437         locked = FALSE;
438 }
439
440 static void grouplist_clear(void)
441 {
442         gtk_signal_handler_block_by_func(GTK_OBJECT(ctree),
443                                          GTK_SIGNAL_FUNC(ctree_unselected),
444                                          NULL);
445         gtk_clist_clear(GTK_CLIST(ctree));
446         gtk_entry_set_text(GTK_ENTRY(entry), "");
447         news_group_list_free(group_list);
448         group_list = NULL;
449         gtk_signal_handler_unblock_by_func(GTK_OBJECT(ctree),
450                                            GTK_SIGNAL_FUNC(ctree_unselected),
451                                            NULL);
452 }
453
454 static void grouplist_recv_func(SockInfo *sock, gint count, gint read_bytes,
455                                 gpointer data)
456 {
457         gchar buf[BUFFSIZE];
458
459         g_snprintf(buf, sizeof(buf),
460                    _("%d newsgroups received (%s read)"),
461                    count, to_human_readable(read_bytes));
462         gtk_label_set_text(GTK_LABEL(status_label), buf);
463         GTK_EVENTS_FLUSH();
464 }
465
466 static void ok_clicked(GtkWidget *widget, gpointer data)
467 {
468         gchar *str;
469
470         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
471
472         if (strchr(str, '*') != NULL)
473                 grouplist_dialog_set_list(str);
474         else {
475                 ack = TRUE;
476                 if (gtk_main_level() > 1)
477                         gtk_main_quit();
478         }
479
480         g_free(str);
481 }
482
483 static void cancel_clicked(GtkWidget *widget, gpointer data)
484 {
485         ack = FALSE;
486         if (gtk_main_level() > 1)
487                 gtk_main_quit();
488 }
489
490 static void refresh_clicked(GtkWidget *widget, gpointer data)
491
492         gchar *str;
493
494         if (locked) return;
495
496         news_remove_group_list_cache(news_folder);
497
498         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
499         grouplist_dialog_set_list(str);
500         g_free(str);
501 }
502
503 static void key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
504 {
505         if (event && event->keyval == GDK_Escape)
506                 cancel_clicked(NULL, NULL);
507 }
508
509 static void ctree_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
510                            gpointer data)
511 {
512         NewsGroupInfo *ginfo;
513
514         ginfo = gtk_ctree_node_get_row_data(ctree, node);
515         if (!ginfo) return;
516
517         subscribed = g_slist_append(subscribed, g_strdup(ginfo->name));
518 }
519
520 static void ctree_unselected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
521                              gpointer data)
522 {
523         NewsGroupInfo *ginfo;
524         GSList *list;
525
526         ginfo = gtk_ctree_node_get_row_data(ctree, node);
527         if (!ginfo) return;
528
529         list = g_slist_find_custom(subscribed, ginfo->name,
530                                    (GCompareFunc)g_strcasecmp);
531         if (list) {
532                 g_free(list->data);
533                 subscribed = g_slist_remove(subscribed, list->data);
534         }
535 }
536
537 static void entry_activated(GtkEditable *editable)
538 {
539         gchar * str;
540         gboolean update_list;
541
542         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
543
544         update_list = FALSE;
545
546         if (strchr(str, '*') != NULL)
547           update_list = TRUE;
548
549         if (update_list) {
550                 grouplist_dialog_set_list(str);
551                 g_free(str);
552         }
553         else {
554                 g_free(str);
555                 ok_clicked(NULL, NULL);
556         }
557 }