Statistics
| Revision:

root / src / grouplistdialog.c @ 1

History | View | Annotate | Download (14.9 KB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2003 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                450
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        (const gchar        *pattern,
74
                                         gboolean         refresh);
75
static void grouplist_search                (void);
76
static void grouplist_clear                (void);
77
static gboolean grouplist_recv_func        (SockInfo        *sock,
78
                                         gint                 count,
79
                                         gint                 read_bytes,
80
                                         gpointer         data);
81

    
82
static gint window_deleted        (GtkWidget        *widget,
83
                                 GdkEventAny        *event,
84
                                 gpointer         data);
85
static void ok_clicked                (GtkWidget        *widget,
86
                                 gpointer         data);
87
static void cancel_clicked        (GtkWidget        *widget,
88
                                 gpointer         data);
89
static void refresh_clicked        (GtkWidget        *widget,
90
                                 gpointer         data);
91
static gboolean key_pressed        (GtkWidget        *widget,
92
                                 GdkEventKey        *event,
93
                                 gpointer         data);
94
static void ctree_selected        (GtkCTree        *ctree,
95
                                 GtkCTreeNode        *node,
96
                                 gint                 column,
97
                                 gpointer         data);
98
static void ctree_unselected        (GtkCTree        *ctree,
99
                                 GtkCTreeNode        *node,
100
                                 gint                 column,
101
                                 gpointer         data);
102
static void entry_activated        (GtkEditable        *editable);
103
static void search_clicked        (GtkWidget        *widget,
104
                                 gpointer         data);
105

    
106
GSList *grouplist_dialog(Folder *folder)
107
{
108
        GNode *node;
109
        FolderItem *item;
110

    
111
        if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL;
112

    
113
        if (!dialog)
114
                grouplist_dialog_create();
115

    
116
        news_folder = folder;
117

    
118
        gtk_widget_show(dialog);
119
        gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
120
        manage_window_set_transient(GTK_WINDOW(dialog));
121
        gtk_widget_grab_focus(ok_button);
122
        gtk_widget_grab_focus(ctree);
123
        GTK_EVENTS_FLUSH();
124

    
125
        subscribed = NULL;
126
        for (node = folder->node->children; node != NULL; node = node->next) {
127
                item = FOLDER_ITEM(node->data);
128
                subscribed = g_slist_append(subscribed, g_strdup(item->path));
129
        }
130

    
131
        grouplist_dialog_set_list(NULL, TRUE);
132

    
133
        if (ack) gtk_main();
134

    
135
        manage_window_focus_out(dialog, NULL, NULL);
136
        gtk_widget_hide(dialog);
137

    
138
        if (!ack) {
139
                slist_free_strings(subscribed);
140
                g_slist_free(subscribed);
141
                subscribed = NULL;
142

    
143
                for (node = folder->node->children; node != NULL;
144
                     node = node->next) {
145
                        item = FOLDER_ITEM(node->data);
146
                        subscribed = g_slist_append(subscribed,
147
                                                    g_strdup(item->path));
148
                }
149
        }
150

    
151
        grouplist_clear();
152

    
153
        return subscribed;
154
}
155

    
156
static void grouplist_dialog_create(void)
157
{
158
        GtkWidget *vbox;
159
        GtkWidget *hbox;
160
        GtkWidget *msg_label;
161
        GtkWidget *search_button;
162
        GtkWidget *confirm_area;
163
        GtkWidget *cancel_button;        
164
        GtkWidget *refresh_button;        
165
        GtkWidget *scrolledwin;
166
        gchar *titles[3];
167
        gint i;
168

    
169
        dialog = gtk_dialog_new();
170
        gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, TRUE, FALSE);
171
        gtk_widget_set_size_request(dialog,
172
                                    GROUPLIST_DIALOG_WIDTH,
173
                                    GROUPLIST_DIALOG_HEIGHT);
174
        gtk_container_set_border_width
175
                (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
176
        gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
177
        gtk_window_set_title(GTK_WINDOW(dialog), _("Subscribe to newsgroup"));
178
        g_signal_connect(G_OBJECT(dialog), "delete_event",
179
                         G_CALLBACK(window_deleted), NULL);
180
        g_signal_connect(G_OBJECT(dialog), "key_press_event",
181
                         G_CALLBACK(key_pressed), NULL);
182
        MANAGE_WINDOW_SIGNALS_CONNECT(dialog);
183

    
184
        gtk_widget_realize(dialog);
185

    
186
        vbox = gtk_vbox_new(FALSE, 8);
187
        gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
188
        gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
189

    
190
        hbox = gtk_hbox_new(FALSE, 0);
191
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
192

    
193
        msg_label = gtk_label_new(_("Select newsgroups to subscribe."));
194
        gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0);
195

    
196
        hbox = gtk_hbox_new(FALSE, 8);
197
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
198

    
199
        msg_label = gtk_label_new(_("Find groups:"));
200
        gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0);
201

    
202
        entry = gtk_entry_new();
203
        gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
204
        g_signal_connect(G_OBJECT(entry), "activate",
205
                         G_CALLBACK(entry_activated), NULL);
206

    
207
        search_button = gtk_button_new_with_label(_(" Search "));
208
        gtk_box_pack_start(GTK_BOX(hbox), search_button, FALSE, FALSE, 0);
209

    
210
        g_signal_connect(G_OBJECT(search_button), "clicked",
211
                         G_CALLBACK(search_clicked), NULL);
212

    
213
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
214
        gtk_box_pack_start(GTK_BOX (vbox), scrolledwin, TRUE, TRUE, 0);
215
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwin),
216
                                       GTK_POLICY_AUTOMATIC,
217
                                       GTK_POLICY_AUTOMATIC);
218

    
219
        titles[0] = _("Newsgroup name");
220
        titles[1] = _("Messages");
221
        titles[2] = _("Type");
222
        ctree = gtk_ctree_new_with_titles(3, 0, titles);
223
        gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
224
        gtk_clist_set_column_width
225
                (GTK_CLIST(ctree), 0, GROUPLIST_COL_NAME_WIDTH);
226
        gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_MULTIPLE);
227
        gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED);
228
        gtk_ctree_set_expander_style(GTK_CTREE(ctree),
229
                                     GTK_CTREE_EXPANDER_SQUARE);
230
        for (i = 0; i < 3; i++)
231
                GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button,
232
                                       GTK_CAN_FOCUS);
233
        g_signal_connect(G_OBJECT(ctree), "tree_select_row",
234
                         G_CALLBACK(ctree_selected), NULL);
235
        g_signal_connect(G_OBJECT(ctree), "tree_unselect_row",
236
                         G_CALLBACK(ctree_unselected), NULL);
237

    
238
        hbox = gtk_hbox_new(FALSE, 0);
239
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
240

    
241
        status_label = gtk_label_new("");
242
        gtk_box_pack_start(GTK_BOX(hbox), status_label, FALSE, FALSE, 0);
243

    
244
        gtkut_button_set_create(&confirm_area,
245
                                &ok_button,      _("OK"),
246
                                &cancel_button,  _("Cancel"),
247
                                &refresh_button, _("Refresh"));
248
        gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
249
                          confirm_area);
250
        gtk_widget_grab_default(ok_button);
251

    
252
        g_signal_connect(G_OBJECT(ok_button), "clicked",
253
                         G_CALLBACK(ok_clicked), NULL);
254
        g_signal_connect(G_OBJECT(cancel_button), "clicked",
255
                         G_CALLBACK(cancel_clicked), NULL);
256
        g_signal_connect(G_OBJECT(refresh_button), "clicked",
257
                         G_CALLBACK(refresh_clicked), NULL);
258

    
259
        gtk_widget_show_all(GTK_DIALOG(dialog)->vbox);
260
}
261

    
262
static GHashTable *branch_node_table;
263

    
264
static void grouplist_hash_init(void)
265
{
266
        branch_node_table = g_hash_table_new(g_str_hash, g_str_equal);
267
}
268

    
269
static void grouplist_hash_done(void)
270
{
271
        hash_free_strings(branch_node_table);
272
        g_hash_table_destroy(branch_node_table);
273
}
274

    
275
static GtkCTreeNode *grouplist_hash_get_branch_node(const gchar *name)
276
{
277
        return g_hash_table_lookup(branch_node_table, name);
278
}
279

    
280
static void grouplist_hash_set_branch_node(const gchar *name,
281
                                           GtkCTreeNode *node)
282
{
283
        g_hash_table_insert(branch_node_table, g_strdup(name), node);
284
}
285

    
286
static gchar *grouplist_get_parent_name(const gchar *name)
287
{
288
        gchar *p;
289

    
290
        p = strrchr(name, '.');
291
        if (!p)
292
                return g_strdup("");
293
        return g_strndup(name, p - name);
294
}
295

    
296
static GtkCTreeNode *grouplist_create_parent(const gchar *name,
297
                                             const gchar *pattern)
298
{
299
        GtkCTreeNode *parent;
300
        GtkCTreeNode *node;
301
        gchar *cols[3];
302
        gchar *parent_name;
303

    
304
        if (*name == '\0') return NULL;
305
        node = grouplist_hash_get_branch_node(name);
306
        if (node != NULL) return node;
307

    
308
        cols[0] = (gchar *)name;
309
        cols[1] = cols[2] = "";
310

    
311
        parent_name = grouplist_get_parent_name(name);
312
        parent = grouplist_create_parent(parent_name, pattern);
313

    
314
        node = parent ? GTK_CTREE_ROW(parent)->children
315
                : GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
316
        node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node,
317
                                     cols, 0, NULL, NULL, NULL, NULL,
318
                                     FALSE, FALSE);
319
        if (parent && fnmatch(pattern, parent_name, 0) != 0)
320
                gtk_ctree_expand(GTK_CTREE(ctree), parent);
321
        gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, FALSE);
322

    
323
        grouplist_hash_set_branch_node(name, node);
324

    
325
        g_free(parent_name);
326

    
327
        return node;
328
}
329

    
330
static GtkCTreeNode *grouplist_create_branch(NewsGroupInfo *ginfo,
331
                                             const gchar *pattern)
332
{
333
        GtkCTreeNode *node;
334
        GtkCTreeNode *parent;
335
        gchar *name = (gchar *)ginfo->name;
336
        gchar *parent_name;
337
        gchar *count_str;
338
        gchar *cols[3];
339
        gint count;
340

    
341
        count = ginfo->last - ginfo->first;
342
        if (count < 0)
343
                count = 0;
344
        count_str = itos(count);
345

    
346
        cols[0] = ginfo->name;
347
        cols[1] = count_str;
348
        if (ginfo->type == 'y')
349
                cols[2] = "";
350
        else if (ginfo->type == 'm')
351
                cols[2] = _("moderated");
352
        else if (ginfo->type == 'n')
353
                cols[2] = _("readonly");
354
        else
355
                cols[2] = _("unknown");
356

    
357
        parent_name = grouplist_get_parent_name(name);
358
        parent = grouplist_create_parent(parent_name, pattern);
359
        node = grouplist_hash_get_branch_node(name);
360
        if (node) {
361
                gtk_ctree_set_node_info(GTK_CTREE(ctree), node, cols[0], 0,
362
                                        NULL, NULL, NULL, NULL, FALSE, FALSE);
363
                gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 1, cols[1]);
364
                gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 2, cols[2]);
365
        } else {
366
                node = parent ? GTK_CTREE_ROW(parent)->children
367
                        : GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
368
                node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node,
369
                                             cols, 0, NULL, NULL, NULL, NULL,
370
                                             TRUE, FALSE);
371
                if (parent && fnmatch(pattern, parent_name, 0) != 0)
372
                        gtk_ctree_expand(GTK_CTREE(ctree), parent);
373
        }
374
        gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, TRUE);
375
        if (node)
376
                gtk_ctree_node_set_row_data(GTK_CTREE(ctree), node, ginfo);
377

    
378
        g_free(parent_name);
379

    
380
        return node;
381
}
382

    
383
static void grouplist_dialog_set_list(const gchar *pattern, gboolean refresh)
384
{
385
        GSList *cur;
386
        GtkCTreeNode *node;
387

    
388
        if (locked) return;
389
        locked = TRUE;
390

    
391
        if (!pattern || *pattern == '\0')
392
                pattern = "*";
393

    
394
        if (refresh) {
395
                ack = TRUE;
396
                grouplist_clear();
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 && ack == TRUE) {
402
                        alertpanel_error(_("Can't retrieve newsgroup list."));
403
                        locked = FALSE;
404
                        return;
405
                }
406
        } else {
407
                g_signal_handlers_block_by_func
408
                        (G_OBJECT(ctree), G_CALLBACK(ctree_unselected),
409
                         NULL);
410
                gtk_clist_clear(GTK_CLIST(ctree));
411
                g_signal_handlers_unblock_by_func
412
                        (G_OBJECT(ctree), G_CALLBACK(ctree_unselected),
413
                         NULL);
414
        }
415
        gtk_entry_set_text(GTK_ENTRY(entry), pattern);
416

    
417
        grouplist_hash_init();
418

    
419
        gtk_clist_freeze(GTK_CLIST(ctree));
420

    
421
        g_signal_handlers_block_by_func(G_OBJECT(ctree),
422
                                        G_CALLBACK(ctree_selected), NULL);
423

    
424
        for (cur = group_list; cur != NULL ; cur = cur->next) {
425
                NewsGroupInfo *ginfo = (NewsGroupInfo *)cur->data;
426

    
427
                if (fnmatch(pattern, ginfo->name, 0) == 0) {
428
                        node = grouplist_create_branch(ginfo, pattern);
429
                        if (g_slist_find_custom(subscribed, ginfo->name,
430
                                                (GCompareFunc)g_strcasecmp)
431
                            != NULL)
432
                                gtk_ctree_select(GTK_CTREE(ctree), node);
433
                }
434
        }
435

    
436
        g_signal_handlers_unblock_by_func(G_OBJECT(ctree),
437
                                          G_CALLBACK(ctree_selected), NULL);
438

    
439
        gtk_clist_thaw(GTK_CLIST(ctree));
440

    
441
        grouplist_hash_done();
442

    
443
        gtk_label_set_text(GTK_LABEL(status_label), _("Done."));
444

    
445
        locked = FALSE;
446
}
447

    
448
static void grouplist_search(void)
449
{
450
        gchar *str;
451

    
452
        if (locked) return;
453

    
454
        str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
455
        grouplist_dialog_set_list(str, FALSE);
456
        g_free(str);
457
}
458

    
459
static void grouplist_clear(void)
460
{
461
        g_signal_handlers_block_by_func(G_OBJECT(ctree),
462
                                        G_CALLBACK(ctree_unselected), NULL);
463
        gtk_clist_clear(GTK_CLIST(ctree));
464
        gtk_entry_set_text(GTK_ENTRY(entry), "");
465
        news_group_list_free(group_list);
466
        group_list = NULL;
467
        g_signal_handlers_unblock_by_func(G_OBJECT(ctree),
468
                                          G_CALLBACK(ctree_unselected), NULL);
469
}
470

    
471
static gboolean grouplist_recv_func(SockInfo *sock, gint count, gint read_bytes,
472
                                    gpointer data)
473
{
474
        gchar buf[BUFFSIZE];
475

    
476
        g_snprintf(buf, sizeof(buf),
477
                   _("%d newsgroups received (%s read)"),
478
                   count, to_human_readable(read_bytes));
479
        gtk_label_set_text(GTK_LABEL(status_label), buf);
480
        GTK_EVENTS_FLUSH();
481
        if (ack == FALSE)
482
                return FALSE;
483
        else
484
                return TRUE;
485
}
486

    
487
static gint window_deleted(GtkWidget *widget, GdkEventAny *event, gpointer data)
488
{
489
        ack = FALSE;
490
        if (gtk_main_level() > 1)
491
                gtk_main_quit();
492

    
493
        return TRUE;
494
}
495

    
496
static void ok_clicked(GtkWidget *widget, gpointer data)
497
{
498
        ack = TRUE;
499
        if (gtk_main_level() > 1)
500
                gtk_main_quit();
501
}
502

    
503
static void cancel_clicked(GtkWidget *widget, gpointer data)
504
{
505
        ack = FALSE;
506
        if (gtk_main_level() > 1)
507
                gtk_main_quit();
508
}
509

    
510
static void refresh_clicked(GtkWidget *widget, gpointer data)
511
{ 
512
        gchar *str;
513

    
514
        if (locked) return;
515

    
516
        news_remove_group_list_cache(news_folder);
517

    
518
        str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
519
        grouplist_dialog_set_list(str, TRUE);
520
        g_free(str);
521
}
522

    
523
static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
524
                            gpointer data)
525
{
526
        if (event && event->keyval == GDK_Escape)
527
                cancel_clicked(NULL, NULL);
528
        return FALSE;
529
}
530

    
531
static void ctree_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
532
                           gpointer data)
533
{
534
        NewsGroupInfo *ginfo;
535

    
536
        ginfo = gtk_ctree_node_get_row_data(ctree, node);
537
        if (!ginfo) return;
538

    
539
        subscribed = g_slist_append(subscribed, g_strdup(ginfo->name));
540
}
541

    
542
static void ctree_unselected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
543
                             gpointer data)
544
{
545
        NewsGroupInfo *ginfo;
546
        GSList *list;
547

    
548
        ginfo = gtk_ctree_node_get_row_data(ctree, node);
549
        if (!ginfo) return;
550

    
551
        list = g_slist_find_custom(subscribed, ginfo->name,
552
                                   (GCompareFunc)g_strcasecmp);
553
        if (list) {
554
                g_free(list->data);
555
                subscribed = g_slist_remove(subscribed, list->data);
556
        }
557
}
558

    
559
static void entry_activated(GtkEditable *editable)
560
{
561
        grouplist_search();
562
}
563

    
564
static void search_clicked(GtkWidget *widget, gpointer data)
565
{
566
        grouplist_search();
567
}