1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2005-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Mailutils is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "mail.h"
18 
19 static size_t top_of_page = 1; /* Number of the topmost message on the page */
20 static size_t cursor;          /* Number of current message */
21 
22 static size_t *page_map;       /* Array of message numbers. page_map[N] holds
23 	  		          number of the message occupying Nth line on
24 			          the screen */
25 static unsigned page_size;     /* Capacity of page_map */
26 static unsigned page_avail;    /* First non-used entry in page map. Can be
27 			          equal to page_size */
28 
29 /* Auxiliary function: Store number of message from mspec into page_map */
30 static int
_fill_map(msgset_t * mspec,mu_message_t msg,void * data)31 _fill_map (msgset_t *mspec, mu_message_t msg, void *data)
32 {
33   unsigned *pos = data;
34   page_map[*pos] = msgset_msgno (mspec);
35   ++*pos;
36   return 0;
37 }
38 
39 /* Fill page_map.
40    page_avail must be set to zero before calling this function */
41 static void
fill_page_map()42 fill_page_map ()
43 {
44   util_range_msg (top_of_page, page_size,
45 		  MSG_COUNT|MSG_NODELETED|MSG_SILENT, _fill_map,
46 		  &page_avail);
47   if (cursor >= page_avail)
48     cursor = page_avail - 1;
49 }
50 
51 /* Check if the page_map is valid. If not, fill it.
52    Page_avail can be set to zero to force re-filling page_map. In particular
53    this happens when deleting current message or when SIGWINCH is delivered
54    to the program */
55 static void
check_page_map()56 check_page_map ()
57 {
58   if (!page_map)
59     {
60       page_size = util_screen_lines ();
61       page_map = mu_realloc (page_map, sizeof (page_map[0]) * page_size);
62       page_avail = 0;
63     }
64   if (page_avail == 0)
65     fill_page_map ();
66 }
67 
68 /* Invalidate page_map. HARD=1 means 'hard invalidation', implying
69    the need to reallocate the array */
70 void
page_invalidate(int hard)71 page_invalidate (int hard)
72 {
73   page_avail = 0;
74   if (hard)
75     {
76       free (page_map);
77       page_map = NULL;
78     }
79 }
80 
81 /* Invalidate page_map if VALUE is number of the current message */
82 void
cond_page_invalidate(size_t value)83 cond_page_invalidate (size_t value)
84 {
85   unsigned i;
86 
87   if (page_map == NULL || page_avail == 0)
88     return;
89   if (page_avail)
90     {
91       if (page_map[page_avail-1] == value)
92 	page_invalidate (0);
93       else
94 	{
95 	  if (page_avail > 1)
96 	    {
97 	      for (i = 0; i < page_avail-1; i++)
98 		if (page_map[i] >= value && value <= page_map[i+1])
99 		  {
100 		    page_invalidate (0);
101 		    return;
102 		  }
103 	    }
104 	  if (page_avail < page_size
105 	      && (value > page_map[page_avail-1] || value < page_map[0]))
106 	    page_invalidate (0);
107 	}
108     }
109 }
110 
111 /* Return a 1-based index of page_map entry occupied by number VALUE.
112    Return 0 if VALUE is not found in page_map */
113 static int
page_line(size_t value)114 page_line (size_t value)
115 {
116   unsigned i;
117 
118   for (i = 0; i < page_avail; i++)
119     if (page_map[i] == value)
120       return i+1;
121   return 0;
122 }
123 
124 /* Return number of the current message.
125    Zero is returned if page_map is empty (i.e. mailbox is empy) */
126 size_t
get_cursor()127 get_cursor ()
128 {
129   check_page_map ();
130   if (page_avail == 0)
131     return 0;
132   return page_map[cursor];
133 }
134 
135 /* Move cursor to message number VALUE */
136 void
set_cursor(unsigned value)137 set_cursor (unsigned value)
138 {
139   int n;
140 
141   if (total == 0)
142     {
143       cursor = 0;
144       return;
145     }
146 
147   check_page_map ();
148   n = page_line (value);
149   if (n == 0)
150     {
151       top_of_page = value;
152       cursor = 0;
153       page_avail = 0;
154       page_move (0);
155     }
156   else
157     cursor = n - 1;
158 }
159 
160 /* Return T if the cursor points to message number N */
161 int
is_current_message(size_t n)162 is_current_message (size_t n)
163 {
164   check_page_map ();
165   return page_map[cursor] == n;
166 }
167 
168 /* Apply function FUNC to each message from the page. DATA supplies
169    call-specific data to the function. */
170 void
page_do(msg_handler_t func,void * data)171 page_do (msg_handler_t func, void *data)
172 {
173   unsigned i;
174 
175   check_page_map ();
176   for (i = 0; i < page_avail; i++)
177     {
178       mu_message_t msg;
179       msgset_t *set = msgset_make_1 (page_map[i]);
180       mu_mailbox_get_message (mbox, page_map[i], &msg);
181       func (set, msg, data);
182       msgset_free (set);
183     }
184 }
185 
186 /* Move current page OFFSET lines forward, if OFFSET > 0, or backward,
187    if OFFSET < 0.
188    Return number of items that will be displayed */
189 size_t
page_move(off_t offset)190 page_move (off_t offset)
191 {
192   size_t start;
193   size_t count = 0;
194 
195   check_page_map ();
196 
197   if (offset < 0 && -offset > page_map[0])
198     start = 1;
199   else
200     start = page_map[0] + offset;
201 
202   util_range_msg (start, page_size,
203 		  MSG_COUNT|MSG_NODELETED|MSG_SILENT, _fill_map, &count);
204 
205   if (offset < 0 && top_of_page == page_map[0])
206     {
207       page_avail = count;
208       return 0;
209     }
210 
211   if (count)
212     {
213       top_of_page = page_map[0];
214 
215       if (count < page_size && top_of_page > 1)
216 	{
217 	  for (start = top_of_page - 1; count < page_size && start > 1;
218 	       start--)
219 	    {
220 	      if (!util_isdeleted (start))
221 		{
222 		  top_of_page = start;
223 		  count++;
224 		  cursor++;
225 		}
226 	    }
227 
228 	  page_avail = 0;
229 	  fill_page_map ();
230 	  if (cursor >= page_avail)
231 	    cursor = page_avail - 1;
232 	}
233       else
234 	page_avail = count;
235     }
236   return count;
237 }
238