1 /**
2  * @file
3  * Sidebar functions
4  *
5  * @authors
6  * Copyright (C) 2020 Richard Russon <rich@flatcap.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 sidebar_functions Sidebar functions
25  *
26  * Sidebar functions
27  */
28 
29 #include "config.h"
30 #include <stddef.h>
31 #include <stdbool.h>
32 #include "private.h"
33 #include "mutt/lib.h"
34 #include "config/lib.h"
35 #include "core/lib.h"
36 #include "gui/lib.h"
37 #include "lib.h"
38 #include "opcodes.h"
39 
40 /**
41  * select_next - Selects the next unhidden mailbox
42  * @param wdata Sidebar data
43  * @retval true The selection changed
44  */
select_next(struct SidebarWindowData * wdata)45 bool select_next(struct SidebarWindowData *wdata)
46 {
47   if (ARRAY_EMPTY(&wdata->entries) || (wdata->hil_index < 0))
48     return false;
49 
50   struct SbEntry **sbep = NULL;
51   ARRAY_FOREACH_FROM(sbep, &wdata->entries, wdata->hil_index + 1)
52   {
53     if (!(*sbep)->is_hidden)
54     {
55       wdata->hil_index = ARRAY_FOREACH_IDX;
56       return true;
57     }
58   }
59 
60   return false;
61 }
62 
63 /**
64  * next_new - Return the next mailbox with new messages
65  * @param wdata Sidebar data
66  * @param begin Starting index for searching
67  * @param end   Ending index for searching
68  * @retval sbe  Pointer to the first entry with new messages
69  * @retval NULL None could be found
70  */
next_new(struct SidebarWindowData * wdata,size_t begin,size_t end)71 static struct SbEntry **next_new(struct SidebarWindowData *wdata, size_t begin, size_t end)
72 {
73   struct SbEntry **sbep = NULL;
74   ARRAY_FOREACH_FROM_TO(sbep, &wdata->entries, begin, end)
75   {
76     if ((*sbep)->mailbox->has_new || (*sbep)->mailbox->msg_unread != 0)
77       return sbep;
78   }
79   return NULL;
80 }
81 
82 /**
83  * select_next_new - Selects the next new mailbox
84  * @param wdata         Sidebar data
85  * @param next_new_wrap Wrap around when searching for the next mailbox with new mail
86  * @retval true The selection changed
87  *
88  * Search down the list of mail folders for one containing new mail.
89  */
select_next_new(struct SidebarWindowData * wdata,bool next_new_wrap)90 static bool select_next_new(struct SidebarWindowData *wdata, bool next_new_wrap)
91 {
92   const size_t max_entries = ARRAY_SIZE(&wdata->entries);
93 
94   if ((max_entries == 0) || (wdata->hil_index < 0))
95     return false;
96 
97   struct SbEntry **sbep = NULL;
98   if ((sbep = next_new(wdata, wdata->hil_index + 1, max_entries)) ||
99       (next_new_wrap && (sbep = next_new(wdata, 0, wdata->hil_index))))
100   {
101     wdata->hil_index = ARRAY_IDX(&wdata->entries, sbep);
102     return true;
103   }
104 
105   return false;
106 }
107 
108 /**
109  * select_prev - Selects the previous unhidden mailbox
110  * @param wdata Sidebar data
111  * @retval true The selection changed
112  */
select_prev(struct SidebarWindowData * wdata)113 bool select_prev(struct SidebarWindowData *wdata)
114 {
115   if (ARRAY_EMPTY(&wdata->entries) || (wdata->hil_index < 0))
116     return false;
117 
118   struct SbEntry **sbep = NULL, **prev = NULL;
119   ARRAY_FOREACH_TO(sbep, &wdata->entries, wdata->hil_index)
120   {
121     if (!(*sbep)->is_hidden)
122       prev = sbep;
123   }
124 
125   if (prev)
126   {
127     wdata->hil_index = ARRAY_IDX(&wdata->entries, prev);
128     return true;
129   }
130 
131   return false;
132 }
133 
134 /**
135  * prev_new - Return the previous mailbox with new messages
136  * @param wdata Sidebar data
137  * @param begin Starting index for searching
138  * @param end   Ending index for searching
139  * @retval sbe  Pointer to the first entry with new messages
140  * @retval NULL None could be found
141  */
prev_new(struct SidebarWindowData * wdata,size_t begin,size_t end)142 static struct SbEntry **prev_new(struct SidebarWindowData *wdata, size_t begin, size_t end)
143 {
144   struct SbEntry **sbep = NULL, **prev = NULL;
145   ARRAY_FOREACH_FROM_TO(sbep, &wdata->entries, begin, end)
146   {
147     if ((*sbep)->mailbox->has_new || (*sbep)->mailbox->msg_unread != 0)
148       prev = sbep;
149   }
150 
151   return prev;
152 }
153 
154 /**
155  * select_prev_new - Selects the previous new mailbox
156  * @param wdata         Sidebar data
157  * @param next_new_wrap Wrap around when searching for the next mailbox with new mail
158  * @retval true The selection changed
159  *
160  * Search up the list of mail folders for one containing new mail.
161  */
select_prev_new(struct SidebarWindowData * wdata,bool next_new_wrap)162 static bool select_prev_new(struct SidebarWindowData *wdata, bool next_new_wrap)
163 {
164   const size_t max_entries = ARRAY_SIZE(&wdata->entries);
165 
166   if ((max_entries == 0) || (wdata->hil_index < 0))
167     return false;
168 
169   struct SbEntry **sbep = NULL;
170   if ((sbep = prev_new(wdata, 0, wdata->hil_index)) ||
171       (next_new_wrap && (sbep = prev_new(wdata, wdata->hil_index + 1, max_entries))))
172   {
173     wdata->hil_index = ARRAY_IDX(&wdata->entries, sbep);
174     return true;
175   }
176 
177   return false;
178 }
179 
180 /**
181  * select_page_down - Selects the first entry in the next page of mailboxes
182  * @param wdata Sidebar data
183  * @retval true The selection changed
184  */
select_page_down(struct SidebarWindowData * wdata)185 static bool select_page_down(struct SidebarWindowData *wdata)
186 {
187   if (ARRAY_EMPTY(&wdata->entries) || (wdata->bot_index < 0))
188     return false;
189 
190   int orig_hil_index = wdata->hil_index;
191 
192   wdata->hil_index = wdata->bot_index;
193   select_next(wdata);
194   /* If the rest of the entries are hidden, go up to the last unhidden one */
195   if ((*ARRAY_GET(&wdata->entries, wdata->hil_index))->is_hidden)
196     select_prev(wdata);
197 
198   return (orig_hil_index != wdata->hil_index);
199 }
200 
201 /**
202  * select_page_up - Selects the last entry in the previous page of mailboxes
203  * @param wdata Sidebar data
204  * @retval true The selection changed
205  */
select_page_up(struct SidebarWindowData * wdata)206 static bool select_page_up(struct SidebarWindowData *wdata)
207 {
208   if (ARRAY_EMPTY(&wdata->entries) || (wdata->top_index < 0))
209     return false;
210 
211   int orig_hil_index = wdata->hil_index;
212 
213   wdata->hil_index = wdata->top_index;
214   select_prev(wdata);
215   /* If the rest of the entries are hidden, go down to the last unhidden one */
216   if ((*ARRAY_GET(&wdata->entries, wdata->hil_index))->is_hidden)
217     select_next(wdata);
218 
219   return (orig_hil_index != wdata->hil_index);
220 }
221 
222 /**
223  * select_first - Selects the first unhidden mailbox
224  * @param wdata Sidebar data
225  * @retval true The selection changed
226  */
select_first(struct SidebarWindowData * wdata)227 static bool select_first(struct SidebarWindowData *wdata)
228 {
229   if (ARRAY_EMPTY(&wdata->entries) || (wdata->hil_index < 0))
230     return false;
231 
232   int orig_hil_index = wdata->hil_index;
233 
234   wdata->hil_index = 0;
235   if ((*ARRAY_GET(&wdata->entries, wdata->hil_index))->is_hidden)
236     if (!select_next(wdata))
237       wdata->hil_index = orig_hil_index;
238 
239   return (orig_hil_index != wdata->hil_index);
240 }
241 
242 /**
243  * select_last - Selects the last unhidden mailbox
244  * @param wdata Sidebar data
245  * @retval true The selection changed
246  */
select_last(struct SidebarWindowData * wdata)247 static bool select_last(struct SidebarWindowData *wdata)
248 {
249   if (ARRAY_EMPTY(&wdata->entries) || (wdata->hil_index < 0))
250     return false;
251 
252   int orig_hil_index = wdata->hil_index;
253 
254   wdata->hil_index = ARRAY_SIZE(&wdata->entries);
255   if (!select_prev(wdata))
256     wdata->hil_index = orig_hil_index;
257 
258   return (orig_hil_index != wdata->hil_index);
259 }
260 
261 /**
262  * sb_change_mailbox - Perform a Sidebar function
263  * @param win Sidebar Window
264  * @param op  Operation to perform, e.g. OP_SIDEBAR_NEXT_NEW
265  */
sb_change_mailbox(struct MuttWindow * win,int op)266 void sb_change_mailbox(struct MuttWindow *win, int op)
267 {
268   if (!mutt_window_is_visible(win))
269     return;
270 
271   struct SidebarWindowData *wdata = sb_wdata_get(win);
272   if (!wdata)
273     return;
274 
275   if (wdata->hil_index < 0) /* It'll get reset on the next draw */
276     return;
277 
278   bool changed = false;
279   const bool c_sidebar_next_new_wrap =
280       cs_subset_bool(NeoMutt->sub, "sidebar_next_new_wrap");
281   switch (op)
282   {
283     case OP_SIDEBAR_FIRST:
284       changed = select_first(wdata);
285       break;
286     case OP_SIDEBAR_LAST:
287       changed = select_last(wdata);
288       break;
289     case OP_SIDEBAR_NEXT:
290       changed = select_next(wdata);
291       break;
292     case OP_SIDEBAR_NEXT_NEW:
293       changed = select_next_new(wdata, c_sidebar_next_new_wrap);
294       break;
295     case OP_SIDEBAR_PAGE_DOWN:
296       changed = select_page_down(wdata);
297       break;
298     case OP_SIDEBAR_PAGE_UP:
299       changed = select_page_up(wdata);
300       break;
301     case OP_SIDEBAR_PREV:
302       changed = select_prev(wdata);
303       break;
304     case OP_SIDEBAR_PREV_NEW:
305       changed = select_prev_new(wdata, c_sidebar_next_new_wrap);
306       break;
307     default:
308       return;
309   }
310   if (changed)
311     win->actions |= WA_RECALC;
312 }
313