newsgroups dialog / enhanced mailto
[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 gchar * get_node_name(gchar * name)
310 {
311         gchar * p;
312
313         p = (gchar *) strrchr(name, '.');
314         if (p == NULL)
315                 return name;
316
317         return p + 1;
318 }
319
320 static GtkCTreeNode * create_parent(GtkCTree * groups_tree, gchar * name)
321 {
322         gchar * cols[3];
323         GtkCTreeNode * parent;
324         GtkCTreeNode * node;
325         gchar * parent_name;
326
327         if (* name == 0)
328                 return;
329
330         if (hash_news_get_branch_node(name) != NULL)
331                 return;
332
333         cols[0] = get_node_name(name);
334         cols[1] = "";
335         cols[2] = "";
336         
337         parent_name = get_parent_name(name);
338         create_parent(groups_tree, parent_name);
339
340         parent = hash_news_get_branch_node(parent_name);
341         node = hash_news_get_last_node(parent_name);
342         node = gtk_ctree_insert_node(groups_tree,
343                                      parent, node,
344                                      cols, 0, NULL, NULL,
345                                      NULL, NULL,
346                                      FALSE, FALSE);
347         gtk_ctree_node_set_selectable(groups_tree, node, FALSE);
348         hash_news_set_last_node(parent_name, node);
349         hash_news_set_branch_node(name, node);
350
351         g_free(parent_name);
352 }
353
354 static GtkCTreeNode * create_branch(GtkCTree * groups_tree, gchar * name,
355                                     struct NNTPGroupInfo * info)
356 {
357         gchar * parent_name;
358
359         GtkCTreeNode * node;
360         GtkCTreeNode * parent;
361
362         gchar count_str[10];
363         gchar * cols[3];
364         gint count;
365
366         count = info->last - info->first;
367         if (count < 0)
368                 count = 0;
369         snprintf(count_str, 10, "%i", count);
370         
371         cols[0] = get_node_name(info->name);
372         cols[1] = count_str;
373         if (info->type == 'y')
374                 cols[2] = "";
375         else if (info->type == 'm')
376                 cols[2] = "moderated";
377         else if (info->type == 'n')
378                 cols[2] = "readonly";
379         else
380                 cols[2] = "unkown";
381         
382         parent_name = get_parent_name(name);
383
384         create_parent(groups_tree, parent_name);
385
386         parent = hash_news_get_branch_node(parent_name);
387         node = hash_news_get_last_node(parent_name);
388         node = gtk_ctree_insert_node(groups_tree,
389                                      parent, node,
390                                      cols, 0, NULL, NULL,
391                                      NULL, NULL,
392                                      TRUE, FALSE);
393         gtk_ctree_node_set_selectable(groups_tree, node, TRUE);
394         hash_news_set_last_node(parent_name, node);
395
396         g_free(parent_name);
397
398         if (node == NULL)
399                 return NULL;
400
401         gtk_ctree_node_set_row_data(GTK_CTREE(groups_tree), node, info);
402
403         return node;
404 }
405
406 static void grouplist_dialog_set_list(gchar * pattern)
407 {
408         GSList *cur;
409         GtkCTreeNode * node;
410         GSList * group_list = NULL;
411         GSList * r_list;
412
413         if (pattern == NULL)
414                 pattern = "*";
415
416         if (locked) return;
417         locked = TRUE;
418
419         grouplist_clear();
420
421         recv_set_ui_func(grouplist_recv_func, NULL);
422         group_list = news_get_group_list(news_folder);
423         recv_set_ui_func(NULL, NULL);
424         if (group_list == NULL) {
425                 alertpanel_error(_("Can't retrieve newsgroup list."));
426                 locked = FALSE;
427                 return;
428         }
429
430         dont_unsubscribed = TRUE;
431
432         hash_news_init();
433
434         gtk_clist_freeze(GTK_CLIST(groups_tree));
435
436         r_list = g_slist_copy(group_list);
437         r_list = g_slist_reverse(r_list);
438
439         for (cur = r_list; cur != NULL ; cur = cur->next) {
440                 struct NNTPGroupInfo * info;
441
442                 info = (struct NNTPGroupInfo *) cur->data;
443
444                 if (fnmatch(pattern, info->name, 0) == 0) {
445                         GList * l;
446
447                         node = create_branch(GTK_CTREE(groups_tree),
448                                              info->name, info);
449
450                         l = g_list_find_custom(subscribed, info->name,
451                                                (GCompareFunc) g_strcasecmp);
452                         
453                         if (l != NULL)
454                           gtk_ctree_select(GTK_CTREE(groups_tree), node);
455                 }
456         }
457
458         g_slist_free(r_list);
459
460         hash_news_done();
461
462         node = gtk_ctree_node_nth(GTK_CTREE(groups_tree), 0);
463         gtk_ctree_node_moveto(GTK_CTREE(groups_tree), node, 0, 0, 0);
464
465         dont_unsubscribed = FALSE;
466
467         gtk_clist_set_column_width(GTK_CLIST(groups_tree), 0, GROUPLIST_NAMES);
468         gtk_clist_set_column_justification (GTK_CLIST(groups_tree), 1,
469                                             GTK_JUSTIFY_RIGHT);
470         gtk_clist_set_column_auto_resize(GTK_CLIST(groups_tree), 1, TRUE);
471         gtk_clist_set_column_auto_resize(GTK_CLIST(groups_tree), 2, TRUE);
472
473         gtk_clist_thaw(GTK_CLIST(groups_tree));
474
475         gtk_widget_grab_focus(ok_button);
476         gtk_widget_grab_focus(groups_tree);
477
478         gtk_label_set_text(GTK_LABEL(status_label), _("Done."));
479
480         locked = FALSE;
481 }
482
483 static void grouplist_recv_func(SockInfo *sock, gint count, gint read_bytes,
484                                 gpointer data)
485 {
486         gchar buf[BUFFSIZE];
487
488         g_snprintf(buf, sizeof(buf),
489                    _("%d newsgroups received (%s read)"),
490                    count, to_human_readable(read_bytes));
491         gtk_label_set_text(GTK_LABEL(status_label), buf);
492         GTK_EVENTS_FLUSH();
493 }
494
495 static void ok_clicked(GtkWidget *widget, gpointer data)
496 {
497         gchar * str;
498         gboolean update_list;
499
500         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
501
502         update_list = FALSE;
503
504         if (strchr(str, '*') != NULL)
505           update_list = TRUE;
506
507         if (update_list) {
508                 grouplist_dialog_set_list(str);
509                 g_free(str);
510         }
511         else {
512                 g_free(str);
513                 ack = TRUE;
514                 if (gtk_main_level() > 1)
515                         gtk_main_quit();
516         }
517 }
518
519 static void cancel_clicked(GtkWidget *widget, gpointer data)
520 {
521         ack = FALSE;
522         if (gtk_main_level() > 1)
523                 gtk_main_quit();
524 }
525
526 static void refresh_clicked(GtkWidget *widget, gpointer data)
527 {
528         gchar * str;
529  
530         if (locked) return;
531
532         news_cancel_group_list_cache(news_folder);
533
534         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
535         grouplist_dialog_set_list(str);
536         g_free(str);
537 }
538
539 static void key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
540 {
541         if (event && event->keyval == GDK_Escape)
542                 cancel_clicked(NULL, NULL);
543 }
544
545 static void groups_tree_selected(GtkCTree *groups_tree, GtkCTreeNode * node,
546                                  gint column,
547                                  GdkEventButton *event, gpointer user_data)
548 {
549         struct NNTPGroupInfo * group;
550         GList * l;
551
552         group = (struct NNTPGroupInfo *)
553           gtk_ctree_node_get_row_data(GTK_CTREE(groups_tree), node);
554
555         if (group == NULL)
556                 return;
557
558         if (!dont_unsubscribed) {
559                 subscribed = g_list_append(subscribed, g_strdup(group->name));
560         }
561 }
562
563 static void groups_tree_unselected(GtkCTree *groups_tree, GtkCTreeNode * node,
564                                    gint column,
565                                    GdkEventButton *event, gpointer user_data)
566 {
567         struct NNTPGroupInfo * group;
568         GList * l;
569
570         group = (struct NNTPGroupInfo *)
571           gtk_ctree_node_get_row_data(GTK_CTREE(groups_tree), node);
572
573         if (group == NULL)
574                 return;
575
576         if (!dont_unsubscribed) {
577                 l = g_list_find_custom(subscribed, group->name,
578                                        (GCompareFunc) g_strcasecmp);
579                 if (l != NULL) {
580                   g_free(l->data);
581                         subscribed = g_list_remove(subscribed, l->data);
582                 }
583         }
584 }
585
586 static void entry_activated(GtkEditable *editable)
587 {
588         gchar * str;
589         gboolean update_list;
590
591         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
592
593         update_list = FALSE;
594
595         if (strchr(str, '*') != NULL)
596           update_list = TRUE;
597
598         if (update_list) {
599                 grouplist_dialog_set_list(str);
600                 g_free(str);
601         }
602         else {
603                 g_free(str);
604                 ok_clicked(NULL, NULL);
605         }
606 }