Statistics
| Revision:

root / src / quick_search.c @ 3279

History | View | Annotate | Download (12.7 KB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2011 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
#include "defs.h"
21

    
22
#include <glib.h>
23
#include <glib/gi18n.h>
24
#include <gdk/gdkkeysyms.h>
25
#include <gtk/gtkwidget.h>
26
#include <gtk/gtkhbox.h>
27
#include <gtk/gtkvbox.h>
28
#include <gtk/gtkoptionmenu.h>
29
#include <gtk/gtkimage.h>
30
#include <gtk/gtkentry.h>
31

    
32
#include "summaryview.h"
33
#include "quick_search.h"
34
#include "filter.h"
35
#include "procheader.h"
36
#include "menu.h"
37
#include "addressbook.h"
38

    
39
static const struct {
40
        QSearchCondType type;
41
        FilterCondType ftype;
42
} qsearch_cond_types[] = {
43
        {QS_ALL,        -1},
44
        {QS_UNREAD,        FLT_COND_UNREAD},
45
        {QS_MARK,        FLT_COND_MARK},
46
        {QS_CLABEL,        FLT_COND_COLOR_LABEL},
47
        {QS_MIME,        FLT_COND_MIME},
48
        {QS_W1DAY,        -1},
49
        {QS_LAST5,        -1},
50
        {QS_LAST7,        -1},
51
        {QS_IN_ADDRESSBOOK,        -1},
52
        {QS_LAST30,        -1}
53
};
54

    
55
static GdkColor dim_color = {0, COLOR_DIM, COLOR_DIM, COLOR_DIM};
56

    
57
static void menu_activated                (GtkWidget        *menuitem,
58
                                         QuickSearch        *qsearch);
59
static gboolean entry_focus_in                (GtkWidget        *entry,
60
                                         GdkEventFocus        *event,
61
                                         QuickSearch        *qsearch);
62
static gboolean entry_focus_out                (GtkWidget        *entry,
63
                                         GdkEventFocus        *event,
64
                                         QuickSearch        *qsearch);
65
static void entry_changed                (GtkWidget        *entry,
66
                                         QuickSearch        *qsearch);
67
static void entry_activated                (GtkWidget        *entry,
68
                                         QuickSearch        *qsearch);
69
static gboolean entry_key_pressed        (GtkWidget        *treeview,
70
                                         GdkEventKey        *event,
71
                                         QuickSearch        *qsearch);
72
static void clear_clicked                (GtkWidget        *button,
73
                                         QuickSearch        *qsearch);
74

    
75

    
76
QuickSearch *quick_search_create(SummaryView *summaryview)
77
{
78
        QuickSearch *qsearch;
79
        GtkWidget *hbox;
80
        GtkWidget *optmenu;
81
        GtkWidget *menu;
82
        GtkWidget *menuitem;
83
        GtkWidget *hbox2;
84
        GtkWidget *label;
85
        GtkWidget *entry;
86
        GtkTooltips *tip;
87
        GtkWidget *vbox;
88
        GtkWidget *clear_btn;
89
        GtkWidget *image;
90
        GtkWidget *status_label;
91

    
92
        qsearch = g_new0(QuickSearch, 1);
93

    
94
        hbox = gtk_hbox_new(FALSE, 0);
95
        gtk_container_set_border_width(GTK_CONTAINER(hbox), 2);
96

    
97
        optmenu = gtk_option_menu_new();
98
        gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0);
99

    
100
#define COND_MENUITEM_ADD(str, action)                                        \
101
{                                                                        \
102
        MENUITEM_ADD(menu, menuitem, str, action);                        \
103
        g_signal_connect(G_OBJECT(menuitem), "activate",                \
104
                         G_CALLBACK(menu_activated), qsearch);                \
105
}
106

    
107
        menu = gtk_menu_new();
108
        COND_MENUITEM_ADD(_("All"), QS_ALL);
109
        COND_MENUITEM_ADD(_("Unread"), QS_UNREAD);
110
        COND_MENUITEM_ADD(_("Marked"), QS_MARK);
111
        COND_MENUITEM_ADD(_("Have color label"), QS_CLABEL);
112
        COND_MENUITEM_ADD(_("Have attachment"), QS_MIME);
113
        MENUITEM_ADD(menu, menuitem, NULL, 0);
114
        COND_MENUITEM_ADD(_("Within 1 day"), QS_W1DAY);
115
        COND_MENUITEM_ADD(_("Last 5 days"), QS_LAST5);
116
        COND_MENUITEM_ADD(_("Last 7 days"), QS_LAST7);
117
        COND_MENUITEM_ADD(_("Last 30 days"), QS_LAST30);
118
        MENUITEM_ADD(menu, menuitem, NULL, 0);
119
        COND_MENUITEM_ADD(_("In addressbook"), QS_IN_ADDRESSBOOK);
120
        gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu);
121

    
122
#undef COND_MENUITEM_ADD
123

    
124
        hbox2 = gtk_hbox_new(FALSE, 0);
125
        gtk_widget_set_size_request(hbox2, 8, -1);
126
        gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
127

    
128
        label = gtk_label_new(_("Search:"));
129
        gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
130

    
131
        hbox2 = gtk_hbox_new(FALSE, 0);
132
        gtk_widget_set_size_request(hbox2, 4, -1);
133
        gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
134

    
135
        entry = gtk_entry_new();
136
        gtk_widget_set_size_request(entry, 200, -1);
137
        gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
138
        g_signal_connect(G_OBJECT(entry), "focus-in-event",
139
                         G_CALLBACK(entry_focus_in), qsearch);
140
        g_signal_connect(G_OBJECT(entry), "focus-out-event",
141
                         G_CALLBACK(entry_focus_out), qsearch);
142
        g_signal_connect(G_OBJECT(entry), "changed",
143
                         G_CALLBACK(entry_changed), qsearch);
144
        g_signal_connect(G_OBJECT(entry), "activate",
145
                         G_CALLBACK(entry_activated), qsearch);
146
        g_signal_connect(G_OBJECT(entry), "key_press_event",
147
                         G_CALLBACK(entry_key_pressed), qsearch);
148

    
149
        tip = gtk_tooltips_new();
150
        gtk_tooltips_set_tip(tip, entry, _("Search for Subject or From"), NULL);
151

    
152
        hbox2 = gtk_hbox_new(FALSE, 0);
153
        gtk_widget_set_size_request(hbox2, 2, -1);
154
        gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
155

    
156
        vbox = gtk_vbox_new(FALSE, 0);
157
        gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
158

    
159
        clear_btn = gtk_button_new();
160
        gtk_button_set_relief(GTK_BUTTON(clear_btn), GTK_RELIEF_NONE);
161
        gtk_widget_set_size_request(clear_btn, 20, 20);
162
        image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
163
        gtk_container_add(GTK_CONTAINER(clear_btn), image);
164
        GTK_WIDGET_UNSET_FLAGS(clear_btn, GTK_CAN_FOCUS);
165
        gtk_box_pack_start(GTK_BOX(vbox), clear_btn, TRUE, FALSE, 0);
166
        g_signal_connect(G_OBJECT(clear_btn), "clicked",
167
                         G_CALLBACK(clear_clicked), qsearch);
168

    
169
        hbox2 = gtk_hbox_new(FALSE, 0);
170
        gtk_widget_set_size_request(hbox2, 4, -1);
171
        gtk_box_pack_start(GTK_BOX(hbox), hbox2, FALSE, FALSE, 0);
172

    
173
        status_label = gtk_label_new("");
174
        gtk_box_pack_start(GTK_BOX(hbox), status_label, FALSE, FALSE, 0);
175

    
176
        qsearch->hbox = hbox;
177
        qsearch->optmenu = optmenu;
178
        qsearch->menu = menu;
179
        qsearch->label = label;
180
        qsearch->entry = entry;
181
        qsearch->clear_btn = clear_btn;
182
        qsearch->status_label = status_label;
183
        qsearch->summaryview = summaryview;
184
        summaryview->qsearch = qsearch;
185
        qsearch->entry_entered = FALSE;
186

    
187
        gtk_widget_show_all(hbox);
188
        gtk_widget_hide(clear_btn);
189

    
190
        entry_focus_out(entry, NULL, qsearch);
191

    
192
        return qsearch;
193
}
194

    
195
void quick_search_clear_entry(QuickSearch *qsearch)
196
{
197
        qsearch->entry_entered = FALSE;
198
        if (GTK_WIDGET_HAS_FOCUS(qsearch->entry))
199
                entry_focus_in(qsearch->entry, NULL, qsearch);
200
        else
201
                entry_focus_out(qsearch->entry, NULL, qsearch);
202

    
203
        gtk_label_set_text(GTK_LABEL(qsearch->status_label), "");
204
        gtk_widget_hide(qsearch->clear_btn);
205
}
206

    
207
GSList *quick_search_filter(QuickSearch *qsearch, QSearchCondType type,
208
                           const gchar *key)
209
{
210
        SummaryView *summaryview = qsearch->summaryview;
211
        FilterCondType ftype;
212
        FilterRule *status_rule = NULL;
213
        FilterRule *rule = NULL;
214
        FilterCond *cond;
215
        FilterInfo fltinfo;
216
        GSList *cond_list = NULL;
217
        GSList *rule_list = NULL;
218
        GSList *flt_mlist = NULL;
219
        GSList *cur;
220
        gint count = 0, total = 0;
221
        gchar status_text[1024];
222
        gboolean dmode;
223

    
224
        if (!summaryview->all_mlist)
225
                return NULL;
226

    
227
        debug_print("quick_search_filter: filtering summary (type: %d)\n",
228
                    type);
229

    
230
        switch (type) {
231
        case QS_UNREAD:
232
        case QS_MARK:
233
        case QS_CLABEL:
234
        case QS_MIME:
235
                ftype = qsearch_cond_types[type].ftype;
236
                cond = filter_cond_new(ftype, 0, 0, NULL, NULL);
237
                cond_list = g_slist_append(cond_list, cond);
238
                status_rule = filter_rule_new("Status filter rule", FLT_OR,
239
                                              cond_list, NULL);
240
                break;
241
        case QS_W1DAY:
242
                cond = filter_cond_new(FLT_COND_AGE_GREATER, 0, FLT_NOT_MATCH,
243
                                       NULL, "1");
244
                cond_list = g_slist_append(cond_list, cond);
245
                status_rule = filter_rule_new("Status filter rule", FLT_OR,
246
                                              cond_list, NULL);
247
                break;
248
        case QS_LAST5:
249
                cond = filter_cond_new(FLT_COND_AGE_GREATER, 0, FLT_NOT_MATCH,
250
                                       NULL, "5");
251
                cond_list = g_slist_append(cond_list, cond);
252
                status_rule = filter_rule_new("Status filter rule", FLT_OR,
253
                                              cond_list, NULL);
254
                break;
255
        case QS_LAST7:
256
                cond = filter_cond_new(FLT_COND_AGE_GREATER, 0, FLT_NOT_MATCH,
257
                                       NULL, "7");
258
                cond_list = g_slist_append(cond_list, cond);
259
                status_rule = filter_rule_new("Status filter rule", FLT_OR,
260
                                              cond_list, NULL);
261
                break;
262
        case QS_LAST30:
263
                cond = filter_cond_new(FLT_COND_AGE_GREATER, 0, FLT_NOT_MATCH,
264
                                       NULL, "30");
265
                cond_list = g_slist_append(cond_list, cond);
266
                status_rule = filter_rule_new("Status filter rule", FLT_OR,
267
                                              cond_list, NULL);
268
                break;
269
        case QS_IN_ADDRESSBOOK:
270
                cond = filter_cond_new(FLT_COND_HEADER, FLT_IN_ADDRESSBOOK, 0,
271
                                       "From", NULL);
272
                cond_list = g_slist_append(cond_list, cond);
273
                status_rule = filter_rule_new("Status filter rule", FLT_OR,
274
                                              cond_list, NULL);
275
                break;
276
        case QS_ALL:
277
        default:
278
                break;
279
        }
280

    
281
        if (key) {
282
                gchar **keys;
283
                gint i;
284

    
285
                keys = g_strsplit(key, " ", -1);
286
                for (i = 0; keys[i] != NULL; i++) {
287
                        cond_list = NULL;
288

    
289
                        if (keys[i] == '\0')
290
                                continue;
291

    
292
                        cond = filter_cond_new(FLT_COND_HEADER, FLT_CONTAIN, 0,
293
                                               "Subject", keys[i]);
294
                        cond_list = g_slist_append(cond_list, cond);
295
                        cond = filter_cond_new(FLT_COND_HEADER, FLT_CONTAIN, 0,
296
                                               "From", keys[i]);
297
                        cond_list = g_slist_append(cond_list, cond);
298
                        if (FOLDER_ITEM_IS_SENT_FOLDER(summaryview->folder_item)) {
299
                                cond = filter_cond_new(FLT_COND_TO_OR_CC, FLT_CONTAIN,
300
                                                       0, NULL, keys[i]);
301
                                cond_list = g_slist_append(cond_list, cond);
302
                        }
303

    
304
                        if (cond_list) {
305
                                rule = filter_rule_new("Quick search rule",
306
                                                       FLT_OR, cond_list, NULL);
307
                                rule_list = g_slist_append(rule_list, rule);
308
                        }
309
                }
310
                g_strfreev(keys);
311
        }
312

    
313
        memset(&fltinfo, 0, sizeof(FilterInfo));
314
        dmode = get_debug_mode();
315
        set_debug_mode(FALSE);
316

    
317
        for (cur = summaryview->all_mlist; cur != NULL; cur = cur->next) {
318
                MsgInfo *msginfo = (MsgInfo *)cur->data;
319
                GSList *hlist = NULL;
320
                gboolean matched = TRUE;
321

    
322
                total++;
323

    
324
                if (status_rule) {
325
                        if (type == QS_IN_ADDRESSBOOK)
326
                                hlist = procheader_get_header_list_from_msginfo
327
                                        (msginfo);
328
                        if (!filter_match_rule(status_rule, msginfo, hlist,
329
                                               &fltinfo)) {
330
                                if (hlist)
331
                                        procheader_header_list_destroy(hlist);
332
                                continue;
333
                        }
334
                }
335

    
336
                if (rule_list) {
337
                        GSList *rcur;
338

    
339
                        if (!hlist)
340
                                hlist = procheader_get_header_list_from_msginfo
341
                                        (msginfo);
342

    
343
                        /* AND keyword match */
344
                        for (rcur = rule_list; rcur != NULL; rcur = rcur->next) {
345
                                rule = (FilterRule *)rcur->data;
346
                                if (!filter_match_rule(rule, msginfo, hlist, &fltinfo)) {
347
                                        matched = FALSE;
348
                                        break;
349
                                }
350
                        }
351
                }
352

    
353
                if (matched) {
354
                        flt_mlist = g_slist_prepend(flt_mlist, msginfo);
355
                        count++;
356
                }
357

    
358
                if (hlist)
359
                        procheader_header_list_destroy(hlist);
360
        }
361
        flt_mlist = g_slist_reverse(flt_mlist);
362

    
363
        set_debug_mode(dmode);
364

    
365
        if (status_rule || rule) {
366
                if (count > 0)
367
                        g_snprintf(status_text, sizeof(status_text),
368
                                   _("%1$d in %2$d matched"), count, total);
369
                else
370
                        g_snprintf(status_text, sizeof(status_text),
371
                                   _("No messages matched"));
372
                gtk_label_set_text(GTK_LABEL(qsearch->status_label),
373
                                   status_text);
374
        } else
375
                gtk_label_set_text(GTK_LABEL(qsearch->status_label), "");
376

    
377
        filter_rule_list_free(rule_list);
378
        filter_rule_free(status_rule);
379

    
380
        return flt_mlist;
381
}
382

    
383
static void menu_activated(GtkWidget *menuitem, QuickSearch *qsearch)
384
{
385
        summary_qsearch(qsearch->summaryview);
386
}
387

    
388
static gboolean entry_focus_in(GtkWidget *entry, GdkEventFocus *event,
389
                               QuickSearch *qsearch)
390
{
391
        if (!qsearch->entry_entered) {
392
                g_signal_handlers_block_by_func(entry, entry_changed, qsearch);
393
                gtk_entry_set_text(GTK_ENTRY(entry), "");
394
                gtk_widget_modify_text(entry, GTK_STATE_NORMAL, NULL);
395
                g_signal_handlers_unblock_by_func(entry, entry_changed, qsearch);
396
        }
397

    
398
        return FALSE;
399
}
400

    
401
static gboolean entry_focus_out(GtkWidget *entry, GdkEventFocus *event,
402
                                QuickSearch *qsearch)
403
{
404
        if (!qsearch->entry_entered) {
405
                g_signal_handlers_block_by_func(entry, entry_changed, qsearch);
406
                gtk_widget_modify_text(entry, GTK_STATE_NORMAL, &dim_color);
407
                gtk_entry_set_text(GTK_ENTRY(entry), _("Search for Subject or From"));
408
                g_signal_handlers_unblock_by_func(entry, entry_changed, qsearch);
409
        }
410

    
411
        return FALSE;
412
}
413

    
414
static void entry_changed(GtkWidget *entry, QuickSearch *qsearch)
415
{
416
        const gchar *text;
417

    
418
        text = gtk_entry_get_text(GTK_ENTRY(entry));
419
        if (text && *text != '\0') {
420
                gtk_widget_show(qsearch->clear_btn);
421
                qsearch->entry_entered = TRUE;
422
        } else {
423
                gtk_widget_hide(qsearch->clear_btn);
424
                qsearch->entry_entered = FALSE;
425
        }
426
}
427

    
428
static void entry_activated(GtkWidget *entry, QuickSearch *qsearch)
429
{
430
        gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
431
        summary_qsearch(qsearch->summaryview);
432
}
433

    
434
static gboolean entry_key_pressed(GtkWidget *treeview, GdkEventKey *event,
435
                                  QuickSearch *qsearch)
436
{
437
        if (event && event->keyval == GDK_Escape) {
438
                summary_qsearch_clear_entry(qsearch->summaryview);
439
                return TRUE;
440
        }
441
        return FALSE;
442
}
443

    
444
static void clear_clicked(GtkWidget *button, QuickSearch *qsearch)
445
{
446
        summary_qsearch_clear_entry(qsearch->summaryview);
447
}