Statistics
| Revision:

root / src / query_search.c @ 3063

History | View | Annotate | Download (34.4 kB)

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