Statistics
| Revision:

root / src / grouplistdialog.c @ 31

History | View | Annotate | Download (14.9 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2005 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 <gtk/gtkstock.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                450
57
#define GROUPLIST_DIALOG_HEIGHT                400
58
#define GROUPLIST_COL_NAME_WIDTH        250
59
60
static gboolean ack;
61
static gboolean locked;
62
63
static GtkWidget *dialog;
64
static GtkWidget *entry;
65
static GtkWidget *ctree;
66
static GtkWidget *status_label;
67
static GtkWidget *ok_button;
68
static GSList *group_list;
69
static Folder *news_folder;
70
71
static GSList *subscribed;
72
73
static void grouplist_dialog_create        (void);
74
static void grouplist_dialog_set_list        (const gchar        *pattern,
75
                                         gboolean         refresh);
76
static void grouplist_search                (void);
77
static void grouplist_clear                (void);
78
static gboolean grouplist_recv_func        (SockInfo        *sock,
79
                                         gint                 count,
80
                                         gint                 read_bytes,
81
                                         gpointer         data);
82
83
static gint window_deleted        (GtkWidget        *widget,
84
                                 GdkEventAny        *event,
85
                                 gpointer         data);
86
static void ok_clicked                (GtkWidget        *widget,
87
                                 gpointer         data);
88
static void cancel_clicked        (GtkWidget        *widget,
89
                                 gpointer         data);
90
static void refresh_clicked        (GtkWidget        *widget,
91
                                 gpointer         data);
92
static gboolean key_pressed        (GtkWidget        *widget,
93
                                 GdkEventKey        *event,
94
                                 gpointer         data);
95
static void ctree_selected        (GtkCTree        *ctree,
96
                                 GtkCTreeNode        *node,
97
                                 gint                 column,
98
                                 gpointer         data);
99
static void ctree_unselected        (GtkCTree        *ctree,
100
                                 GtkCTreeNode        *node,
101
                                 gint                 column,
102
                                 gpointer         data);
103
static void entry_activated        (GtkEditable        *editable);
104
static void search_clicked        (GtkWidget        *widget,
105
                                 gpointer         data);
106
107
GSList *grouplist_dialog(Folder *folder)
108
{
109
        GNode *node;
110
        FolderItem *item;
111
112
        if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL;
113
114
        if (!dialog)
115
                grouplist_dialog_create();
116
117
        news_folder = folder;
118
119
        gtk_widget_show(dialog);
120
        gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
121
        manage_window_set_transient(GTK_WINDOW(dialog));
122
        gtk_widget_grab_focus(ok_button);
123
        gtk_widget_grab_focus(ctree);
124
        GTK_EVENTS_FLUSH();
125
126
        subscribed = NULL;
127
        for (node = folder->node->children; node != NULL; node = node->next) {
128
                item = FOLDER_ITEM(node->data);
129
                subscribed = g_slist_append(subscribed, g_strdup(item->path));
130
        }
131
132
        grouplist_dialog_set_list(NULL, TRUE);
133
134
        if (ack) gtk_main();
135
136
        manage_window_focus_out(dialog, NULL, NULL);
137
        gtk_widget_hide(dialog);
138
139
        if (!ack) {
140
                slist_free_strings(subscribed);
141
                g_slist_free(subscribed);
142
                subscribed = NULL;
143
144
                for (node = folder->node->children; node != NULL;
145
                     node = node->next) {
146
                        item = FOLDER_ITEM(node->data);
147
                        subscribed = g_slist_append(subscribed,
148
                                                    g_strdup(item->path));
149
                }
150
        }
151
152
        grouplist_clear();
153
154
        return subscribed;
155
}
156
157
static void grouplist_dialog_create(void)
158
{
159
        GtkWidget *vbox;
160
        GtkWidget *hbox;
161
        GtkWidget *msg_label;
162
        GtkWidget *search_button;
163
        GtkWidget *confirm_area;
164
        GtkWidget *cancel_button;        
165
        GtkWidget *refresh_button;        
166
        GtkWidget *scrolledwin;
167
        gchar *titles[3];
168
        gint i;
169
170
        dialog = gtk_dialog_new();
171
        gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, TRUE, FALSE);
172
        gtk_widget_set_size_request(dialog,
173
                                    GROUPLIST_DIALOG_WIDTH,
174
                                    GROUPLIST_DIALOG_HEIGHT);
175
        gtk_container_set_border_width
176
                (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
177
        gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
178
        gtk_window_set_title(GTK_WINDOW(dialog), _("Subscribe to newsgroup"));
179
        g_signal_connect(G_OBJECT(dialog), "delete_event",
180
                         G_CALLBACK(window_deleted), NULL);
181
        g_signal_connect(G_OBJECT(dialog), "key_press_event",
182
                         G_CALLBACK(key_pressed), NULL);
183
        MANAGE_WINDOW_SIGNALS_CONNECT(dialog);
184
185
        gtk_widget_realize(dialog);
186
187
        vbox = gtk_vbox_new(FALSE, 8);
188
        gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
189
        gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
190
191
        hbox = gtk_hbox_new(FALSE, 0);
192
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
193
194
        msg_label = gtk_label_new(_("Select newsgroups to subscribe."));
195
        gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0);
196
197
        hbox = gtk_hbox_new(FALSE, 8);
198
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
199
200
        msg_label = gtk_label_new(_("Find groups:"));
201
        gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0);
202
203
        entry = gtk_entry_new();
204
        gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
205
        g_signal_connect(G_OBJECT(entry), "activate",
206
                         G_CALLBACK(entry_activated), NULL);
207
208
        search_button = gtk_button_new_with_label(_(" Search "));
209
        gtk_box_pack_start(GTK_BOX(hbox), search_button, FALSE, FALSE, 0);
210
211
        g_signal_connect(G_OBJECT(search_button), "clicked",
212
                         G_CALLBACK(search_clicked), NULL);
213
214
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
215
        gtk_box_pack_start(GTK_BOX (vbox), scrolledwin, TRUE, TRUE, 0);
216
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwin),
217
                                       GTK_POLICY_AUTOMATIC,
218
                                       GTK_POLICY_AUTOMATIC);
219
220
        titles[0] = _("Newsgroup name");
221
        titles[1] = _("Messages");
222
        titles[2] = _("Type");
223
        ctree = gtk_ctree_new_with_titles(3, 0, titles);
224
        gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
225
        gtk_clist_set_column_width
226
                (GTK_CLIST(ctree), 0, GROUPLIST_COL_NAME_WIDTH);
227
        gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_MULTIPLE);
228
        gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED);
229
        gtk_ctree_set_expander_style(GTK_CTREE(ctree),
230
                                     GTK_CTREE_EXPANDER_SQUARE);
231
        for (i = 0; i < 3; i++)
232
                GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button,
233
                                       GTK_CAN_FOCUS);
234
        g_signal_connect(G_OBJECT(ctree), "tree_select_row",
235
                         G_CALLBACK(ctree_selected), NULL);
236
        g_signal_connect(G_OBJECT(ctree), "tree_unselect_row",
237
                         G_CALLBACK(ctree_unselected), NULL);
238
239
        hbox = gtk_hbox_new(FALSE, 0);
240
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
241
242
        status_label = gtk_label_new("");
243
        gtk_box_pack_start(GTK_BOX(hbox), status_label, FALSE, FALSE, 0);
244
245
        gtkut_stock_button_set_create(&confirm_area,
246
                                      &ok_button, GTK_STOCK_OK,
247
                                      &cancel_button, GTK_STOCK_CANCEL,
248
                                      &refresh_button, GTK_STOCK_REFRESH);
249
        gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
250
                          confirm_area);
251
        gtk_widget_grab_default(ok_button);
252
253
        g_signal_connect(G_OBJECT(ok_button), "clicked",
254
                         G_CALLBACK(ok_clicked), NULL);
255
        g_signal_connect(G_OBJECT(cancel_button), "clicked",
256
                         G_CALLBACK(cancel_clicked), NULL);
257
        g_signal_connect(G_OBJECT(refresh_button), "clicked",
258
                         G_CALLBACK(refresh_clicked), NULL);
259
260
        gtk_widget_show_all(GTK_DIALOG(dialog)->vbox);
261
}
262
263
static GHashTable *branch_node_table;
264
265
static void grouplist_hash_init(void)
266
{
267
        branch_node_table = g_hash_table_new(g_str_hash, g_str_equal);
268
}
269
270
static void grouplist_hash_done(void)
271
{
272
        hash_free_strings(branch_node_table);
273
        g_hash_table_destroy(branch_node_table);
274
}
275
276
static GtkCTreeNode *grouplist_hash_get_branch_node(const gchar *name)
277
{
278
        return g_hash_table_lookup(branch_node_table, name);
279
}
280
281
static void grouplist_hash_set_branch_node(const gchar *name,
282
                                           GtkCTreeNode *node)
283
{
284
        g_hash_table_insert(branch_node_table, g_strdup(name), node);
285
}
286
287
static gchar *grouplist_get_parent_name(const gchar *name)
288
{
289
        gchar *p;
290
291
        p = strrchr(name, '.');
292
        if (!p)
293
                return g_strdup("");
294
        return g_strndup(name, p - name);
295
}
296
297
static GtkCTreeNode *grouplist_create_parent(const gchar *name,
298
                                             const gchar *pattern)
299
{
300
        GtkCTreeNode *parent;
301
        GtkCTreeNode *node;
302
        gchar *cols[3];
303
        gchar *parent_name;
304
305
        if (*name == '\0') return NULL;
306
        node = grouplist_hash_get_branch_node(name);
307
        if (node != NULL) return node;
308
309
        cols[0] = (gchar *)name;
310
        cols[1] = cols[2] = "";
311
312
        parent_name = grouplist_get_parent_name(name);
313
        parent = grouplist_create_parent(parent_name, pattern);
314
315
        node = parent ? GTK_CTREE_ROW(parent)->children
316
                : GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
317
        node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node,
318
                                     cols, 0, NULL, NULL, NULL, NULL,
319
                                     FALSE, FALSE);
320
        if (parent && fnmatch(pattern, parent_name, 0) != 0)
321
                gtk_ctree_expand(GTK_CTREE(ctree), parent);
322
        gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, FALSE);
323
324
        grouplist_hash_set_branch_node(name, node);
325
326
        g_free(parent_name);
327
328
        return node;
329
}
330
331
static GtkCTreeNode *grouplist_create_branch(NewsGroupInfo *ginfo,
332
                                             const gchar *pattern)
333
{
334
        GtkCTreeNode *node;
335
        GtkCTreeNode *parent;
336
        gchar *name = (gchar *)ginfo->name;
337
        gchar *parent_name;
338
        gchar *count_str;
339
        gchar *cols[3];
340
        gint count;
341
342
        count = ginfo->last - ginfo->first;
343
        if (count < 0)
344
                count = 0;
345
        count_str = itos(count);
346
347
        cols[0] = ginfo->name;
348
        cols[1] = count_str;
349
        if (ginfo->type == 'y')
350
                cols[2] = "";
351
        else if (ginfo->type == 'm')
352
                cols[2] = _("moderated");
353
        else if (ginfo->type == 'n')
354
                cols[2] = _("readonly");
355
        else
356
                cols[2] = _("unknown");
357
358
        parent_name = grouplist_get_parent_name(name);
359
        parent = grouplist_create_parent(parent_name, pattern);
360
        node = grouplist_hash_get_branch_node(name);
361
        if (node) {
362
                gtk_ctree_set_node_info(GTK_CTREE(ctree), node, cols[0], 0,
363
                                        NULL, NULL, NULL, NULL, FALSE, FALSE);
364
                gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 1, cols[1]);
365
                gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 2, cols[2]);
366
        } else {
367
                node = parent ? GTK_CTREE_ROW(parent)->children
368
                        : GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
369
                node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node,
370
                                             cols, 0, NULL, NULL, NULL, NULL,
371
                                             TRUE, FALSE);
372
                if (parent && fnmatch(pattern, parent_name, 0) != 0)
373
                        gtk_ctree_expand(GTK_CTREE(ctree), parent);
374
        }
375
        gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, TRUE);
376
        if (node)
377
                gtk_ctree_node_set_row_data(GTK_CTREE(ctree), node, ginfo);
378
379
        g_free(parent_name);
380
381
        return node;
382
}
383
384
static void grouplist_dialog_set_list(const gchar *pattern, gboolean refresh)
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
        if (refresh) {
396
                ack = TRUE;
397
                grouplist_clear();
398
                recv_set_ui_func(grouplist_recv_func, NULL);
399
                group_list = news_get_group_list(news_folder);
400
                group_list = g_slist_reverse(group_list);
401
                recv_set_ui_func(NULL, NULL);
402
                if (group_list == NULL && ack == TRUE) {
403
                        alertpanel_error(_("Can't retrieve newsgroup list."));
404
                        locked = FALSE;
405
                        return;
406
                }
407
        } else {
408
                g_signal_handlers_block_by_func
409
                        (G_OBJECT(ctree), G_CALLBACK(ctree_unselected),
410
                         NULL);
411
                gtk_clist_clear(GTK_CLIST(ctree));
412
                g_signal_handlers_unblock_by_func
413
                        (G_OBJECT(ctree), G_CALLBACK(ctree_unselected),
414
                         NULL);
415
        }
416
        gtk_entry_set_text(GTK_ENTRY(entry), pattern);
417
418
        grouplist_hash_init();
419
420
        gtk_clist_freeze(GTK_CLIST(ctree));
421
422
        g_signal_handlers_block_by_func(G_OBJECT(ctree),
423
                                        G_CALLBACK(ctree_selected), NULL);
424
425
        for (cur = group_list; cur != NULL ; cur = cur->next) {
426
                NewsGroupInfo *ginfo = (NewsGroupInfo *)cur->data;
427
428
                if (fnmatch(pattern, ginfo->name, 0) == 0) {
429
                        node = grouplist_create_branch(ginfo, pattern);
430
                        if (g_slist_find_custom(subscribed, ginfo->name,
431
                                                (GCompareFunc)g_strcasecmp)
432
                            != NULL)
433
                                gtk_ctree_select(GTK_CTREE(ctree), node);
434
                }
435
        }
436
437
        g_signal_handlers_unblock_by_func(G_OBJECT(ctree),
438
                                          G_CALLBACK(ctree_selected), NULL);
439
440
        gtk_clist_thaw(GTK_CLIST(ctree));
441
442
        grouplist_hash_done();
443
444
        gtk_label_set_text(GTK_LABEL(status_label), _("Done."));
445
446
        locked = FALSE;
447
}
448
449
static void grouplist_search(void)
450
{
451
        gchar *str;
452
453
        if (locked) return;
454
455
        str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
456
        grouplist_dialog_set_list(str, FALSE);
457
        g_free(str);
458
}
459
460
static void grouplist_clear(void)
461
{
462
        g_signal_handlers_block_by_func(G_OBJECT(ctree),
463
                                        G_CALLBACK(ctree_unselected), NULL);
464
        gtk_clist_clear(GTK_CLIST(ctree));
465
        gtk_entry_set_text(GTK_ENTRY(entry), "");
466
        news_group_list_free(group_list);
467
        group_list = NULL;
468
        g_signal_handlers_unblock_by_func(G_OBJECT(ctree),
469
                                          G_CALLBACK(ctree_unselected), NULL);
470
}
471
472
static gboolean 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
        if (ack == FALSE)
483
                return FALSE;
484
        else
485
                return TRUE;
486
}
487
488
static gint window_deleted(GtkWidget *widget, GdkEventAny *event, gpointer data)
489
{
490
        ack = FALSE;
491
        if (gtk_main_level() > 1)
492
                gtk_main_quit();
493
494
        return TRUE;
495
}
496
497
static void ok_clicked(GtkWidget *widget, gpointer data)
498
{
499
        ack = TRUE;
500
        if (gtk_main_level() > 1)
501
                gtk_main_quit();
502
}
503
504
static void cancel_clicked(GtkWidget *widget, gpointer data)
505
{
506
        ack = FALSE;
507
        if (gtk_main_level() > 1)
508
                gtk_main_quit();
509
}
510
511
static void refresh_clicked(GtkWidget *widget, gpointer data)
512
{ 
513
        gchar *str;
514
515
        if (locked) return;
516
517
        news_remove_group_list_cache(news_folder);
518
519
        str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
520
        grouplist_dialog_set_list(str, TRUE);
521
        g_free(str);
522
}
523
524
static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
525
                            gpointer data)
526
{
527
        if (event && event->keyval == GDK_Escape)
528
                cancel_clicked(NULL, NULL);
529
        return FALSE;
530
}
531
532
static void ctree_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
533
                           gpointer data)
534
{
535
        NewsGroupInfo *ginfo;
536
537
        ginfo = gtk_ctree_node_get_row_data(ctree, node);
538
        if (!ginfo) return;
539
540
        subscribed = g_slist_append(subscribed, g_strdup(ginfo->name));
541
}
542
543
static void ctree_unselected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
544
                             gpointer data)
545
{
546
        NewsGroupInfo *ginfo;
547
        GSList *list;
548
549
        ginfo = gtk_ctree_node_get_row_data(ctree, node);
550
        if (!ginfo) return;
551
552
        list = g_slist_find_custom(subscribed, ginfo->name,
553
                                   (GCompareFunc)g_strcasecmp);
554
        if (list) {
555
                g_free(list->data);
556
                subscribed = g_slist_remove(subscribed, list->data);
557
        }
558
}
559
560
static void entry_activated(GtkEditable *editable)
561
{
562
        grouplist_search();
563
}
564
565
static void search_clicked(GtkWidget *widget, gpointer data)
566
{
567
        grouplist_search();
568
}