/src/neomutt/pager/pager.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Pager Window |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2021 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 pager_pager Pager Window |
25 | | * |
26 | | * The Pager Window displays an email to the user. |
27 | | * |
28 | | * ## Windows |
29 | | * |
30 | | * | Name | Type | See Also | |
31 | | * | :----------- | :-------- | :----------------- | |
32 | | * | Pager Window | WT_CUSTOM | pager_window_new() | |
33 | | * |
34 | | * **Parent** |
35 | | * - @ref pager_ppanel |
36 | | * |
37 | | * **Children** |
38 | | * |
39 | | * None. |
40 | | * |
41 | | * ## Data |
42 | | * - #PagerPrivateData |
43 | | * |
44 | | * The Pager Window stores its data (#PagerPrivateData) in MuttWindow::wdata. |
45 | | * |
46 | | * ## Events |
47 | | * |
48 | | * Once constructed, it is controlled by the following events: |
49 | | * |
50 | | * | Event Type | Handler | |
51 | | * | :-------------------- | :---------------------- | |
52 | | * | #NT_COLOR | pager_color_observer() | |
53 | | * | #NT_CONFIG | pager_config_observer() | |
54 | | * | #NT_INDEX | pager_index_observer() | |
55 | | * | #NT_PAGER | pager_pager_observer() | |
56 | | * | #NT_WINDOW | pager_window_observer() | |
57 | | * | MuttWindow::recalc() | pager_recalc() | |
58 | | * | MuttWindow::repaint() | pager_repaint() | |
59 | | */ |
60 | | |
61 | | #include "config.h" |
62 | | #include <stddef.h> |
63 | | #include <inttypes.h> // IWYU pragma: keep |
64 | | #include <stdbool.h> |
65 | | #include <sys/stat.h> |
66 | | #include "mutt/lib.h" |
67 | | #include "config/lib.h" |
68 | | #include "core/lib.h" |
69 | | #include "gui/lib.h" |
70 | | #include "lib.h" |
71 | | #include "color/lib.h" |
72 | | #include "index/lib.h" |
73 | | #include "display.h" |
74 | | #include "opcodes.h" |
75 | | #include "private_data.h" |
76 | | |
77 | | /** |
78 | | * config_pager_index_lines - React to changes to $pager_index_lines |
79 | | * @param win Pager Window |
80 | | * @retval 0 Successfully handled |
81 | | * @retval -1 Error |
82 | | */ |
83 | | static int config_pager_index_lines(struct MuttWindow *win) |
84 | 0 | { |
85 | 0 | if (!mutt_window_is_visible(win)) |
86 | 0 | return 0; |
87 | | |
88 | 0 | struct MuttWindow *dlg = dialog_find(win); |
89 | 0 | struct MuttWindow *panel_index = window_find_child(dlg, WT_INDEX); |
90 | 0 | struct MuttWindow *win_index = window_find_child(panel_index, WT_MENU); |
91 | 0 | if (!win_index) |
92 | 0 | return -1; |
93 | | |
94 | 0 | const short c_pager_index_lines = cs_subset_number(NeoMutt->sub, "pager_index_lines"); |
95 | |
|
96 | 0 | if (c_pager_index_lines > 0) |
97 | 0 | { |
98 | 0 | win_index->req_rows = c_pager_index_lines; |
99 | 0 | win_index->size = MUTT_WIN_SIZE_FIXED; |
100 | |
|
101 | 0 | panel_index->size = MUTT_WIN_SIZE_MINIMISE; |
102 | 0 | panel_index->state.visible = true; |
103 | 0 | } |
104 | 0 | else |
105 | 0 | { |
106 | 0 | win_index->req_rows = MUTT_WIN_SIZE_UNLIMITED; |
107 | 0 | win_index->size = MUTT_WIN_SIZE_MAXIMISE; |
108 | |
|
109 | 0 | panel_index->size = MUTT_WIN_SIZE_MAXIMISE; |
110 | 0 | panel_index->state.visible = false; |
111 | 0 | } |
112 | |
|
113 | 0 | mutt_window_reflow(dlg); |
114 | 0 | mutt_debug(LL_DEBUG5, "config, request WA_REFLOW\n"); |
115 | 0 | return 0; |
116 | 0 | } |
117 | | |
118 | | /** |
119 | | * pager_recalc - Recalculate the Pager display - Implements MuttWindow::recalc() - @ingroup window_recalc |
120 | | */ |
121 | | static int pager_recalc(struct MuttWindow *win) |
122 | 0 | { |
123 | 0 | win->actions |= WA_REPAINT; |
124 | 0 | mutt_debug(LL_DEBUG5, "recalc done, request WA_REPAINT\n"); |
125 | 0 | return 0; |
126 | 0 | } |
127 | | |
128 | | /** |
129 | | * pager_repaint - Repaint the Pager display - Implements MuttWindow::repaint() - @ingroup window_repaint |
130 | | */ |
131 | | static int pager_repaint(struct MuttWindow *win) |
132 | 0 | { |
133 | 0 | struct PagerPrivateData *priv = win->wdata; |
134 | 0 | if (!priv || !priv->pview || !priv->pview->pdata) |
135 | 0 | return 0; |
136 | | |
137 | | #ifdef USE_DEBUG_COLOR |
138 | | dump_pager(priv); |
139 | | #endif |
140 | | |
141 | | // We need to populate more lines, but not change position |
142 | 0 | const bool repopulate = (priv->cur_line > priv->lines_used); |
143 | 0 | if ((priv->redraw & PAGER_REDRAW_FLOW) || repopulate) |
144 | 0 | { |
145 | 0 | if (!(priv->pview->flags & MUTT_PAGER_RETWINCH)) |
146 | 0 | { |
147 | 0 | priv->win_height = -1; |
148 | 0 | for (int i = 0; i <= priv->top_line; i++) |
149 | 0 | if (!priv->lines[i].cont_line) |
150 | 0 | priv->win_height++; |
151 | 0 | for (int i = 0; i < priv->lines_max; i++) |
152 | 0 | { |
153 | 0 | priv->lines[i].offset = 0; |
154 | 0 | priv->lines[i].cid = -1; |
155 | 0 | priv->lines[i].cont_line = false; |
156 | 0 | priv->lines[i].syntax_arr_size = 0; |
157 | 0 | priv->lines[i].search_arr_size = -1; |
158 | 0 | priv->lines[i].quote = NULL; |
159 | |
|
160 | 0 | mutt_mem_realloc(&(priv->lines[i].syntax), sizeof(struct TextSyntax)); |
161 | 0 | if (priv->search_compiled && priv->lines[i].search) |
162 | 0 | FREE(&(priv->lines[i].search)); |
163 | 0 | } |
164 | |
|
165 | 0 | if (!repopulate) |
166 | 0 | { |
167 | 0 | priv->lines_used = 0; |
168 | 0 | priv->top_line = 0; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | int i = -1; |
172 | 0 | int j = -1; |
173 | 0 | while (display_line(priv->fp, &priv->bytes_read, &priv->lines, ++i, |
174 | 0 | &priv->lines_used, &priv->lines_max, |
175 | 0 | priv->has_types | priv->search_flag | (priv->pview->flags & MUTT_PAGER_NOWRAP), |
176 | 0 | &priv->quote_list, &priv->q_level, &priv->force_redraw, |
177 | 0 | &priv->search_re, priv->pview->win_pager, &priv->ansi_list) == 0) |
178 | 0 | { |
179 | 0 | if (!priv->lines[i].cont_line && (++j == priv->win_height)) |
180 | 0 | { |
181 | 0 | if (!repopulate) |
182 | 0 | priv->top_line = i; |
183 | 0 | if (!priv->search_flag) |
184 | 0 | break; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | } |
188 | |
|
189 | 0 | if ((priv->redraw & PAGER_REDRAW_PAGER) || (priv->top_line != priv->old_top_line)) |
190 | 0 | { |
191 | 0 | do |
192 | 0 | { |
193 | 0 | mutt_window_move(priv->pview->win_pager, 0, 0); |
194 | 0 | priv->cur_line = priv->top_line; |
195 | 0 | priv->old_top_line = priv->top_line; |
196 | 0 | priv->win_height = 0; |
197 | 0 | priv->force_redraw = false; |
198 | |
|
199 | 0 | while ((priv->win_height < priv->pview->win_pager->state.rows) && |
200 | 0 | (priv->lines[priv->cur_line].offset <= priv->st.st_size - 1)) |
201 | 0 | { |
202 | 0 | if (display_line(priv->fp, &priv->bytes_read, &priv->lines, |
203 | 0 | priv->cur_line, &priv->lines_used, &priv->lines_max, |
204 | 0 | (priv->pview->flags & MUTT_DISPLAYFLAGS) | priv->hide_quoted | |
205 | 0 | priv->search_flag | (priv->pview->flags & MUTT_PAGER_NOWRAP), |
206 | 0 | &priv->quote_list, &priv->q_level, &priv->force_redraw, |
207 | 0 | &priv->search_re, priv->pview->win_pager, &priv->ansi_list) > 0) |
208 | 0 | { |
209 | 0 | priv->win_height++; |
210 | 0 | } |
211 | 0 | priv->cur_line++; |
212 | 0 | mutt_window_move(priv->pview->win_pager, 0, priv->win_height); |
213 | 0 | } |
214 | 0 | } while (priv->force_redraw); |
215 | |
|
216 | 0 | const bool c_tilde = cs_subset_bool(NeoMutt->sub, "tilde"); |
217 | 0 | mutt_curses_set_normal_backed_color_by_id(MT_COLOR_TILDE); |
218 | 0 | while (priv->win_height < priv->pview->win_pager->state.rows) |
219 | 0 | { |
220 | 0 | mutt_window_clrtoeol(priv->pview->win_pager); |
221 | 0 | if (c_tilde) |
222 | 0 | mutt_window_addch(priv->pview->win_pager, '~'); |
223 | 0 | priv->win_height++; |
224 | 0 | mutt_window_move(priv->pview->win_pager, 0, priv->win_height); |
225 | 0 | } |
226 | 0 | mutt_curses_set_color_by_id(MT_COLOR_NORMAL); |
227 | 0 | } |
228 | |
|
229 | 0 | priv->redraw = PAGER_REDRAW_NO_FLAGS; |
230 | 0 | mutt_debug(LL_DEBUG5, "repaint done\n"); |
231 | 0 | return 0; |
232 | 0 | } |
233 | | |
234 | | /** |
235 | | * pager_color_observer - Notification that a Color has changed - Implements ::observer_t - @ingroup observer_api |
236 | | */ |
237 | | static int pager_color_observer(struct NotifyCallback *nc) |
238 | 0 | { |
239 | 0 | if (nc->event_type != NT_COLOR) |
240 | 0 | return 0; |
241 | 0 | if (!nc->global_data || !nc->event_data) |
242 | 0 | return -1; |
243 | | |
244 | 0 | struct EventColor *ev_c = nc->event_data; |
245 | 0 | struct MuttWindow *win_pager = nc->global_data; |
246 | 0 | struct PagerPrivateData *priv = win_pager->wdata; |
247 | 0 | if (!priv) |
248 | 0 | return 0; |
249 | | |
250 | | // MT_COLOR_MAX is sent on `uncolor *` |
251 | 0 | if ((ev_c->cid == MT_COLOR_QUOTED) || (ev_c->cid == MT_COLOR_MAX)) |
252 | 0 | { |
253 | | // rework quoted colours |
254 | 0 | qstyle_recolour(priv->quote_list); |
255 | 0 | } |
256 | |
|
257 | 0 | if (ev_c->cid == MT_COLOR_MAX) |
258 | 0 | { |
259 | 0 | for (size_t i = 0; i < priv->lines_max; i++) |
260 | 0 | { |
261 | 0 | FREE(&(priv->lines[i].syntax)); |
262 | 0 | } |
263 | 0 | priv->lines_used = 0; |
264 | 0 | } |
265 | |
|
266 | 0 | mutt_debug(LL_DEBUG5, "color done\n"); |
267 | 0 | return 0; |
268 | 0 | } |
269 | | |
270 | | /** |
271 | | * pager_config_observer - Notification that a Config Variable has changed - Implements ::observer_t - @ingroup observer_api |
272 | | */ |
273 | | static int pager_config_observer(struct NotifyCallback *nc) |
274 | 0 | { |
275 | 0 | if (nc->event_type != NT_CONFIG) |
276 | 0 | return 0; |
277 | 0 | if (!nc->global_data || !nc->event_data) |
278 | 0 | return -1; |
279 | | |
280 | 0 | struct EventConfig *ev_c = nc->event_data; |
281 | 0 | struct MuttWindow *win_pager = nc->global_data; |
282 | |
|
283 | 0 | if (mutt_str_equal(ev_c->name, "pager_index_lines")) |
284 | 0 | { |
285 | 0 | config_pager_index_lines(win_pager); |
286 | 0 | mutt_debug(LL_DEBUG5, "config done\n"); |
287 | 0 | } |
288 | |
|
289 | 0 | return 0; |
290 | 0 | } |
291 | | |
292 | | /** |
293 | | * pager_global_observer - Notification that a Global Event occurred - Implements ::observer_t - @ingroup observer_api |
294 | | */ |
295 | | static int pager_global_observer(struct NotifyCallback *nc) |
296 | 0 | { |
297 | 0 | if (nc->event_type != NT_GLOBAL) |
298 | 0 | return 0; |
299 | 0 | if (!nc->global_data) |
300 | 0 | return -1; |
301 | 0 | if (nc->event_subtype != NT_GLOBAL_COMMAND) |
302 | 0 | return 0; |
303 | | |
304 | 0 | struct MuttWindow *win_pager = nc->global_data; |
305 | |
|
306 | 0 | struct PagerPrivateData *priv = win_pager->wdata; |
307 | 0 | const struct PagerView *pview = priv ? priv->pview : NULL; |
308 | 0 | if (priv && pview && (priv->redraw & PAGER_REDRAW_FLOW) && (pview->flags & MUTT_PAGER_RETWINCH)) |
309 | 0 | { |
310 | 0 | priv->rc = OP_REFORMAT_WINCH; |
311 | 0 | } |
312 | |
|
313 | 0 | return 0; |
314 | 0 | } |
315 | | |
316 | | /** |
317 | | * pager_index_observer - Notification that the Index has changed - Implements ::observer_t - @ingroup observer_api |
318 | | */ |
319 | | static int pager_index_observer(struct NotifyCallback *nc) |
320 | 0 | { |
321 | 0 | if (nc->event_type != NT_INDEX) |
322 | 0 | return 0; |
323 | 0 | if (!nc->global_data) |
324 | 0 | return -1; |
325 | | |
326 | 0 | struct MuttWindow *win_pager = nc->global_data; |
327 | |
|
328 | 0 | struct PagerPrivateData *priv = win_pager->wdata; |
329 | 0 | if (!priv) |
330 | 0 | return 0; |
331 | | |
332 | 0 | struct IndexSharedData *shared = nc->event_data; |
333 | |
|
334 | 0 | if (nc->event_subtype & NT_INDEX_MAILBOX) |
335 | 0 | { |
336 | 0 | win_pager->actions |= WA_RECALC; |
337 | 0 | mutt_debug(LL_DEBUG5, "index done, request WA_RECALC\n"); |
338 | 0 | priv->loop = PAGER_LOOP_QUIT; |
339 | 0 | } |
340 | 0 | else if (nc->event_subtype & NT_INDEX_EMAIL) |
341 | 0 | { |
342 | 0 | win_pager->actions |= WA_RECALC; |
343 | 0 | mutt_debug(LL_DEBUG5, "index done, request WA_RECALC\n"); |
344 | 0 | priv->pager_redraw = true; |
345 | 0 | if (shared && shared->email && (priv->loop != PAGER_LOOP_QUIT)) |
346 | 0 | { |
347 | 0 | priv->loop = PAGER_LOOP_RELOAD; |
348 | 0 | } |
349 | 0 | else |
350 | 0 | { |
351 | 0 | priv->loop = PAGER_LOOP_QUIT; |
352 | 0 | priv->rc = 0; |
353 | 0 | } |
354 | 0 | } |
355 | |
|
356 | 0 | return 0; |
357 | 0 | } |
358 | | |
359 | | /** |
360 | | * pager_pager_observer - Notification that the Pager has changed - Implements ::observer_t - @ingroup observer_api |
361 | | */ |
362 | | static int pager_pager_observer(struct NotifyCallback *nc) |
363 | 0 | { |
364 | 0 | if (nc->event_type != NT_PAGER) |
365 | 0 | return 0; |
366 | 0 | if (!nc->global_data || !nc->event_data) |
367 | 0 | return -1; |
368 | | |
369 | 0 | mutt_debug(LL_DEBUG5, "pager done\n"); |
370 | 0 | return 0; |
371 | 0 | } |
372 | | |
373 | | /** |
374 | | * pager_window_observer - Notification that a Window has changed - Implements ::observer_t - @ingroup observer_api |
375 | | */ |
376 | | static int pager_window_observer(struct NotifyCallback *nc) |
377 | 0 | { |
378 | 0 | if (nc->event_type != NT_WINDOW) |
379 | 0 | return 0; |
380 | 0 | if (!nc->global_data || !nc->event_data) |
381 | 0 | return -1; |
382 | 0 | if (nc->event_subtype != NT_WINDOW_DELETE) |
383 | 0 | return 0; |
384 | | |
385 | 0 | struct MuttWindow *win_pager = nc->global_data; |
386 | 0 | struct EventWindow *ev_w = nc->event_data; |
387 | 0 | if (ev_w->win != win_pager) |
388 | 0 | return 0; |
389 | | |
390 | 0 | struct MuttWindow *dlg = window_find_parent(win_pager, WT_DLG_INDEX); |
391 | 0 | if (!dlg) |
392 | 0 | dlg = window_find_parent(win_pager, WT_DLG_DO_PAGER); |
393 | |
|
394 | 0 | struct IndexSharedData *shared = dlg->wdata; |
395 | |
|
396 | 0 | notify_observer_remove(NeoMutt->notify, pager_color_observer, win_pager); |
397 | 0 | notify_observer_remove(NeoMutt->notify, pager_config_observer, win_pager); |
398 | 0 | notify_observer_remove(NeoMutt->notify, pager_global_observer, win_pager); |
399 | 0 | notify_observer_remove(shared->notify, pager_index_observer, win_pager); |
400 | 0 | notify_observer_remove(shared->notify, pager_pager_observer, win_pager); |
401 | 0 | notify_observer_remove(win_pager->notify, pager_window_observer, win_pager); |
402 | |
|
403 | 0 | mutt_debug(LL_DEBUG5, "window delete done\n"); |
404 | |
|
405 | 0 | return 0; |
406 | 0 | } |
407 | | |
408 | | /** |
409 | | * pager_window_new - Create a new Pager Window (list of Emails) |
410 | | * @param shared Shared Index Data |
411 | | * @param priv Private Pager Data |
412 | | * @retval ptr New Window |
413 | | */ |
414 | | struct MuttWindow *pager_window_new(struct IndexSharedData *shared, |
415 | | struct PagerPrivateData *priv) |
416 | 0 | { |
417 | 0 | struct MuttWindow *win = mutt_window_new(WT_CUSTOM, MUTT_WIN_ORIENT_VERTICAL, |
418 | 0 | MUTT_WIN_SIZE_MAXIMISE, MUTT_WIN_SIZE_UNLIMITED, |
419 | 0 | MUTT_WIN_SIZE_UNLIMITED); |
420 | 0 | win->wdata = priv; |
421 | 0 | win->recalc = pager_recalc; |
422 | 0 | win->repaint = pager_repaint; |
423 | |
|
424 | 0 | notify_observer_add(NeoMutt->notify, NT_COLOR, pager_color_observer, win); |
425 | 0 | notify_observer_add(NeoMutt->notify, NT_CONFIG, pager_config_observer, win); |
426 | 0 | notify_observer_add(NeoMutt->notify, NT_GLOBAL, pager_global_observer, win); |
427 | 0 | notify_observer_add(shared->notify, NT_ALL, pager_index_observer, win); |
428 | 0 | notify_observer_add(shared->notify, NT_PAGER, pager_pager_observer, win); |
429 | 0 | notify_observer_add(win->notify, NT_WINDOW, pager_window_observer, win); |
430 | |
|
431 | 0 | return win; |
432 | 0 | } |