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