Statistics
| Revision:

root / src / query_search.c @ 1490

History | View | Annotate | Download (31.5 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2006 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 <glib/gi18n.h>
28
#include <gdk/gdkkeysyms.h>
29
#include <gtk/gtkwidget.h>
30
#include <gtk/gtkwindow.h>
31
#include <gtk/gtkvbox.h>
32
#include <gtk/gtktable.h>
33
#include <gtk/gtkoptionmenu.h>
34
#include <gtk/gtklabel.h>
35
#include <gtk/gtkentry.h>
36
#include <gtk/gtkhbox.h>
37
#include <gtk/gtkcheckbutton.h>
38
#include <gtk/gtkhbbox.h>
39
#include <gtk/gtkbutton.h>
40
#include <gtk/gtkmenuitem.h>
41
#include <gtk/gtkstock.h>
42
#include <gtk/gtktreemodel.h>
43
#include <gtk/gtkliststore.h>
44
#include <gtk/gtktreeview.h>
45
#include <gtk/gtktreeselection.h>
46
#include <gtk/gtkcellrenderertext.h>
47
#include <stdio.h>
48
#include <stdlib.h>
49
#include <string.h>
50
51
#include "query_search.h"
52
#include "summaryview.h"
53
#include "messageview.h"
54
#include "mainwindow.h"
55
#include "folderview.h"
56
#include "menu.h"
57
#include "utils.h"
58
#include "gtkutils.h"
59
#include "manage_window.h"
60
#include "alertpanel.h"
61
#include "foldersel.h"
62
#include "statusbar.h"
63
#include "procmsg.h"
64
#include "procheader.h"
65
#include "folder.h"
66
#include "filter.h"
67
#include "prefs_filter.h"
68
#include "prefs_filter_edit.h"
69
70
enum
71
{
72
        COL_FOLDER,
73
        COL_SUBJECT,
74
        COL_FROM,
75
        COL_DATE,
76
        COL_MSGINFO,
77
        N_COLS
78
};
79
80
static struct QuerySearchWindow {
81
        GtkWidget *window;
82
83
        GtkWidget *bool_optmenu;
84
85
        FilterCondEdit *cond_edit;
86
87
        GtkWidget *folder_entry;
88
        GtkWidget *folder_btn;
89
90
        GtkWidget *subfolder_checkbtn;
91
        GtkWidget *case_checkbtn;
92
93
        GtkWidget *treeview;
94
        GtkListStore *store;
95
96
        GtkWidget *status_label;
97
98
        GtkWidget *clear_btn;
99
        GtkWidget *search_btn;
100
        GtkWidget *save_btn;
101
        GtkWidget *close_btn;
102
103
        FilterRule *rule;
104
        gboolean requires_full_headers;
105
106
        gboolean exclude_trash;
107
108
        gboolean on_search;
109
        gboolean cancelled;
110
} search_window;
111
112
typedef struct {
113
        GtkWidget *window;
114
115
        GtkWidget *folder_entry;
116
        GtkWidget *name_entry;
117
118
        GtkWidget *ok_btn;
119
        GtkWidget *cancel_btn;
120
121
        gboolean cancelled;
122
        gboolean finished;
123
} QuerySearchSaveDialog;
124
125
static void query_search_create        (void);
126
127
static FilterRule *query_search_dialog_to_rule        (const gchar        *name,
128
                                                 FolderItem    **item);
129
130
static void query_search_query                        (void);
131
static void query_search_folder                        (FolderItem        *item);
132
133
static gboolean query_search_recursive_func        (GNode                *node,
134
                                                 gpointer         data);
135
136
static void query_search_append_msg        (MsgInfo        *msginfo);
137
static void query_search_clear_list        (void);
138
139
static void query_search_hbox_added        (CondHBox        *hbox);
140
141
static void row_activated                (GtkTreeView                *treeview,
142
                                         GtkTreePath                *path,
143
                                         GtkTreeViewColumn        *column,
144
                                         gpointer                 data);
145
146
static gboolean row_selected                (GtkTreeSelection        *selection,
147
                                         GtkTreeModel                *model,
148
                                         GtkTreePath                *path,
149
                                         gboolean                 cur_selected,
150
                                         gpointer                 data);
151
152
static void query_search_clear        (GtkButton        *button,
153
                                         gpointer         data);
154
static void query_select_folder        (GtkButton        *button,
155
                                         gpointer         data);
156
static void query_search_clicked        (GtkButton        *button,
157
                                         gpointer         data);
158
static void query_search_save                (GtkButton        *button,
159
                                         gpointer         data);
160
static void query_search_close                (GtkButton        *button,
161
                                         gpointer         data);
162
163
static void query_search_entry_activated(GtkWidget        *widget,
164
                                         gpointer         data);
165
166
static gint query_search_deleted        (GtkWidget        *widget,
167
                                         GdkEventAny        *event,
168
                                         gpointer         data);
169
static gboolean key_pressed                (GtkWidget        *widget,
170
                                         GdkEventKey        *event,
171
                                         gpointer         data);
172
173
static gint query_search_cmp_by_folder        (GtkTreeModel        *model,
174
                                         GtkTreeIter        *a,
175
                                         GtkTreeIter        *b,
176
                                         gpointer         data);
177
static gint query_search_cmp_by_subject        (GtkTreeModel        *model,
178
                                         GtkTreeIter        *a,
179
                                         GtkTreeIter        *b,
180
                                         gpointer         data);
181
static gint query_search_cmp_by_from        (GtkTreeModel        *model,
182
                                         GtkTreeIter        *a,
183
                                         GtkTreeIter        *b,
184
                                         gpointer         data);
185
static gint query_search_cmp_by_date        (GtkTreeModel        *model,
186
                                         GtkTreeIter        *a,
187
                                         GtkTreeIter        *b,
188
                                         gpointer         data);
189
190
191
void query_search(FolderItem *item)
192
{
193
        gchar *id;
194
195
        if (!search_window.window)
196
                query_search_create();
197
        else
198
                gtk_window_present(GTK_WINDOW(search_window.window));
199
200
        if (item && item->stype != F_VIRTUAL) {
201
                id = folder_item_get_identifier(item);
202
                gtk_entry_set_text(GTK_ENTRY(search_window.folder_entry), id);
203
                g_free(id);
204
        } else
205
                gtk_entry_set_text(GTK_ENTRY(search_window.folder_entry), "");
206
207
        gtk_widget_grab_focus(search_window.search_btn);
208
        gtk_widget_show(search_window.window);
209
}
210
211
static void query_search_create(void)
212
{
213
        GtkWidget *window;
214
        GtkWidget *vbox1;
215
        GtkWidget *bool_hbox;
216
        GtkWidget *bool_optmenu;
217
        GtkWidget *bool_menu;
218
        GtkWidget *menuitem;
219
        GtkWidget *clear_btn;
220
        GtkWidget *search_btn;
221
222
        GtkWidget *scrolledwin;
223
        FilterCondEdit *cond_edit;
224
        CondHBox *cond_hbox;
225
226
        GtkWidget *folder_hbox;
227
        GtkWidget *folder_label;
228
        GtkWidget *folder_entry;
229
        GtkWidget *folder_btn;
230
231
        GtkWidget *checkbtn_hbox;
232
        GtkWidget *subfolder_checkbtn;
233
        GtkWidget *case_checkbtn;
234
235
        GtkWidget *treeview;
236
        GtkListStore *store;
237
        GtkTreeViewColumn *column;
238
        GtkCellRenderer *renderer;
239
        GtkTreeSelection *selection;
240
241
        GtkWidget *confirm_area;
242
243
        GtkWidget *status_label;
244
245
        GtkWidget *btn_hbox;
246
        GtkWidget *hbbox;
247
        GtkWidget *save_btn;
248
        GtkWidget *close_btn;
249
250
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
251
        gtk_window_set_title(GTK_WINDOW (window), _("Search messages"));
252
        gtk_widget_set_size_request(window, 600, -1);
253
        gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE);
254
        gtk_container_set_border_width(GTK_CONTAINER (window), 8);
255
        g_signal_connect(G_OBJECT(window), "delete_event",
256
                         G_CALLBACK(query_search_deleted), NULL);
257
        g_signal_connect(G_OBJECT(window), "key_press_event",
258
                         G_CALLBACK(key_pressed), NULL);
259
        MANAGE_WINDOW_SIGNALS_CONNECT(window);
260
261
        vbox1 = gtk_vbox_new (FALSE, 6);
262
        gtk_widget_show (vbox1);
263
        gtk_container_add (GTK_CONTAINER (window), vbox1);
264
265
        bool_hbox = gtk_hbox_new(FALSE, 12);
266
        gtk_widget_show(bool_hbox);
267
        gtk_box_pack_start(GTK_BOX(vbox1), bool_hbox, FALSE, FALSE, 0);
268
269
        bool_optmenu = gtk_option_menu_new();
270
        gtk_widget_show(bool_optmenu);
271
        gtk_box_pack_start(GTK_BOX(bool_hbox), bool_optmenu, FALSE, FALSE, 0);
272
273
        bool_menu = gtk_menu_new();
274
        MENUITEM_ADD(bool_menu, menuitem, _("Match any of the following"),
275
                     FLT_OR);
276
        MENUITEM_ADD(bool_menu, menuitem, _("Match all of the following"),
277
                     FLT_AND);
278
        gtk_option_menu_set_menu(GTK_OPTION_MENU(bool_optmenu), bool_menu);
279
280
        hbbox = gtk_hbutton_box_new();
281
        gtk_widget_show(hbbox);
282
        gtk_button_box_set_layout(GTK_BUTTON_BOX(hbbox), GTK_BUTTONBOX_END);
283
        gtk_box_set_spacing(GTK_BOX(hbbox), 6);
284
        gtk_box_pack_end(GTK_BOX(bool_hbox), hbbox, FALSE, FALSE, 0);
285
286
        clear_btn = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
287
        gtk_widget_show(clear_btn);
288
        gtk_box_pack_start(GTK_BOX(hbbox), clear_btn, FALSE, FALSE, 0);
289
290
        search_btn = gtk_button_new_from_stock(GTK_STOCK_FIND);
291
        GTK_WIDGET_SET_FLAGS(search_btn, GTK_CAN_DEFAULT);
292
        gtk_widget_show(search_btn);
293
        gtk_box_pack_start(GTK_BOX(hbbox), search_btn, FALSE, FALSE, 0);
294
        gtk_widget_grab_default(search_btn);
295
296
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
297
        gtk_widget_show(scrolledwin);
298
        gtk_box_pack_start(GTK_BOX(vbox1), scrolledwin, FALSE, FALSE, 0);
299
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
300
                                       GTK_POLICY_AUTOMATIC,
301
                                       GTK_POLICY_AUTOMATIC);
302
        gtk_widget_set_size_request(scrolledwin, -1, 120);
303
304
        cond_edit = prefs_filter_edit_cond_edit_create();
305
        cond_edit->add_hbox = query_search_hbox_added;
306
        gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolledwin),
307
                                              cond_edit->cond_vbox);
308
        prefs_filter_set_header_list(NULL);
309
        prefs_filter_edit_set_header_list(cond_edit, NULL);
310
        cond_hbox = prefs_filter_edit_cond_hbox_create(cond_edit);
311
        prefs_filter_edit_set_cond_hbox_widgets(cond_hbox, PF_COND_HEADER);
312
        prefs_filter_edit_insert_cond_hbox(cond_edit, cond_hbox, -1);
313
        if (cond_edit->add_hbox)
314
                cond_edit->add_hbox(cond_hbox);
315
316
        folder_hbox = gtk_hbox_new (FALSE, 8);
317
        gtk_widget_show (folder_hbox);
318
        gtk_box_pack_start (GTK_BOX (vbox1), folder_hbox, FALSE, FALSE, 0);
319
320
        folder_label = gtk_label_new (_("Folder:"));
321
        gtk_widget_show (folder_label);
322
        gtk_box_pack_start (GTK_BOX (folder_hbox), folder_label,
323
                            FALSE, FALSE, 0);
324
325
        folder_entry = gtk_entry_new ();
326
        gtk_widget_show (folder_entry);
327
        gtk_box_pack_start (GTK_BOX (folder_hbox), folder_entry, TRUE, TRUE, 0);
328
329
        folder_btn = gtk_button_new_with_label("...");
330
        gtk_widget_show (folder_btn);
331
        gtk_box_pack_start (GTK_BOX (folder_hbox), folder_btn, FALSE, FALSE, 0);
332
333
        checkbtn_hbox = gtk_hbox_new (FALSE, 12);
334
        gtk_widget_show (checkbtn_hbox);
335
        gtk_box_pack_start (GTK_BOX (vbox1), checkbtn_hbox, FALSE, FALSE, 0);
336
337
        subfolder_checkbtn =
338
                gtk_check_button_new_with_label (_("Search subfolders"));
339
        gtk_widget_show (subfolder_checkbtn);
340
        gtk_box_pack_start (GTK_BOX (checkbtn_hbox), subfolder_checkbtn,
341
                            FALSE, FALSE, 0);
342
343
        case_checkbtn = gtk_check_button_new_with_label (_("Case sensitive"));
344
        gtk_widget_show (case_checkbtn);
345
        gtk_box_pack_start (GTK_BOX (checkbtn_hbox), case_checkbtn,
346
                            FALSE, FALSE, 0);
347
348
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
349
        gtk_box_pack_start(GTK_BOX(vbox1), scrolledwin, TRUE, TRUE, 0);
350
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
351
                                       GTK_POLICY_AUTOMATIC,
352
                                       GTK_POLICY_AUTOMATIC);
353
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
354
                                            GTK_SHADOW_IN);
355
        gtk_widget_set_size_request(scrolledwin, -1, 150);
356
357
        store = gtk_list_store_new(N_COLS,
358
                                   G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
359
                                   G_TYPE_STRING, G_TYPE_POINTER);
360
        treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
361
        g_object_unref(store);
362
        gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
363
        g_signal_connect(G_OBJECT(treeview), "row-activated",
364
                         G_CALLBACK(row_activated), NULL);
365
366
        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), COL_FOLDER,
367
                                        query_search_cmp_by_folder, NULL, NULL);
368
        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), COL_SUBJECT,
369
                                        query_search_cmp_by_subject,
370
                                        NULL, NULL);
371
        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), COL_FROM,
372
                                        query_search_cmp_by_from, NULL, NULL);
373
        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), COL_DATE,
374
                                        query_search_cmp_by_date, NULL, NULL);
375
376
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
377
        gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
378
        gtk_tree_selection_set_select_function(selection, row_selected,
379
                                               NULL, NULL);
380
381
        gtk_container_add(GTK_CONTAINER(scrolledwin), treeview);
382
383
#define APPEND_COLUMN(label, col, width)                                \
384
{                                                                        \
385
        renderer = gtk_cell_renderer_text_new();                        \
386
        column = gtk_tree_view_column_new_with_attributes                \
387
                (label, renderer, "text", col, NULL);                        \
388
        gtk_tree_view_column_set_resizable(column, TRUE);                \
389
        if (width) {                                                        \
390
                gtk_tree_view_column_set_sizing                                \
391
                        (column, GTK_TREE_VIEW_COLUMN_FIXED);                \
392
                gtk_tree_view_column_set_fixed_width(column, width);        \
393
        }                                                                \
394
        gtk_tree_view_column_set_sort_column_id(column, col);                \
395
        gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);        \
396
}
397
398
        APPEND_COLUMN(_("Folder"), COL_FOLDER, 0);
399
        APPEND_COLUMN(_("Subject"), COL_SUBJECT, 200);
400
        APPEND_COLUMN(_("From"), COL_FROM, 180);
401
        APPEND_COLUMN(_("Date"), COL_DATE, 0);
402
403
        gtk_widget_show_all(scrolledwin);
404
405
        confirm_area = gtk_hbox_new(FALSE, 12);
406
        gtk_widget_show(confirm_area);
407
        gtk_box_pack_start(GTK_BOX(vbox1), confirm_area, FALSE, FALSE, 0);
408
409
        status_label = gtk_label_new("");
410
        gtk_widget_show(status_label);
411
        gtk_box_pack_start(GTK_BOX(confirm_area), status_label,
412
                           FALSE, FALSE, 0);
413
414
        btn_hbox = gtk_hbox_new(FALSE, 6);
415
        gtk_widget_show(btn_hbox);
416
        gtk_box_pack_end(GTK_BOX(confirm_area), btn_hbox, FALSE, FALSE, 0);
417
418
        gtkut_stock_button_set_create(&hbbox, &close_btn, GTK_STOCK_CLOSE,
419
                                      NULL, NULL, NULL, NULL);
420
        gtk_widget_show(hbbox);
421
        gtk_box_pack_end(GTK_BOX(btn_hbox), hbbox, FALSE, FALSE, 0);
422
423
        save_btn = gtk_button_new_with_mnemonic(_("_Save as search folder"));
424
        gtk_widget_show(save_btn);
425
        gtk_box_pack_end(GTK_BOX(btn_hbox), save_btn, FALSE, FALSE, 0);
426
427
        g_signal_connect(G_OBJECT(clear_btn), "clicked",
428
                         G_CALLBACK(query_search_clear), NULL);
429
        g_signal_connect(G_OBJECT(folder_btn), "clicked",
430
                         G_CALLBACK(query_select_folder), NULL);
431
        g_signal_connect(G_OBJECT(search_btn), "clicked",
432
                         G_CALLBACK(query_search_clicked), NULL);
433
        g_signal_connect(G_OBJECT(save_btn), "clicked",
434
                         G_CALLBACK(query_search_save), NULL);
435
        g_signal_connect(G_OBJECT(close_btn), "clicked",
436
                         G_CALLBACK(query_search_close), NULL);
437
438
        search_window.window = window;
439
        search_window.bool_optmenu = bool_optmenu;
440
441
        search_window.cond_edit = cond_edit;
442
443
        search_window.folder_entry = folder_entry;
444
        search_window.folder_btn = folder_btn;
445
        search_window.subfolder_checkbtn = subfolder_checkbtn;
446
        search_window.case_checkbtn = case_checkbtn;
447
448
        search_window.treeview = treeview;
449
        search_window.store = store;
450
451
        search_window.status_label = status_label;
452
453
        search_window.clear_btn = clear_btn;
454
        search_window.search_btn = search_btn;
455
        search_window.save_btn  = save_btn;
456
        search_window.close_btn = close_btn;
457
}
458
459
static FilterRule *query_search_dialog_to_rule(const gchar *name,
460
                                                 FolderItem **item)
461
{
462
        const gchar *id;
463
        FolderItem *item_;
464
        FilterBoolOp bool_op = FLT_OR;
465
        gboolean recursive;
466
        gboolean case_sens;
467
        GSList *cond_list;
468
        FilterRule *rule;
469
470
        id = gtk_entry_get_text(GTK_ENTRY(search_window.folder_entry));
471
        item_ = folder_find_item_from_identifier(id);
472
        if (!item_)
473
                return NULL;
474
        if (item)
475
                *item = item_;
476
477
        bool_op = menu_get_option_menu_active_index
478
                (GTK_OPTION_MENU(search_window.bool_optmenu));
479
        recursive = gtk_toggle_button_get_active
480
                (GTK_TOGGLE_BUTTON(search_window.subfolder_checkbtn));
481
        case_sens = gtk_toggle_button_get_active
482
                (GTK_TOGGLE_BUTTON(search_window.case_checkbtn));
483
484
        cond_list = prefs_filter_edit_cond_edit_to_list(search_window.cond_edit,
485
                                                        case_sens);
486
        if (!cond_list)
487
                return NULL;
488
489
        rule = filter_rule_new(name, bool_op, cond_list, NULL);
490
        rule->target_folder = g_strdup(id);
491
        rule->recursive = recursive;
492
493
        return rule;
494
}
495
496
static void query_search_query(void)
497
{
498
        FolderItem *item;
499
500
        if (search_window.on_search)
501
                return;
502
503
        search_window.on_search = TRUE;
504
505
        search_window.rule = query_search_dialog_to_rule("Query rule", &item);
506
        if (!search_window.rule) {
507
                search_window.on_search = FALSE;
508
                return;
509
        }
510
        search_window.requires_full_headers =
511
                filter_rule_requires_full_headers(search_window.rule);
512
513
        if (search_window.rule->recursive) {
514
                if (item->stype == F_TRASH)
515
                        search_window.exclude_trash = FALSE;
516
                else
517
                        search_window.exclude_trash = TRUE;
518
        } else
519
                search_window.exclude_trash = FALSE;
520
521
        search_window.cancelled = FALSE;
522
523
        gtk_widget_set_sensitive(search_window.clear_btn, FALSE);
524
        gtk_button_set_label(GTK_BUTTON(search_window.search_btn),
525
                             GTK_STOCK_STOP);
526
        query_search_clear_list();
527
528
        if (search_window.rule->recursive)
529
                g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
530
                                query_search_recursive_func, NULL);
531
        else
532
                query_search_folder(item);
533
534
        filter_rule_free(search_window.rule);
535
        search_window.rule = NULL;
536
        search_window.requires_full_headers = FALSE;
537
        search_window.exclude_trash = FALSE;
538
539
        gtk_widget_set_sensitive(search_window.clear_btn, TRUE);
540
        gtk_button_set_label(GTK_BUTTON(search_window.search_btn),
541
                             GTK_STOCK_FIND);
542
        gtk_label_set_text(GTK_LABEL(search_window.status_label), _("Done."));
543
        statusbar_pop_all();
544
545
        if (search_window.cancelled)
546
                debug_print("* query search cancelled.\n");
547
        debug_print("query search finished.\n");
548
549
        search_window.on_search = FALSE;
550
        search_window.cancelled = FALSE;
551
}
552
553
static void query_search_folder(FolderItem *item)
554
{
555
        gchar *folder_name, *str;
556
        GSList *mlist;
557
        FilterInfo fltinfo;
558
        GSList *cur;
559
        gint count = 1, total;
560
        GTimeVal tv_prev, tv_cur;
561
562
        if (!item->path || item->stype == F_VIRTUAL)
563
                return;
564
565
        folder_name = g_path_get_basename(item->path);
566
        str = g_strdup_printf(_("Searching %s ..."), folder_name);
567
        gtk_label_set_text(GTK_LABEL(search_window.status_label), str);
568
        g_free(str);
569
        g_get_current_time(&tv_prev);
570
        ui_update();
571
572
        if (search_window.cancelled) {
573
                g_free(folder_name);
574
                return;
575
        }
576
577
        mlist = folder_item_get_msg_list(item, TRUE);
578
        total = g_slist_length(mlist);
579
580
        memset(&fltinfo, 0, sizeof(FilterInfo));
581
582
        debug_print("requires_full_headers: %d\n",
583
                    search_window.requires_full_headers);
584
        debug_print("start query search: %s\n", item->path ? item->path : "");
585
586
        for (cur = mlist; cur != NULL; cur = cur->next) {
587
                MsgInfo *msginfo = (MsgInfo *)cur->data;
588
                GSList *hlist;
589
590
                g_get_current_time(&tv_cur);
591
                if (tv_cur.tv_sec > tv_prev.tv_sec ||
592
                    tv_cur.tv_usec - tv_prev.tv_usec >
593
                    PROGRESS_UPDATE_INTERVAL * 1000) {
594
                        str = g_strdup_printf(_("Searching %s (%d / %d)..."),
595
                                              folder_name, count, total);
596
                        gtk_label_set_text
597
                                (GTK_LABEL(search_window.status_label), str);
598
                        g_free(str);
599
                        ui_update();
600
                        tv_prev = tv_cur;
601
                }
602
                ++count;
603
604
                if (search_window.cancelled)
605
                        break;
606
607
                fltinfo.flags = msginfo->flags;
608
                if (search_window.requires_full_headers) {
609
                        gchar *file;
610
611
                        file = procmsg_get_message_file(msginfo);
612
                        hlist = procheader_get_header_list_from_file(file);
613
                        g_free(file);
614
                } else
615
                        hlist = procheader_get_header_list_from_msginfo
616
                                (msginfo);
617
                if (!hlist)
618
                        continue;
619
620
                if (filter_match_rule(search_window.rule, msginfo, hlist,
621
                                      &fltinfo)) {
622
                        query_search_append_msg(msginfo);
623
                        cur->data = NULL;
624
                }
625
626
                procheader_header_list_destroy(hlist);
627
        }
628
629
        procmsg_msg_list_free(mlist);
630
        g_free(folder_name);
631
}
632
633
static gboolean query_search_recursive_func(GNode *node, gpointer data)
634
{
635
        FolderItem *item;
636
637
        g_return_val_if_fail(node->data != NULL, FALSE);
638
639
        item = FOLDER_ITEM(node->data);
640
641
        if (!item->path)
642
                return FALSE;
643
        if (search_window.exclude_trash && item->stype == F_TRASH)
644
                return FALSE;
645
646
        query_search_folder(item);
647
648
        if (search_window.cancelled)
649
                return TRUE;
650
651
        return FALSE;
652
}
653
654
static void query_search_append_msg(MsgInfo *msginfo)
655
{
656
        GtkListStore *store = search_window.store;
657
        GtkTreeIter iter;
658
        gchar *folder;
659
        gchar date_buf[80];
660
        const gchar *subject, *from, *date;
661
        gchar *id;
662
663
        id = folder_item_get_identifier(msginfo->folder);
664
        folder = g_path_get_basename(id);
665
        g_free(id);
666
        subject = msginfo->subject ? msginfo->subject : _("(No Subject)");
667
        from = msginfo->from ? msginfo->from : _("(No From)");
668
        if (msginfo->date_t) {
669
                procheader_date_get_localtime(date_buf, sizeof(date_buf),
670
                                              msginfo->date_t);
671
                date = date_buf;
672
        } else if (msginfo->date)
673
                date = msginfo->date;
674
        else
675
                date = _("(No Date)");
676
677
        gtk_list_store_append(store, &iter);
678
        gtk_list_store_set(store, &iter,
679
                           COL_FOLDER, folder,
680
                           COL_SUBJECT, subject,
681
                           COL_FROM, from,
682
                           COL_DATE, date,
683
                           COL_MSGINFO, msginfo,
684
                           -1);
685
686
        g_free(folder);
687
}
688
689
static void query_search_clear_list(void)
690
{
691
        GtkTreeIter iter;
692
        GtkTreeModel *model = GTK_TREE_MODEL(search_window.store);
693
        MsgInfo *msginfo;
694
695
        gtkut_tree_sortable_unset_sort_column_id
696
                (GTK_TREE_SORTABLE(search_window.store));
697
698
        if (!gtk_tree_model_get_iter_first(model, &iter))
699
                return;
700
701
        do {
702
                gtk_tree_model_get(model, &iter, COL_MSGINFO, &msginfo, -1);
703
                procmsg_msginfo_free(msginfo);
704
        } while (gtk_tree_model_iter_next(model, &iter));
705
706
        gtk_list_store_clear(search_window.store);
707
}
708
709
static void query_search_hbox_added(CondHBox *hbox)
710
{
711
        g_signal_connect(hbox->key_entry, "activate",
712
                         G_CALLBACK(query_search_entry_activated), NULL);
713
}
714
715
static void row_activated(GtkTreeView *treeview, GtkTreePath *path,
716
                          GtkTreeViewColumn *column, gpointer data)
717
{
718
        GtkTreeIter iter;
719
        GtkTreeModel *model = GTK_TREE_MODEL(search_window.store);
720
        MsgInfo *msginfo;
721
        MessageView *msgview;
722
723
        if (!gtk_tree_model_get_iter(model, &iter, path))
724
                return;
725
726
        gtk_tree_model_get(model, &iter, COL_MSGINFO, &msginfo, -1);
727
        if (!summary_select_by_msginfo(main_window_get()->summaryview,
728
                                       msginfo)) {
729
                msgview = messageview_create_with_new_window();
730
                messageview_show(msgview, msginfo, FALSE);
731
        }
732
}
733
734
static gboolean row_selected(GtkTreeSelection *selection,
735
                             GtkTreeModel *model, GtkTreePath *path,
736
                             gboolean cur_selected, gpointer data)
737
{
738
        return TRUE;
739
}
740
741
static void query_search_clear(GtkButton *button, gpointer data)
742
{
743
        CondHBox *cond_hbox;
744
745
        if (search_window.on_search)
746
                return;
747
748
        prefs_filter_edit_clear_cond_edit(search_window.cond_edit);
749
        prefs_filter_set_header_list(NULL);
750
        prefs_filter_edit_set_header_list(search_window.cond_edit, NULL);
751
        cond_hbox = prefs_filter_edit_cond_hbox_create(search_window.cond_edit);
752
        prefs_filter_edit_set_cond_hbox_widgets(cond_hbox, PF_COND_HEADER);
753
        prefs_filter_edit_insert_cond_hbox
754
                (search_window.cond_edit, cond_hbox, -1);
755
        if (search_window.cond_edit->add_hbox)
756
                search_window.cond_edit->add_hbox(cond_hbox);
757
758
        gtk_label_set_text(GTK_LABEL(search_window.status_label), "");
759
760
        query_search_clear_list();
761
}
762
763
static void query_select_folder(GtkButton *button, gpointer data)
764
{
765
        FolderItem *item;
766
        gchar *id;
767
768
        item = foldersel_folder_sel(NULL, FOLDER_SEL_ALL, NULL);
769
        if (!item || item->stype == F_VIRTUAL)
770
                return;
771
772
        id = folder_item_get_identifier(item);
773
        if (id) {
774
                gtk_entry_set_text(GTK_ENTRY(search_window.folder_entry), id);
775
                g_free(id);
776
        }
777
}
778
779
static void query_search_clicked(GtkButton *button, gpointer data)
780
{
781
        if (search_window.on_search)
782
                search_window.cancelled = TRUE;
783
        else
784
                query_search_query();
785
}
786
787
static gint query_search_save_dialog_deleted(GtkWidget *widget,
788
                                             GdkEventAny *event, gpointer data)
789
{
790
        QuerySearchSaveDialog *dialog = (QuerySearchSaveDialog *)data;
791
792
        dialog->cancelled = TRUE;
793
        dialog->finished = TRUE;
794
        return TRUE;
795
}
796
797
static gint query_search_save_dialog_key_pressed(GtkWidget *widget,
798
                                                   GdkEventKey *event,
799
                                                   gpointer data)
800
{
801
        QuerySearchSaveDialog *dialog = (QuerySearchSaveDialog *)data;
802
803
        if (event && event->keyval == GDK_Escape) {
804
                dialog->cancelled = TRUE;
805
                dialog->finished = TRUE;
806
        }
807
        return FALSE;
808
}
809
810
static void query_search_save_dialog_select_folder(GtkButton *button,
811
                                                   gpointer data)
812
{
813
        QuerySearchSaveDialog *dialog = (QuerySearchSaveDialog *)data;
814
        FolderItem *item;
815
        gchar *id;
816
817
        item = foldersel_folder_sel(NULL, FOLDER_SEL_ALL, NULL);
818
        if (!item || item->no_sub || item->stype == F_VIRTUAL)
819
                return;
820
821
        id = folder_item_get_identifier(item);
822
        if (id) {
823
                gtk_entry_set_text(GTK_ENTRY(dialog->folder_entry), id);
824
                g_free(id);
825
        }
826
}
827
828
static void query_search_save_activated(GtkEditable *editable, gpointer data)
829
{
830
        QuerySearchSaveDialog *dialog = (QuerySearchSaveDialog *)data;
831
832
        gtk_button_clicked(GTK_BUTTON(dialog->ok_btn));
833
}
834
835
static void query_search_save_ok(GtkButton *button, gpointer data)
836
{
837
        QuerySearchSaveDialog *dialog = (QuerySearchSaveDialog *)data;
838
839
        dialog->finished = TRUE;
840
}
841
842
static void query_search_save_cancel(GtkButton *button, gpointer data)
843
{
844
        QuerySearchSaveDialog *dialog = (QuerySearchSaveDialog *)data;
845
846
        dialog->cancelled = TRUE;
847
        dialog->finished = TRUE;
848
}
849
850
static QuerySearchSaveDialog *query_search_save_dialog_create(void)
851
{
852
        QuerySearchSaveDialog *dialog;
853
        GtkWidget *window;
854
        GtkWidget *vbox;
855
        GtkWidget *hbox;
856
        GtkWidget *label;
857
        GtkWidget *folder_entry;
858
        GtkWidget *folder_btn;
859
        GtkWidget *name_entry;
860
861
        GtkWidget *confirm_area;
862
        GtkWidget *hbbox;
863
        GtkWidget *cancel_btn;
864
        GtkWidget *ok_btn;
865
866
        dialog = g_new0(QuerySearchSaveDialog, 1);
867
868
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
869
        gtk_window_set_title(GTK_WINDOW(window), _("Save as search folder"));
870
        gtk_widget_set_size_request(window, 400, -1);
871
        gtk_container_set_border_width(GTK_CONTAINER(window), 8);
872
        gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
873
        gtk_window_set_modal(GTK_WINDOW(window), TRUE);
874
        gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE);
875
        g_signal_connect(G_OBJECT(window), "delete_event",
876
                         G_CALLBACK(query_search_save_dialog_deleted),
877
                         dialog);
878
        g_signal_connect(G_OBJECT(window), "key_press_event",
879
                         G_CALLBACK(query_search_save_dialog_key_pressed),
880
                         dialog);
881
        MANAGE_WINDOW_SIGNALS_CONNECT(window);
882
        manage_window_set_transient(GTK_WINDOW(window));
883
884
        vbox = gtk_vbox_new(FALSE, 8);
885
        gtk_container_add(GTK_CONTAINER(window), vbox);
886
887
        hbox = gtk_hbox_new(FALSE, 8);
888
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
889
890
        label = gtk_label_new(_("Location:"));
891
        gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
892
893
        folder_entry = gtk_entry_new();
894
        gtk_box_pack_start(GTK_BOX(hbox), folder_entry, TRUE, TRUE, 0);
895
896
        folder_btn = gtk_button_new_with_label("...");
897
        gtk_box_pack_start(GTK_BOX(hbox), folder_btn, FALSE, FALSE, 0);
898
        g_signal_connect(G_OBJECT(folder_btn), "clicked",
899
                         G_CALLBACK(query_search_save_dialog_select_folder),
900
                         dialog);
901
902
        hbox = gtk_hbox_new(FALSE, 8);
903
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
904
905
        label = gtk_label_new(_("Folder name:"));
906
        gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
907
908
        name_entry = gtk_entry_new();
909
        gtk_box_pack_start(GTK_BOX(hbox), name_entry, TRUE, TRUE, 0);
910
        g_signal_connect(G_OBJECT(name_entry), "activate",
911
                         G_CALLBACK(query_search_save_activated), dialog);
912
913
        confirm_area = gtk_hbox_new(FALSE, 12);
914
        gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
915
916
        gtkut_stock_button_set_create(&hbbox,
917
                                      &ok_btn, GTK_STOCK_OK,
918
                                      &cancel_btn, GTK_STOCK_CANCEL,
919
                                      NULL, NULL);
920
        gtk_box_pack_end(GTK_BOX(confirm_area), hbbox, FALSE, FALSE, 0);
921
        GTK_WIDGET_SET_FLAGS(ok_btn, GTK_CAN_DEFAULT);
922
        gtk_widget_grab_default(ok_btn);
923
        g_signal_connect(G_OBJECT(ok_btn), "clicked",
924
                         G_CALLBACK(query_search_save_ok), dialog);
925
        g_signal_connect(G_OBJECT(cancel_btn), "clicked",
926
                         G_CALLBACK(query_search_save_cancel), dialog);
927
928
        gtk_widget_grab_focus(name_entry);
929
930
        gtk_widget_show_all(window);
931
932
        dialog->window = window;
933
        dialog->folder_entry = folder_entry;
934
        dialog->name_entry = name_entry;
935
        dialog->ok_btn = ok_btn;
936
        dialog->cancel_btn = cancel_btn;
937
        dialog->cancelled = FALSE;
938
        dialog->finished = FALSE;
939
940
        return dialog;
941
}
942
943
static void query_search_save_dialog_destroy(QuerySearchSaveDialog *dialog)
944
{
945
        gtk_widget_destroy(dialog->window);
946
        g_free(dialog);
947
}
948
949
static FolderItem *query_search_create_vfolder(FolderItem *parent,
950
                                               const gchar *name)
951
{
952
        gchar *path;
953
        gchar *fs_name;
954
        gchar *fullpath;
955
        FolderItem *item;
956
957
        g_return_val_if_fail(parent != NULL, NULL);
958
        g_return_val_if_fail(name != NULL, NULL);
959
960
        path = folder_item_get_path(parent);
961
        fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
962
        fullpath = g_strconcat(path, G_DIR_SEPARATOR_S,
963
                               fs_name ? fs_name : name, NULL);
964
        g_free(fs_name);
965
        g_free(path);
966
967
        if (make_dir_hier(fullpath) < 0) {
968
                g_free(fullpath);
969
                return NULL;
970
        }
971
972
        if (parent->path)
973
                path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name, NULL);
974
        else
975
                path = g_strdup(name);
976
977
        item = folder_item_new(name, path);
978
        item->stype = F_VIRTUAL;
979
        item->no_sub = TRUE;
980
        folder_item_append(parent, item);
981
982
        g_free(path);
983
984
        return item;
985
}
986
987
static void query_search_vfolder_update_rule(FolderItem *item)
988
{
989
        GSList list;
990
        FilterRule *rule;
991
        gchar *file;
992
        gchar *path;
993
994
        rule = query_search_dialog_to_rule(item->name, NULL);
995
        list.data = rule;
996
        list.next = NULL;
997
998
        path = folder_item_get_path(item);
999
        file = g_strconcat(path, G_DIR_SEPARATOR_S, FILTER_LIST, NULL);
1000
        filter_write_file(&list, file);
1001
        g_free(file);
1002
        g_free(path);
1003
1004
        filter_rule_free(rule);
1005
}
1006
1007
static void query_search_save(GtkButton *button, gpointer data)
1008
{
1009
        QuerySearchSaveDialog *dialog;
1010
        const gchar *id, *name;
1011
        FolderItem *parent, *item;
1012
1013
        dialog = query_search_save_dialog_create();
1014
        id = gtk_entry_get_text(GTK_ENTRY(search_window.folder_entry));
1015
        if (id && *id)
1016
                gtk_entry_set_text(GTK_ENTRY(dialog->folder_entry), id);
1017
1018
        while (!dialog->finished)
1019
                gtk_main_iteration();
1020
1021
        if (dialog->cancelled) {
1022
                query_search_save_dialog_destroy(dialog);
1023
                return;
1024
        }
1025
1026
        id = gtk_entry_get_text(GTK_ENTRY(dialog->folder_entry));
1027
        parent = folder_find_item_from_identifier(id);
1028
        name = gtk_entry_get_text(GTK_ENTRY(dialog->name_entry));
1029
        if (parent && name && *name) {
1030
                if (folder_find_child_item_by_name(parent, name)) {
1031
                        alertpanel_error(_("The folder `%s' already exists."),
1032
                                         name);
1033
                } else {
1034
                        item = query_search_create_vfolder(parent, name);
1035
                        if (item) {
1036
                                query_search_vfolder_update_rule(item);
1037
                                folderview_append_item(folderview_get(),
1038
                                                       NULL, item, TRUE);
1039
                                folder_write_list();
1040
                        }
1041
                }
1042
        }
1043
1044
        query_search_save_dialog_destroy(dialog);
1045
}
1046
1047
static void query_search_close(GtkButton *button, gpointer data)
1048
{
1049
        if (search_window.on_search)
1050
                search_window.cancelled = TRUE;
1051
        gtk_widget_hide(search_window.window);
1052
}
1053
1054
static void query_search_entry_activated(GtkWidget *widget, gpointer data)
1055
{
1056
        gtk_button_clicked(GTK_BUTTON(search_window.search_btn));
1057
}
1058
1059
static gint query_search_deleted(GtkWidget *widget, GdkEventAny *event,
1060
                                 gpointer data)
1061
{
1062
        gtk_button_clicked(GTK_BUTTON(search_window.close_btn));
1063
        return TRUE;
1064
}
1065
1066
static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
1067
                            gpointer data)
1068
{
1069
        if (event && event->keyval == GDK_Escape) {
1070
                if (search_window.on_search)
1071
                        gtk_button_clicked
1072
                                (GTK_BUTTON(search_window.search_btn));
1073
                else
1074
                        gtk_button_clicked(GTK_BUTTON(search_window.close_btn));
1075
                return TRUE;
1076
        }
1077
        return FALSE;
1078
}
1079
1080
static gint query_search_cmp_by_folder(GtkTreeModel *model,
1081
                                       GtkTreeIter *a, GtkTreeIter *b,
1082
                                       gpointer data)
1083
{
1084
        gchar *folder_a = NULL, *folder_b = NULL;
1085
        MsgInfo *msginfo_a = NULL, *msginfo_b = NULL;
1086
        gint ret;
1087
1088
        gtk_tree_model_get(model, a, COL_FOLDER, &folder_a, COL_MSGINFO,
1089
                           &msginfo_a, -1);
1090
        gtk_tree_model_get(model, b, COL_FOLDER, &folder_b, COL_MSGINFO,
1091
                           &msginfo_b, -1);
1092
1093
        if (!folder_a || !folder_b || !msginfo_a || !msginfo_b)
1094
                return 0;
1095
1096
        ret = g_ascii_strcasecmp(folder_a, folder_b);
1097
        return (ret != 0) ? ret : (msginfo_a->date_t - msginfo_b->date_t);
1098
}
1099
1100
static gint query_search_cmp_by_subject(GtkTreeModel *model,
1101
                                        GtkTreeIter *a, GtkTreeIter *b,
1102
                                        gpointer data)
1103
{
1104
        MsgInfo *msginfo_a = NULL, *msginfo_b = NULL;
1105
        gint ret;
1106
1107
        gtk_tree_model_get(model, a, COL_MSGINFO, &msginfo_a, -1);
1108
        gtk_tree_model_get(model, b, COL_MSGINFO, &msginfo_b, -1);
1109
1110
        if (!msginfo_a || !msginfo_b)
1111
                return 0;
1112
1113
        if (!msginfo_a->subject)
1114
                return -(msginfo_b->subject != NULL);
1115
        if (!msginfo_b->subject)
1116
                return (msginfo_a->subject != NULL);
1117
1118
        ret = subject_compare_for_sort(msginfo_a->subject, msginfo_b->subject);
1119
        return (ret != 0) ? ret : (msginfo_a->date_t - msginfo_b->date_t);
1120
}
1121
1122
static gint query_search_cmp_by_from(GtkTreeModel *model,
1123
                                     GtkTreeIter *a, GtkTreeIter *b,
1124
                                     gpointer data)
1125
{
1126
        MsgInfo *msginfo_a = NULL, *msginfo_b = NULL;
1127
        gint ret;
1128
1129
        gtk_tree_model_get(model, a, COL_MSGINFO, &msginfo_a, -1);
1130
        gtk_tree_model_get(model, b, COL_MSGINFO, &msginfo_b, -1);
1131
1132
        if (!msginfo_a || !msginfo_b)
1133
                return 0;
1134
1135
        if (!msginfo_a->fromname)
1136
                return -(msginfo_b->fromname != NULL);
1137
        if (!msginfo_b->fromname)
1138
                return (msginfo_a->fromname != NULL);
1139
1140
        ret = g_ascii_strcasecmp(msginfo_a->fromname, msginfo_b->fromname);
1141
        return (ret != 0) ? ret : (msginfo_a->date_t - msginfo_b->date_t);
1142
}
1143
1144
static gint query_search_cmp_by_date(GtkTreeModel *model,
1145
                                     GtkTreeIter *a, GtkTreeIter *b,
1146
                                     gpointer data)
1147
{
1148
        MsgInfo *msginfo_a = NULL, *msginfo_b = NULL;
1149
1150
        gtk_tree_model_get(model, a, COL_MSGINFO, &msginfo_a, -1);
1151
        gtk_tree_model_get(model, b, COL_MSGINFO, &msginfo_b, -1);
1152
1153
        if (!msginfo_a || !msginfo_b)
1154
                return 0;
1155
1156
        return msginfo_a->date_t - msginfo_b->date_t;
1157
}