new newsgroups dialog box
[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/gtkclist.h>
38 #include <gtk/gtkctree.h>
39 #include <gtk/gtkscrolledwindow.h>
40 #include <gtk/gtkbutton.h>
41 #include <gtk/gtkhbbox.h>
42 #include <string.h>
43 #include <fnmatch.h>
44
45 #include "intl.h"
46 #include "grouplistdialog.h"
47 #include "manage_window.h"
48 #include "gtkutils.h"
49 #include "utils.h"
50 #include "news.h"
51 #include "folder.h"
52 #include "alertpanel.h"
53 #include "recv.h"
54 #include "socket.h"
55
56 #define GROUPLIST_DIALOG_WIDTH  500
57 #define GROUPLIST_NAMES         250
58 #define GROUPLIST_DIALOG_HEIGHT 400
59
60 static GList * subscribed = NULL;
61 gboolean dont_unsubscribed = FALSE;
62
63 static gboolean ack;
64 static gboolean locked;
65
66 static GtkWidget *dialog;
67 static GtkWidget *entry;
68 // static GtkWidget *clist;
69 static GtkWidget *groups_tree;
70 static GtkWidget *status_label;
71 static GtkWidget *ok_button;
72 static Folder *news_folder;
73
74 static void grouplist_dialog_create     (void);
75 static void grouplist_dialog_set_list   (gchar * pattern);
76 static void grouplist_clear             (void);
77 static void grouplist_recv_func         (SockInfo       *sock,
78                                          gint            count,
79                                          gint            read_bytes,
80                                          gpointer        data);
81
82 static void ok_clicked          (GtkWidget      *widget,
83                                  gpointer        data);
84 static void cancel_clicked      (GtkWidget      *widget,
85                                  gpointer        data);
86 static void refresh_clicked     (GtkWidget      *widget,
87                                  gpointer        data);
88 static void key_pressed         (GtkWidget      *widget,
89                                  GdkEventKey    *event,
90                                  gpointer        data);
91 static void groups_tree_selected        (GtkCTree       *groups_tree,
92                                          GtkCTreeNode   * node,
93                                          gint            column,
94                                          GdkEventButton *event,
95                                          gpointer        user_data);
96 static void groups_tree_unselected      (GtkCTree       *groups_tree,
97                                          GtkCTreeNode   * node,
98                                          gint            column,
99                                          GdkEventButton *event,
100                                          gpointer        user_data);
101 static void entry_activated     (GtkEditable    *editable);
102
103 GList *grouplist_dialog(Folder *folder, GList * cur_subscriptions)
104 {
105         gchar *str;
106         GList * l;
107
108         if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL;
109
110         if (!dialog)
111                 grouplist_dialog_create();
112
113         news_folder = folder;
114
115         gtk_widget_show(dialog);
116         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
117         manage_window_set_transient(GTK_WINDOW(dialog));
118         GTK_EVENTS_FLUSH();
119
120         subscribed = NULL;
121
122         for(l = cur_subscriptions ; l != NULL ; l = l->next)
123           subscribed = g_list_append(subscribed, g_strdup((gchar *) l->data));
124
125         grouplist_dialog_set_list(NULL);
126
127         gtk_main();
128
129         manage_window_focus_out(dialog, NULL, NULL);
130         gtk_widget_hide(dialog);
131
132         if (!ack) {
133           list_free_strings(subscribed);
134           g_list_free(subscribed);
135
136           subscribed = NULL;
137           
138           for(l = cur_subscriptions ; l != NULL ; l = l->next)
139             subscribed = g_list_append(subscribed, g_strdup((gchar *) l->data));
140         }
141
142         GTK_EVENTS_FLUSH();
143
144         debug_print("return string = %s\n", str ? str : "(none)");
145         return subscribed;
146 }
147
148 static void grouplist_clear(void)
149 {
150         dont_unsubscribed = TRUE;
151         gtk_clist_clear(GTK_CLIST(groups_tree));
152         gtk_entry_set_text(GTK_ENTRY(entry), "");
153         dont_unsubscribed = FALSE;
154 }
155
156 static void grouplist_dialog_create(void)
157 {
158         GtkWidget *vbox;
159         GtkWidget *hbox;
160         GtkWidget *msg_label;
161         GtkWidget *confirm_area;
162         GtkWidget *cancel_button;       
163         GtkWidget *refresh_button;      
164         GtkWidget *scrolledwin;
165         gchar * col_names[3] = {
166                 _("name"), _("count of messages"), _("type")
167         };
168
169         dialog = gtk_dialog_new();
170         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, TRUE, FALSE);
171         gtk_widget_set_usize(dialog,
172                              GROUPLIST_DIALOG_WIDTH, GROUPLIST_DIALOG_HEIGHT);
173         gtk_container_set_border_width
174                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
175         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
176         gtk_window_set_title(GTK_WINDOW(dialog), _("Subscribe to newsgroup"));
177         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
178                            GTK_SIGNAL_FUNC(cancel_clicked), NULL);
179         gtk_signal_connect(GTK_OBJECT(dialog), "key_press_event",
180                            GTK_SIGNAL_FUNC(key_pressed), NULL);
181         gtk_signal_connect(GTK_OBJECT(dialog), "focus_in_event",
182                            GTK_SIGNAL_FUNC(manage_window_focus_in), NULL);
183         gtk_signal_connect(GTK_OBJECT(dialog), "focus_out_event",
184                            GTK_SIGNAL_FUNC(manage_window_focus_out), NULL);
185
186         gtk_widget_realize(dialog);
187
188         vbox = gtk_vbox_new(FALSE, 8);
189         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
190         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
191
192         hbox = gtk_hbox_new(FALSE, 0);
193         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
194
195         msg_label = gtk_label_new(_("Input subscribing newsgroup:"));
196         gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0);
197
198         entry = gtk_entry_new();
199         gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
200         gtk_signal_connect(GTK_OBJECT(entry), "activate",
201                            GTK_SIGNAL_FUNC(entry_activated), NULL);
202
203         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
204         gtk_box_pack_start(GTK_BOX (vbox), scrolledwin, TRUE, TRUE, 0);
205         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwin),
206                                        GTK_POLICY_AUTOMATIC,
207                                        GTK_POLICY_AUTOMATIC);
208
209         groups_tree = gtk_ctree_new_with_titles(3, 0, col_names);
210         gtk_container_add(GTK_CONTAINER(scrolledwin), groups_tree);
211         gtk_clist_set_selection_mode(GTK_CLIST(groups_tree),
212                                      GTK_SELECTION_MULTIPLE);
213         /*
214         GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[0].button,
215                                GTK_CAN_FOCUS);
216         */
217         gtk_signal_connect(GTK_OBJECT(groups_tree), "tree_select_row",
218                            GTK_SIGNAL_FUNC(groups_tree_selected), NULL);
219         gtk_signal_connect(GTK_OBJECT(groups_tree), "tree_unselect_row",
220                            GTK_SIGNAL_FUNC(groups_tree_unselected), NULL);
221
222         hbox = gtk_hbox_new(FALSE, 0);
223         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
224
225         status_label = gtk_label_new("");
226         gtk_box_pack_start(GTK_BOX(hbox), status_label, FALSE, FALSE, 0);
227
228         gtkut_button_set_create(&confirm_area,
229                                 &ok_button,      _("OK"),
230                                 &cancel_button,  _("Cancel"),
231                                 &refresh_button, _("Refresh"));
232         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
233                           confirm_area);
234         gtk_widget_grab_default(ok_button);
235
236         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
237                            GTK_SIGNAL_FUNC(ok_clicked), NULL);
238         gtk_signal_connect(GTK_OBJECT(cancel_button), "clicked",
239                            GTK_SIGNAL_FUNC(cancel_clicked), NULL);
240         gtk_signal_connect(GTK_OBJECT(refresh_button), "clicked",
241                            GTK_SIGNAL_FUNC(refresh_clicked), NULL);
242
243         gtk_widget_show_all(GTK_DIALOG(dialog)->vbox);
244 }
245
246 static GHashTable * hash_last_node;
247 static GHashTable * hash_branch_node;
248
249 static void hash_news_init()
250 {
251         hash_last_node = g_hash_table_new(g_str_hash, g_str_equal);
252         hash_branch_node = g_hash_table_new(g_str_hash, g_str_equal);
253 }
254
255 static void free_key(gchar * key, void * value, void * user_data)
256 {
257         g_free(key);
258 }
259
260 static void hash_news_done()
261 {
262         g_hash_table_foreach(hash_branch_node, free_key, NULL);
263         g_hash_table_foreach(hash_last_node, free_key, NULL);
264
265         g_hash_table_destroy(hash_branch_node);
266         g_hash_table_destroy(hash_last_node);
267 }
268
269 static GtkCTreeNode * hash_news_get_branch_node(gchar * name)
270 {
271         return g_hash_table_lookup(hash_branch_node, name);
272 }
273
274 static void hash_news_set_branch_node(gchar * name, GtkCTreeNode * node)
275 {
276         g_hash_table_insert(hash_branch_node, g_strdup(name), node);
277 }
278
279 static GtkCTreeNode * hash_news_get_last_node(gchar * name)
280 {
281         return g_hash_table_lookup(hash_last_node, name);
282 }
283
284 static void hash_news_set_last_node(gchar * name, GtkCTreeNode * node)
285 {
286         gchar * key;
287         void * value;
288
289         if (g_hash_table_lookup_extended(hash_last_node, name,
290                                          (void *) &key, &value)) {
291                 g_hash_table_remove(hash_last_node, name);
292                 g_free(key);
293         }
294
295         g_hash_table_insert(hash_last_node, g_strdup(name), node);
296 }
297
298 static gchar * get_parent_name(gchar * name)
299 {
300         gchar * p;
301
302         p = (gchar *) strrchr(name, '.');
303         if (p == NULL)
304                 return g_strdup("");
305
306         return g_strndup(name, p - name);
307 }
308
309 static GtkCTreeNode * create_parent(GtkCTree * groups_tree, gchar * name)
310 {
311         gchar * cols[3];
312         GtkCTreeNode * parent;
313         GtkCTreeNode * node;
314         gchar * parent_name;
315
316         if (* name == 0)
317                 return;
318
319         if (hash_news_get_branch_node(name) != NULL)
320                 return;
321
322         cols[0] = name;
323         cols[1] = "";
324         cols[2] = "";
325         
326         parent_name = get_parent_name(name);
327         create_parent(groups_tree, parent_name);
328
329         parent = hash_news_get_branch_node(parent_name);
330         node = hash_news_get_last_node(parent_name);
331         node = gtk_ctree_insert_node(groups_tree,
332                                      parent, node,
333                                      cols, 0, NULL, NULL,
334                                      NULL, NULL,
335                                      FALSE, FALSE);
336         gtk_ctree_node_set_selectable(groups_tree, node, FALSE);
337         hash_news_set_last_node(parent_name, node);
338         hash_news_set_branch_node(name, node);
339
340         g_free(parent_name);
341 }
342
343 static GtkCTreeNode * create_branch(GtkCTree * groups_tree, gchar * name,
344                                     struct NNTPGroupInfo * info)
345 {
346         gchar * parent_name;
347
348         GtkCTreeNode * node;
349         GtkCTreeNode * parent;
350
351         gchar count_str[10];
352         gchar * cols[3];
353         gint count;
354
355         count = info->last - info->first;
356         if (count < 0)
357                 count = 0;
358         snprintf(count_str, 10, "%i", count);
359         
360         cols[0] = info->name;
361         cols[1] = count_str;
362         if (info->type == 'y')
363                 cols[2] = "";
364         else if (info->type == 'm')
365                 cols[2] = "moderated";
366         else if (info->type == 'n')
367                 cols[2] = "readonly";
368         else
369                 cols[2] = "unkown";
370         
371         parent_name = get_parent_name(name);
372
373         create_parent(groups_tree, parent_name);
374
375         parent = hash_news_get_branch_node(parent_name);
376         node = hash_news_get_last_node(parent_name);
377         node = gtk_ctree_insert_node(groups_tree,
378                                      parent, node,
379                                      cols, 0, NULL, NULL,
380                                      NULL, NULL,
381                                      TRUE, FALSE);
382         gtk_ctree_node_set_selectable(groups_tree, node, TRUE);
383         hash_news_set_last_node(parent_name, node);
384
385         g_free(parent_name);
386
387         if (node == NULL)
388                 return NULL;
389
390         gtk_ctree_node_set_row_data(GTK_CTREE(groups_tree), node, info);
391
392         return node;
393 }
394
395 static void grouplist_dialog_set_list(gchar * pattern)
396 {
397         GSList *cur;
398         GtkCTreeNode * node;
399         GSList * group_list = NULL;
400         GSList * r_list;
401
402         if (pattern == NULL)
403                 pattern = "*";
404
405         if (locked) return;
406         locked = TRUE;
407
408         grouplist_clear();
409
410         recv_set_ui_func(grouplist_recv_func, NULL);
411         group_list = news_get_group_list(news_folder);
412         recv_set_ui_func(NULL, NULL);
413         if (group_list == NULL) {
414                 alertpanel_error(_("Can't retrieve newsgroup list."));
415                 locked = FALSE;
416                 return;
417         }
418
419         dont_unsubscribed = TRUE;
420
421         hash_news_init();
422
423         gtk_clist_freeze(GTK_CLIST(groups_tree));
424
425         r_list = g_slist_copy(group_list);
426         r_list = g_slist_reverse(r_list);
427
428         for (cur = r_list; cur != NULL ; cur = cur->next) {
429                 struct NNTPGroupInfo * info;
430
431                 info = (struct NNTPGroupInfo *) cur->data;
432
433                 if (fnmatch(pattern, info->name, 0) == 0) {
434                         GList * l;
435
436                         node = create_branch(GTK_CTREE(groups_tree),
437                                              info->name, info);
438
439                         l = g_list_find_custom(subscribed, info->name,
440                                                (GCompareFunc) g_strcasecmp);
441                         
442                         if (l != NULL)
443                           gtk_ctree_select(GTK_CTREE(groups_tree), node);
444                 }
445         }
446
447         g_slist_free(r_list);
448
449         hash_news_done();
450
451         node = gtk_ctree_node_nth(GTK_CTREE(groups_tree), 0);
452         gtk_ctree_node_moveto(GTK_CTREE(groups_tree), node, 0, 0, 0);
453
454         dont_unsubscribed = FALSE;
455
456         gtk_clist_set_column_width(GTK_CLIST(groups_tree), 0, GROUPLIST_NAMES);
457         gtk_clist_set_column_justification (GTK_CLIST(groups_tree), 1,
458                                             GTK_JUSTIFY_RIGHT);
459         gtk_clist_set_column_auto_resize(GTK_CLIST(groups_tree), 1, TRUE);
460         gtk_clist_set_column_auto_resize(GTK_CLIST(groups_tree), 2, TRUE);
461
462         gtk_clist_thaw(GTK_CLIST(groups_tree));
463
464         gtk_widget_grab_focus(ok_button);
465         gtk_widget_grab_focus(groups_tree);
466
467         gtk_label_set_text(GTK_LABEL(status_label), _("Done."));
468
469         locked = FALSE;
470 }
471
472 static void grouplist_recv_func(SockInfo *sock, gint count, gint read_bytes,
473                                 gpointer data)
474 {
475         gchar buf[BUFFSIZE];
476
477         g_snprintf(buf, sizeof(buf),
478                    _("%d newsgroups received (%s read)"),
479                    count, to_human_readable(read_bytes));
480         gtk_label_set_text(GTK_LABEL(status_label), buf);
481         GTK_EVENTS_FLUSH();
482 }
483
484 static void ok_clicked(GtkWidget *widget, gpointer data)
485 {
486         gchar * str;
487         gboolean update_list;
488
489         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
490
491         update_list = FALSE;
492
493         if (strchr(str, '*') != NULL)
494           update_list = TRUE;
495
496         if (update_list) {
497                 grouplist_dialog_set_list(str);
498                 g_free(str);
499         }
500         else {
501                 g_free(str);
502                 ack = TRUE;
503                 if (gtk_main_level() > 1)
504                         gtk_main_quit();
505         }
506 }
507
508 static void cancel_clicked(GtkWidget *widget, gpointer data)
509 {
510         ack = FALSE;
511         if (gtk_main_level() > 1)
512                 gtk_main_quit();
513 }
514
515 static void refresh_clicked(GtkWidget *widget, gpointer data)
516 {
517         gchar * str;
518  
519         if (locked) return;
520
521         news_cancel_group_list_cache(news_folder);
522
523         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
524         grouplist_dialog_set_list(str);
525         g_free(str);
526 }
527
528 static void key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
529 {
530         if (event && event->keyval == GDK_Escape)
531                 cancel_clicked(NULL, NULL);
532 }
533
534 static void groups_tree_selected(GtkCTree *groups_tree, GtkCTreeNode * node,
535                                  gint column,
536                                  GdkEventButton *event, gpointer user_data)
537 {
538         struct NNTPGroupInfo * group;
539         GList * l;
540
541         group = (struct NNTPGroupInfo *)
542           gtk_ctree_node_get_row_data(GTK_CTREE(groups_tree), node);
543
544         if (group == NULL)
545                 return;
546
547         if (!dont_unsubscribed) {
548                 subscribed = g_list_append(subscribed, g_strdup(group->name));
549         }
550 }
551
552 static void groups_tree_unselected(GtkCTree *groups_tree, GtkCTreeNode * node,
553                                    gint column,
554                                    GdkEventButton *event, gpointer user_data)
555 {
556         struct NNTPGroupInfo * group;
557         GList * l;
558
559         group = (struct NNTPGroupInfo *)
560           gtk_ctree_node_get_row_data(GTK_CTREE(groups_tree), node);
561
562         if (group == NULL)
563                 return;
564
565         if (!dont_unsubscribed) {
566                 l = g_list_find_custom(subscribed, group->name,
567                                        (GCompareFunc) g_strcasecmp);
568                 if (l != NULL) {
569                   g_free(l->data);
570                         subscribed = g_list_remove(subscribed, l->data);
571                 }
572         }
573 }
574
575 static void entry_activated(GtkEditable *editable)
576 {
577         gchar * str;
578         gboolean update_list;
579
580         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
581
582         update_list = FALSE;
583
584         if (strchr(str, '*') != NULL)
585           update_list = TRUE;
586
587         if (update_list) {
588                 grouplist_dialog_set_list(str);
589                 g_free(str);
590         }
591         else {
592                 g_free(str);
593                 ok_clicked(NULL, NULL);
594         }
595 }