11fa388831a4489ac87619a91e4db7f50899ee27
[apps/madmutt.git] / lib-ui / query.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <string.h>
15 #include <stdlib.h>
16 #include <ctype.h>
17
18 #include <lib-lib/lib-lib.h>
19 #include <lib-sys/unix.h>
20
21 #include <lib-ui/menu.h>
22 #include <lib-ui/curses.h>
23
24 #include "mutt.h"
25 #include "alias.h"
26 #include "mutt_idna.h"
27 #include "sort.h"
28
29 typedef struct query {
30   address_t *addr;
31   char *name;
32   char *other;
33   struct query *next;
34 } QUERY;
35
36 typedef struct entry {
37   int tagged;
38   QUERY *data;
39 } ENTRY;
40
41 static struct mapping_t QueryHelp[] = {
42   {N_("Exit"), OP_EXIT},
43   {N_("Mail"), OP_MAIL},
44   {N_("New Query"), OP_QUERY},
45   {N_("Make Alias"), OP_CREATE_ALIAS},
46   {N_("Search"), OP_SEARCH},
47   {N_("Help"), OP_HELP},
48   {NULL, OP_NULL}
49 };
50
51 /* Variables for outsizing output format */
52 static int FirstColumn;
53 static int SecondColumn;
54
55 static void query_menu (char *buf, ssize_t buflen, QUERY * results,
56                         int retbuf);
57
58 static address_t *result_to_addr (QUERY * r)
59 {
60   static address_t *tmp;
61
62   tmp = address_list_dup (r->addr);
63
64   if (!tmp->next && !tmp->personal)
65     tmp->personal = m_strdup(r->name);
66
67   mutt_addrlist_to_idna (tmp, NULL);
68   return tmp;
69 }
70
71 static QUERY *run_query (char *s, int quiet)
72 {
73   FILE *fp;
74   QUERY *first = NULL;
75   QUERY *cur = NULL;
76   char cmd[_POSIX_PATH_MAX];
77   char *buf = NULL;
78   ssize_t buflen;
79   int dummy = 0;
80   char msg[STRING];
81   char *p;
82   pid_t thepid;
83   int l;
84
85
86   mutt_expand_file_fmt (cmd, sizeof (cmd), QueryCmd, s);
87
88   if ((thepid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0) {
89     return 0;
90   }
91   if (!quiet)
92     mutt_message _("Waiting for response...");
93
94   fgets (msg, sizeof (msg), fp);
95   if ((p = strrchr (msg, '\n')))
96     *p = '\0';
97   while ((buf = mutt_read_line (buf, &buflen, fp, &dummy)) != NULL) {
98     if ((p = strtok (buf, "\t\n"))) {
99       if (first == NULL) {
100         FirstColumn = 0;
101         SecondColumn = 0;
102         first = p_new(QUERY, 1);
103         cur = first;
104       }
105       else {
106         cur->next = p_new(QUERY, 1);
107         cur = cur->next;
108       }
109
110       l = mutt_strwidth (p);
111       if (l > SecondColumn)
112         SecondColumn = l;
113
114       cur->addr = rfc822_parse_adrlist (cur->addr, p);
115       p = strtok (NULL, "\t\n");
116       if (p) {
117         l = mutt_strwidth (p);
118         if (l > FirstColumn)
119           FirstColumn = l;
120         cur->name = m_strdup(p);
121         p = strtok (NULL, "\t\n");
122         if (p) {
123           cur->other = m_strdup(p);
124         }
125       }
126     }
127   }
128   p_delete(&buf);
129   fclose (fp);
130   if (mutt_wait_filter (thepid)) {
131     if (!quiet)
132       mutt_error ("%s", msg);
133   }
134   else {
135     if (!quiet)
136       mutt_message ("%s", msg);
137   }
138
139   return first;
140 }
141
142 static int query_search (MUTTMENU * m, regex_t * re, int n)
143 {
144   ENTRY *table = (ENTRY *) m->data;
145
146   if (table[n].data->name && !regexec (re, table[n].data->name, 0, NULL, 0))
147     return 0;
148   if (table[n].data->other && !regexec (re, table[n].data->other, 0, NULL, 0))
149     return 0;
150   if (table[n].data->addr) {
151     if (table[n].data->addr->personal &&
152         !regexec (re, table[n].data->addr->personal, 0, NULL, 0))
153       return 0;
154     if (table[n].data->addr->mailbox &&
155         !regexec (re, table[n].data->addr->mailbox, 0, NULL, 0))
156       return 0;
157   }
158
159   return REG_NOMATCH;
160 }
161
162 /* This is the callback routine from mutt_menuLoop() which is used to generate
163  * a menu entry for the requested item number.
164  */
165 #define QUERY_MIN_COLUMN_LENGHT 20      /* Must be < 70/2 */
166 static void query_entry (char *s, ssize_t slen, MUTTMENU * m, int num)
167 {
168   ENTRY *table = (ENTRY *) m->data;
169   char buf2[SHORT_STRING], buf[SHORT_STRING] = "";
170
171   /* need a query format ... hard coded constants are not good */
172   while (FirstColumn + SecondColumn > 70) {
173     FirstColumn = FirstColumn * 3 / 4;
174     SecondColumn = SecondColumn * 3 / 4;
175     if (FirstColumn < QUERY_MIN_COLUMN_LENGHT)
176       FirstColumn = QUERY_MIN_COLUMN_LENGHT;
177     if (SecondColumn < QUERY_MIN_COLUMN_LENGHT)
178       SecondColumn = QUERY_MIN_COLUMN_LENGHT;
179   }
180
181   rfc822_write_address (buf, sizeof (buf), table[num].data->addr, 1);
182
183   mutt_format_string (buf2, sizeof (buf2),
184                       FirstColumn + 2, FirstColumn + 2,
185                       0, ' ', table[num].data->name,
186                       m_strlen(table[num].data->name), 0);
187
188   snprintf (s, slen, " %c %3d %s %-*.*s %s",
189             table[num].tagged ? '*' : ' ',
190             num + 1,
191             buf2,
192             SecondColumn + 2,
193             SecondColumn + 2, buf, NONULL (table[num].data->other));
194 }
195
196 static int query_tag (MUTTMENU * menu, int n, int m)
197 {
198   ENTRY *cur = &((ENTRY *) menu->data)[n];
199   int ot = cur->tagged;
200
201   cur->tagged = m >= 0 ? m : !cur->tagged;
202   return cur->tagged - ot;
203 }
204
205 int mutt_query_complete (char *buf, ssize_t buflen)
206 {
207   QUERY *results = NULL;
208   address_t *tmpa;
209
210   if (!QueryCmd) {
211     mutt_error _("Query command not defined.");
212
213     return 0;
214   }
215
216   results = run_query (buf, 1);
217   if (results) {
218     /* only one response? */
219     if (results->next == NULL) {
220       tmpa = result_to_addr (results);
221       mutt_addrlist_to_local (tmpa);
222       buf[0] = '\0';
223       rfc822_write_address (buf, buflen, tmpa, 0);
224       address_list_wipe(&tmpa);
225       mutt_clear_error ();
226       return (0);
227     }
228     /* multiple results, choose from query menu */
229     query_menu (buf, buflen, results, 1);
230   }
231   return (0);
232 }
233
234 void mutt_query_menu (char *buf, ssize_t buflen)
235 {
236   if (!QueryCmd) {
237     mutt_error _("Query command not defined.");
238
239     return;
240   }
241
242   if (buf == NULL) {
243     char buffer[STRING] = "";
244
245     query_menu (buffer, sizeof (buffer), NULL, 0);
246   }
247   else {
248     query_menu (buf, buflen, NULL, 1);
249   }
250 }
251
252 static void query_menu (char *buf, ssize_t buflen, QUERY * results, int retbuf)
253 {
254   MUTTMENU *menu;
255   HEADER *msg = NULL;
256   ENTRY *QueryTable = NULL;
257   QUERY *queryp = NULL;
258   int i, done = 0;
259   int op;
260   char helpstr[SHORT_STRING];
261   char title[STRING];
262
263   snprintf (title, sizeof (title), _("Query")); /* FIXME */
264
265   menu = mutt_new_menu ();
266   menu->make_entry = query_entry;
267   menu->search = query_search;
268   menu->tag = query_tag;
269   menu->menu = MENU_QUERY;
270   menu->title = title;
271   menu->help =
272     mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY, QueryHelp);
273
274   if (results == NULL) {
275     /* Prompt for Query */
276     if (mutt_get_field (_("Query: "), buf, buflen, 0) == 0 && buf[0]) {
277       results = run_query (buf, 0);
278     }
279   }
280
281   if (results) {
282     snprintf (title, sizeof (title), _("Query '%s'"), buf);
283
284     /* count the number of results */
285     for (queryp = results; queryp; queryp = queryp->next)
286       menu->max++;
287
288     menu->data = QueryTable = p_new(ENTRY, menu->max);
289
290     for (i = 0, queryp = results; queryp; queryp = queryp->next, i++)
291       QueryTable[i].data = queryp;
292
293     while (!done) {
294       switch ((op = mutt_menuLoop (menu))) {
295       case OP_QUERY_APPEND:
296       case OP_QUERY:
297         if (mutt_get_field (_("Query: "), buf, buflen, 0) == 0 && buf[0]) {
298           QUERY *newresults = NULL;
299
300           newresults = run_query (buf, 0);
301
302           menu->redraw = REDRAW_FULL;
303           if (newresults) {
304             snprintf (title, sizeof (title), _("Query '%s'"), buf);
305
306             if (op == OP_QUERY) {
307               queryp = results;
308               while (queryp) {
309                 address_list_wipe(&queryp->addr);
310                 p_delete(&queryp->name);
311                 p_delete(&queryp->other);
312                 results = queryp->next;
313                 p_delete(&queryp);
314                 queryp = results;
315               }
316               results = newresults;
317               p_delete(&QueryTable);
318             }
319             else {
320               /* append */
321               for (queryp = results; queryp->next; queryp = queryp->next);
322
323               queryp->next = newresults;
324             }
325
326
327             menu->current = 0;
328             mutt_menuDestroy (&menu);
329             menu = mutt_new_menu ();
330             menu->make_entry = query_entry;
331             menu->search = query_search;
332             menu->tag = query_tag;
333             menu->menu = MENU_QUERY;
334             menu->title = title;
335             menu->help =
336               mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY,
337                                  QueryHelp);
338
339             /* count the number of results */
340             for (queryp = results; queryp; queryp = queryp->next)
341               menu->max++;
342
343             if (op == OP_QUERY) {
344               menu->data = QueryTable = p_new(ENTRY, menu->max);
345
346               for (i = 0, queryp = results; queryp;
347                    queryp = queryp->next, i++)
348                 QueryTable[i].data = queryp;
349             }
350             else {
351               int doclear = 0;
352
353               /* append */
354               p_realloc(&QueryTable, menu->max);
355
356               menu->data = QueryTable;
357
358               for (i = 0, queryp = results; queryp;
359                    queryp = queryp->next, i++) {
360                 /* once we hit new entries, clear/init the tag */
361                 if (queryp == newresults)
362                   doclear = 1;
363
364                 QueryTable[i].data = queryp;
365                 if (doclear)
366                   QueryTable[i].tagged = 0;
367               }
368             }
369           }
370         }
371         break;
372
373       case OP_CREATE_ALIAS:
374         if (menu->tagprefix) {
375           address_t *naddr = NULL;
376
377           for (i = 0; i < menu->max; i++)
378             if (QueryTable[i].tagged) {
379               address_list_append(&naddr, result_to_addr(QueryTable[i].data));
380             }
381
382           mutt_create_alias (NULL, naddr);
383         }
384         else {
385           address_t *a = result_to_addr (QueryTable[menu->current].data);
386
387           mutt_create_alias (NULL, a);
388           address_list_wipe(&a);
389         }
390         break;
391
392       case OP_GENERIC_SELECT_ENTRY:
393         if (retbuf) {
394           done = 2;
395           break;
396         }
397         /* fall through to OP_MAIL */
398
399       case OP_MAIL:
400         msg = header_new();
401         msg->env = envelope_new();
402         if (!menu->tagprefix) {
403           msg->env->to = result_to_addr (QueryTable[menu->current].data);
404         }
405         else {
406           for (i = 0; i < menu->max; i++)
407             if (QueryTable[i].tagged) {
408               address_list_append(&msg->env->to, result_to_addr(QueryTable[i].data));
409             }
410         }
411         ci_send_message (0, msg, NULL, Context, NULL);
412         menu->redraw = REDRAW_FULL;
413         break;
414
415       case OP_EXIT:
416         done = 1;
417         break;
418       }
419     }
420
421     /* if we need to return the selected entries */
422     if (retbuf && (done == 2)) {
423       int tagged = 0;
424       ssize_t curpos = 0;
425
426       p_clear(buf, buflen);
427
428       /* check for tagged entries */
429       for (i = 0; i < menu->max; i++) {
430         if (QueryTable[i].tagged) {
431           if (curpos == 0) {
432             address_t *tmpa = result_to_addr (QueryTable[i].data);
433
434             mutt_addrlist_to_local (tmpa);
435             tagged = 1;
436             rfc822_write_address (buf, buflen, tmpa, 0);
437             curpos = m_strlen(buf);
438             address_list_wipe(&tmpa);
439           }
440           else if (curpos + 2 < buflen) {
441             address_t *tmpa = result_to_addr (QueryTable[i].data);
442
443             mutt_addrlist_to_local (tmpa);
444             strcat (buf, ", "); /* __STRCAT_CHECKED__ */
445             rfc822_write_address ((char *) buf + curpos + 1,
446                                   buflen - curpos - 1, tmpa, 0);
447             curpos = m_strlen(buf);
448             address_list_wipe(&tmpa);
449           }
450         }
451       }
452       /* then enter current message */
453       if (!tagged) {
454         address_t *tmpa = result_to_addr (QueryTable[menu->current].data);
455
456         mutt_addrlist_to_local (tmpa);
457         rfc822_write_address (buf, buflen, tmpa, 0);
458         address_list_wipe(&tmpa);
459       }
460
461     }
462
463     queryp = results;
464     while (queryp) {
465       address_list_wipe(&queryp->addr);
466       p_delete(&queryp->name);
467       p_delete(&queryp->other);
468       results = queryp->next;
469       p_delete(&queryp);
470       queryp = results;
471     }
472     p_delete(&QueryTable);
473
474     /* tell whoever called me to redraw the screen when I return */
475     set_option (OPTNEEDREDRAW);
476   }
477
478   mutt_menuDestroy (&menu);
479 }