1 /**
2  * @file
3  * Manipulate the flags in an email header
4  *
5  * @authors
6  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page neo_flags Manipulate the flags in an email header
25  *
26  * Manipulate the flags in an email header
27  */
28 
29 #include "config.h"
30 #include <stddef.h>
31 #include <stdbool.h>
32 #include "mutt/lib.h"
33 #include "config/lib.h"
34 #include "email/lib.h"
35 #include "core/lib.h"
36 #include "gui/lib.h"
37 #include "mutt.h"
38 #include "index/lib.h"
39 #include "keymap.h"
40 #include "mutt_thread.h"
41 #include "protos.h"
42 
43 /**
44  * mutt_set_flag_update - Set a flag on an email
45  * @param m        Mailbox
46  * @param e        Email
47  * @param flag     Flag to set, e.g. #MUTT_DELETE
48  * @param bf       true: set the flag; false: clear the flag
49  * @param upd_mbox true: update the Mailbox
50  */
mutt_set_flag_update(struct Mailbox * m,struct Email * e,enum MessageType flag,bool bf,bool upd_mbox)51 void mutt_set_flag_update(struct Mailbox *m, struct Email *e,
52                           enum MessageType flag, bool bf, bool upd_mbox)
53 {
54   if (!m || !e)
55     return;
56 
57   bool changed = e->changed;
58   int deleted = m->msg_deleted;
59   int tagged = m->msg_tagged;
60   int flagged = m->msg_flagged;
61   int update = false;
62 
63   if (m->readonly && (flag != MUTT_TAG))
64     return; /* don't modify anything if we are read-only */
65 
66   switch (flag)
67   {
68     case MUTT_DELETE:
69 
70       if (!(m->rights & MUTT_ACL_DELETE))
71         return;
72 
73       if (bf)
74       {
75         const bool c_flag_safe = cs_subset_bool(NeoMutt->sub, "flag_safe");
76         if (!e->deleted && !m->readonly && (!e->flagged || !c_flag_safe))
77         {
78           e->deleted = true;
79           update = true;
80           if (upd_mbox)
81             m->msg_deleted++;
82 #ifdef USE_IMAP
83           /* deleted messages aren't treated as changed elsewhere so that the
84            * purge-on-sync option works correctly. This isn't applicable here */
85           if (m->type == MUTT_IMAP)
86           {
87             e->changed = true;
88             if (upd_mbox)
89               m->changed = true;
90           }
91 #endif
92         }
93       }
94       else if (e->deleted)
95       {
96         e->deleted = false;
97         update = true;
98         if (upd_mbox)
99           m->msg_deleted--;
100 #ifdef USE_IMAP
101         /* see my comment above */
102         if (m->type == MUTT_IMAP)
103         {
104           e->changed = true;
105           if (upd_mbox)
106             m->changed = true;
107         }
108 #endif
109         /* If the user undeletes a message which is marked as
110          * "trash" in the maildir folder on disk, the folder has
111          * been changed, and is marked accordingly.  However, we do
112          * _not_ mark the message itself changed, because trashing
113          * is checked in specific code in the maildir folder
114          * driver.  */
115         if ((m->type == MUTT_MAILDIR) && upd_mbox && e->trash)
116           m->changed = true;
117       }
118       break;
119 
120     case MUTT_PURGE:
121 
122       if (!(m->rights & MUTT_ACL_DELETE))
123         return;
124 
125       if (bf)
126       {
127         if (!e->purge && !m->readonly)
128           e->purge = true;
129       }
130       else if (e->purge)
131         e->purge = false;
132       break;
133 
134     case MUTT_NEW:
135 
136       if (!(m->rights & MUTT_ACL_SEEN))
137         return;
138 
139       if (bf)
140       {
141         if (e->read || e->old)
142         {
143           update = true;
144           e->old = false;
145           if (upd_mbox)
146             m->msg_new++;
147           if (e->read)
148           {
149             e->read = false;
150             if (upd_mbox)
151               m->msg_unread++;
152           }
153           e->changed = true;
154           if (upd_mbox)
155             m->changed = true;
156         }
157       }
158       else if (!e->read)
159       {
160         update = true;
161         if (!e->old)
162           if (upd_mbox)
163             m->msg_new--;
164         e->read = true;
165         if (upd_mbox)
166           m->msg_unread--;
167         e->changed = true;
168         if (upd_mbox)
169           m->changed = true;
170       }
171       break;
172 
173     case MUTT_OLD:
174 
175       if (!(m->rights & MUTT_ACL_SEEN))
176         return;
177 
178       if (bf)
179       {
180         if (!e->old)
181         {
182           update = true;
183           e->old = true;
184           if (!e->read)
185             if (upd_mbox)
186               m->msg_new--;
187           e->changed = true;
188           if (upd_mbox)
189             m->changed = true;
190         }
191       }
192       else if (e->old)
193       {
194         update = true;
195         e->old = false;
196         if (!e->read)
197           if (upd_mbox)
198             m->msg_new++;
199         e->changed = true;
200         if (upd_mbox)
201           m->changed = true;
202       }
203       break;
204 
205     case MUTT_READ:
206 
207       if (!(m->rights & MUTT_ACL_SEEN))
208         return;
209 
210       if (bf)
211       {
212         if (!e->read)
213         {
214           update = true;
215           e->read = true;
216           if (upd_mbox)
217             m->msg_unread--;
218           if (!e->old)
219             if (upd_mbox)
220               m->msg_new--;
221           e->changed = true;
222           if (upd_mbox)
223             m->changed = true;
224         }
225       }
226       else if (e->read)
227       {
228         update = true;
229         e->read = false;
230         if (upd_mbox)
231           m->msg_unread++;
232         if (!e->old)
233           if (upd_mbox)
234             m->msg_new++;
235         e->changed = true;
236         if (upd_mbox)
237           m->changed = true;
238       }
239       break;
240 
241     case MUTT_REPLIED:
242 
243       if (!(m->rights & MUTT_ACL_WRITE))
244         return;
245 
246       if (bf)
247       {
248         if (!e->replied)
249         {
250           update = true;
251           e->replied = true;
252           if (!e->read)
253           {
254             e->read = true;
255             if (upd_mbox)
256               m->msg_unread--;
257             if (!e->old)
258               if (upd_mbox)
259                 m->msg_new--;
260           }
261           e->changed = true;
262           if (upd_mbox)
263             m->changed = true;
264         }
265       }
266       else if (e->replied)
267       {
268         update = true;
269         e->replied = false;
270         e->changed = true;
271         if (upd_mbox)
272           m->changed = true;
273       }
274       break;
275 
276     case MUTT_FLAG:
277 
278       if (!(m->rights & MUTT_ACL_WRITE))
279         return;
280 
281       if (bf)
282       {
283         if (!e->flagged)
284         {
285           update = true;
286           e->flagged = bf;
287           if (upd_mbox)
288             m->msg_flagged++;
289           e->changed = true;
290           if (upd_mbox)
291             m->changed = true;
292         }
293       }
294       else if (e->flagged)
295       {
296         update = true;
297         e->flagged = false;
298         if (upd_mbox)
299           m->msg_flagged--;
300         e->changed = true;
301         if (upd_mbox)
302           m->changed = true;
303       }
304       break;
305 
306     case MUTT_TAG:
307       if (bf)
308       {
309         if (!e->tagged)
310         {
311           update = true;
312           e->tagged = true;
313           if (upd_mbox)
314             m->msg_tagged++;
315         }
316       }
317       else if (e->tagged)
318       {
319         update = true;
320         e->tagged = false;
321         if (upd_mbox)
322           m->msg_tagged--;
323       }
324       break;
325 
326     default:
327       break;
328   }
329 
330   if (update)
331   {
332     mutt_set_header_color(m, e);
333     struct EventMailbox ev_m = { m };
334     notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m);
335   }
336 
337   /* if the message status has changed, we need to invalidate the cached
338    * search results so that any future search will match the current status
339    * of this message and not what it was at the time it was last searched.  */
340   if (e->searched && ((changed != e->changed) || (deleted != m->msg_deleted) ||
341                       (tagged != m->msg_tagged) || (flagged != m->msg_flagged)))
342   {
343     e->searched = false;
344   }
345 }
346 
347 /**
348  * mutt_emails_set_flag - Set flag on messages
349  * @param m    Mailbox
350  * @param el   List of Emails to flag
351  * @param flag Flag to set, e.g. #MUTT_DELETE
352  * @param bf   true: set the flag; false: clear the flag
353  */
mutt_emails_set_flag(struct Mailbox * m,struct EmailList * el,enum MessageType flag,bool bf)354 void mutt_emails_set_flag(struct Mailbox *m, struct EmailList *el,
355                           enum MessageType flag, bool bf)
356 {
357   if (!m || !el || STAILQ_EMPTY(el))
358     return;
359 
360   struct EmailNode *en = NULL;
361   STAILQ_FOREACH(en, el, entries)
362   {
363     mutt_set_flag(m, en->email, flag, bf);
364   }
365 }
366 
367 /**
368  * mutt_thread_set_flag - Set a flag on an entire thread
369  * @param m         Mailbox
370  * @param e         Email
371  * @param flag      Flag to set, e.g. #MUTT_DELETE
372  * @param bf        true: set the flag; false: clear the flag
373  * @param subthread If true apply to all of the thread
374  * @retval  0 Success
375  * @retval -1 Failure
376  */
mutt_thread_set_flag(struct Mailbox * m,struct Email * e,enum MessageType flag,bool bf,bool subthread)377 int mutt_thread_set_flag(struct Mailbox *m, struct Email *e,
378                          enum MessageType flag, bool bf, bool subthread)
379 {
380   struct MuttThread *start = NULL;
381   struct MuttThread *cur = e->thread;
382 
383   if (!mutt_using_threads())
384   {
385     mutt_error(_("Threading is not enabled"));
386     return -1;
387   }
388 
389   if (!subthread)
390     while (cur->parent)
391       cur = cur->parent;
392   start = cur;
393 
394   if (cur->message && (cur != e->thread))
395     mutt_set_flag(m, cur->message, flag, bf);
396 
397   cur = cur->child;
398   if (!cur)
399     goto done;
400 
401   while (true)
402   {
403     if (cur->message && (cur != e->thread))
404       mutt_set_flag(m, cur->message, flag, bf);
405 
406     if (cur->child)
407       cur = cur->child;
408     else if (cur->next)
409       cur = cur->next;
410     else
411     {
412       while (!cur->next)
413       {
414         cur = cur->parent;
415         if (cur == start)
416           goto done;
417       }
418       cur = cur->next;
419     }
420   }
421 done:
422   cur = e->thread;
423   if (cur->message)
424     mutt_set_flag(m, cur->message, flag, bf);
425   return 0;
426 }
427 
428 /**
429  * mutt_change_flag - Change the flag on a Message
430  * @param m  Mailbox
431  * @param el List of Emails to change
432  * @param bf true: set the flag; false: clear the flag
433  * @retval  0 Success
434  * @retval -1 Failure
435  */
mutt_change_flag(struct Mailbox * m,struct EmailList * el,bool bf)436 int mutt_change_flag(struct Mailbox *m, struct EmailList *el, bool bf)
437 {
438   struct MuttWindow *win = msgwin_get_window();
439   if (!win)
440     return -1;
441 
442   if (!m || !el || STAILQ_EMPTY(el))
443     return -1;
444 
445   enum MessageType flag = MUTT_NONE;
446   struct KeyEvent event;
447 
448   struct MuttWindow *old_focus = window_set_focus(win);
449 
450   mutt_window_mvprintw(win, 0, 0, "%s? (D/N/O/r/*/!): ", bf ? _("Set flag") : _("Clear flag"));
451   mutt_window_clrtoeol(win);
452   window_redraw(NULL);
453 
454   do
455   {
456     event = mutt_getch();
457   } while (event.ch == -2); // Timeout
458 
459   window_set_focus(old_focus);
460   msgwin_clear_text();
461 
462   if (event.ch < 0) // SIGINT, Abort key (Ctrl-G)
463     return -1;
464 
465   switch (event.ch)
466   {
467     case 'd':
468     case 'D':
469       if (!bf)
470         mutt_emails_set_flag(m, el, MUTT_PURGE, bf);
471       flag = MUTT_DELETE;
472       break;
473 
474     case 'N':
475     case 'n':
476       flag = MUTT_NEW;
477       break;
478 
479     case 'o':
480     case 'O':
481       mutt_emails_set_flag(m, el, MUTT_READ, !bf);
482       flag = MUTT_OLD;
483       break;
484 
485     case 'r':
486     case 'R':
487       flag = MUTT_REPLIED;
488       break;
489 
490     case '*':
491       flag = MUTT_TAG;
492       break;
493 
494     case '!':
495       flag = MUTT_FLAG;
496       break;
497 
498     default:
499       mutt_beep(false);
500       return -1;
501   }
502 
503   mutt_emails_set_flag(m, el, flag, bf);
504   return 0;
505 }
506