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