1 /*
2 Support of multiply editors and viewers.
3
4 Original idea and code: Oleg "Olegarch" Konovalov <olegarch@linuxinside.com>
5
6 Copyright (C) 2009-2025
7 Free Software Foundation, Inc.
8
9 Written by:
10 Daniel Borca <dborca@yahoo.com>, 2007
11 Andrew Borodin <aborodin@vmail.ru>, 2010-2022
12
13 This file is part of the Midnight Commander.
14
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
19
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <https://www.gnu.org/licenses/>.
27 */
28
29 /** \file dialog-switch.c
30 * \brief Source: support of multiply editors and viewers.
31 */
32
33 #include <config.h>
34
35 #include "lib/global.h"
36 #include "lib/tty/tty.h" // LINES, COLS
37 #include "lib/tty/color.h" // tty_set_normal_attrs()
38 #include "lib/widget.h"
39 #include "lib/event.h"
40
41 /*** global variables ****************************************************************************/
42
43 /* Primitive way to check if the the current dialog is our dialog */
44 /* This is needed by async routines like load_prompt */
45 GList *top_dlg = NULL;
46
47 /* If set then dialogs just clean the screen when refreshing, else */
48 /* they do a complete refresh, refreshing all the parts of the program */
49 gboolean fast_refresh = FALSE;
50
51 WDialog *filemanager = NULL;
52
53 /*** file scope macro definitions ****************************************************************/
54
55 /*** file scope type declarations ****************************************************************/
56
57 /*** forward declarations (file scope functions) *************************************************/
58
59 /*** file scope variables ************************************************************************/
60
61 /* List of dialogs: filemanagers, editors, viewers */
62 static GList *mc_dialogs = NULL;
63 /* Currently active dialog */
64 static GList *mc_current = NULL;
65 /* Is there any dialogs that we have to run after returning to the manager from another dialog */
66 static gboolean dialog_switch_pending = FALSE;
67
68 /* --------------------------------------------------------------------------------------------- */
69 /*** file scope functions ************************************************************************/
70 /* --------------------------------------------------------------------------------------------- */
71
72 static unsigned char
73 get_hotkey (int n)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
74 {
75 return (n <= 9) ? '0' + n : 'a' + n - 10;
76 }
77
78 /* --------------------------------------------------------------------------------------------- */
79
80 static void
81 dialog_switch_suspend (void *data, void *user_data)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
82 {
83 (void) user_data;
84
85 if (data != mc_current->data)
86 widget_set_state (WIDGET (data), WST_SUSPENDED, TRUE);
87 }
88
89 /* --------------------------------------------------------------------------------------------- */
90
91 static void
92 dialog_switch_goto (GList *dlg)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
93 {
94 if (mc_current != dlg)
95 {
96 WDialog *old = DIALOG (mc_current->data);
97
98 mc_current = dlg;
99
100 if (old == filemanager)
101 {
102 // switch from panels to another dialog (editor, viewer, etc)
103 dialog_switch_pending = TRUE;
104 dialog_switch_process_pending ();
105 }
106 else
107 {
108 // switch from editor, viewer, etc to another dialog
109 widget_set_state (WIDGET (old), WST_SUSPENDED, TRUE);
110
111 if (DIALOG (dlg->data) != filemanager)
112 // switch to another editor, viewer, etc
113 // return to panels before run the required dialog
114 dialog_switch_pending = TRUE;
115 else
116 {
117 // switch to panels
118 widget_set_state (WIDGET (filemanager), WST_ACTIVE, TRUE);
119 do_refresh ();
120 }
121 }
122 }
123 }
124
125 /* --------------------------------------------------------------------------------------------- */
126
127 static void
128 dialog_switch_resize (WDialog *d)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
129 {
130 if (widget_get_state (WIDGET (d), WST_ACTIVE))
131 send_message (d, NULL, MSG_RESIZE, 0, NULL);
132 else
133 GROUP (d)->winch_pending = TRUE;
134 }
135
136 /* --------------------------------------------------------------------------------------------- */
137 /*** public functions ****************************************************************************/
138 /* --------------------------------------------------------------------------------------------- */
139
140 void
141 dialog_switch_add (WDialog *h)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
142 {
143 GList *dlg;
144
145 dlg = g_list_find (mc_dialogs, h);
146
147 if (dlg != NULL)
148 mc_current = dlg;
149 else
150 {
151 mc_dialogs = g_list_prepend (mc_dialogs, h);
152 mc_current = mc_dialogs;
153 }
154
155 // suspend forced all other screens
156 g_list_foreach (mc_dialogs, dialog_switch_suspend, NULL);
157 }
158
159 /* --------------------------------------------------------------------------------------------- */
160
161 void
162 dialog_switch_remove (WDialog *h)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
163 {
164 GList *this;
165
166 if (DIALOG (mc_current->data) == h)
167 this = mc_current;
168 else
169 this = g_list_find (mc_dialogs, h);
170
171 mc_dialogs = g_list_delete_link (mc_dialogs, this);
172
173 // adjust current dialog
174 if (top_dlg != NULL)
175 mc_current = g_list_find (mc_dialogs, DIALOG (top_dlg->data));
176 else
177 mc_current = mc_dialogs;
178
179 // resume forced the current screen
180 if (mc_current != NULL)
181 widget_set_state (WIDGET (mc_current->data), WST_ACTIVE, TRUE);
182 }
183
184 /* --------------------------------------------------------------------------------------------- */
185
186 size_t
187 dialog_switch_num (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
188 {
189 return g_list_length (mc_dialogs);
190 }
191
192 /* --------------------------------------------------------------------------------------------- */
193
194 void
195 dialog_switch_next (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
196 {
197 GList *next;
198
199 if (mc_global.midnight_shutdown || mc_current == NULL)
200 return;
201
202 next = g_list_next (mc_current);
203 if (next == NULL)
204 next = mc_dialogs;
205
206 dialog_switch_goto (next);
207 }
208
209 /* --------------------------------------------------------------------------------------------- */
210
211 void
212 dialog_switch_prev (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
213 {
214 GList *prev;
215
216 if (mc_global.midnight_shutdown || mc_current == NULL)
217 return;
218
219 prev = g_list_previous (mc_current);
220 if (prev == NULL)
221 prev = g_list_last (mc_dialogs);
222
223 dialog_switch_goto (prev);
224 }
225
226 /* --------------------------------------------------------------------------------------------- */
227
228 void
229 dialog_switch_list (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
230 {
231 const size_t dlg_num = g_list_length (mc_dialogs);
232 int lines, cols;
233 Listbox *listbox;
234 GList *h, *selected;
235 int i = 0;
236
237 if (mc_global.midnight_shutdown || mc_current == NULL)
238 return;
239
240 lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
241 cols = COLS * 2 / 3;
242
243 listbox = listbox_window_new (lines, cols, _ ("Screens"), "[Screen selector]");
244
245 for (h = mc_dialogs; h != NULL; h = g_list_next (h))
246 {
247 WDialog *dlg = DIALOG (h->data);
248 char *title;
249
250 if (dlg->get_title != NULL)
251 title = dlg->get_title (dlg, WIDGET (listbox->list)->rect.cols - 2);
252 else
253 title = g_strdup ("");
254
255 listbox_add_item_take (listbox->list, LISTBOX_APPEND_BEFORE, get_hotkey (i++), title, h,
256 FALSE);
257 }
258
259 selected = listbox_run_with_data (listbox, mc_current);
260 if (selected != NULL)
261 dialog_switch_goto (selected);
262 }
263
264 /* --------------------------------------------------------------------------------------------- */
265
266 int
267 dialog_switch_process_pending (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
268 {
269 WDialog *h;
270 int ret = 0;
271
272 if (mc_current == NULL)
273 return ret;
274
275 h = DIALOG (mc_current->data);
276
277 if (!dialog_switch_pending)
278 {
279 // return to panels and reload them forced
280 if (mc_global.mc_run_mode == MC_RUN_FULL && h == filemanager)
281 mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "update_panels", NULL);
282 }
283 else
284 while (dialog_switch_pending)
285 {
286 Widget *wh = WIDGET (h);
287
288 dialog_switch_pending = FALSE;
289 widget_set_state (wh, WST_SUSPENDED, TRUE);
290 ret = dlg_run (h);
291 if (widget_get_state (wh, WST_CLOSED))
292 {
293 widget_destroy (wh);
294
295 // return to panels
296 if (mc_global.mc_run_mode == MC_RUN_FULL)
297 {
298 mc_current = g_list_find (mc_dialogs, filemanager);
299 mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "update_panels", NULL);
300 }
301 }
302 }
303
304 repaint_screen ();
305
306 return ret;
307 }
308
309 /* --------------------------------------------------------------------------------------------- */
310
311 void
312 dialog_switch_got_winch (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
313 {
314 GList *dlg;
315
316 for (dlg = mc_dialogs; dlg != NULL; dlg = g_list_next (dlg))
317 if (dlg != mc_current)
318 GROUP (dlg->data)->winch_pending = TRUE;
319 }
320
321 /* --------------------------------------------------------------------------------------------- */
322
323 void
324 dialog_switch_shutdown (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
325 {
326 while (mc_dialogs != NULL)
327 {
328 WDialog *dlg = DIALOG (mc_dialogs->data);
329
330 dlg_run (dlg);
331 widget_destroy (WIDGET (dlg));
332 }
333 }
334
335 /* --------------------------------------------------------------------------------------------- */
336
337 void
338 do_refresh (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
339 {
340 GList *d = top_dlg;
341
342 if (fast_refresh)
343 {
344 if (d != NULL)
345 widget_draw (WIDGET (d->data));
346 }
347 else
348 {
349 // Search first fullscreen dialog
350 for (; d != NULL; d = g_list_next (d))
351 if ((WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
352 break;
353
354 /* when small dialog (i.e. error message) is created first,
355 there is no fullscreen dialog in the stack */
356 if (d == NULL)
357 d = g_list_last (top_dlg);
358
359 // back to top dialog
360 for (; d != NULL; d = g_list_previous (d))
361 widget_draw (WIDGET (d->data));
362 }
363 }
364
365 /* --------------------------------------------------------------------------------------------- */
366
367 void
368 repaint_screen (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
369 {
370 do_refresh ();
371 tty_refresh ();
372 }
373
374 /* --------------------------------------------------------------------------------------------- */
375
376 void
377 mc_refresh (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
378 {
379 #ifdef ENABLE_BACKGROUND
380 if (mc_global.we_are_background)
381 return;
382 #endif
383
384 if (!tty_got_winch ())
385 tty_refresh ();
386 else
387 {
388 /* if winch was caugth, we should do not only redraw screen, but
389 reposition/resize all */
390 dialog_change_screen_size ();
391 }
392 }
393
394 /* --------------------------------------------------------------------------------------------- */
395
396 void
397 dialog_change_screen_size (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
398 {
399 GList *d;
400
401 /* On startup, when mc reads directories top_dlg isn't created yet. If window is resized
402 * at this time, SIGWINCH can be missed because tty_flush_winch() is called here before
403 * a first tty_got_winch() call in frontend_dlg_run().
404 *
405 * Keep SIGWINCH events in pipe if top_dlg isn't created yet. */
406 if (top_dlg == NULL)
407 return;
408
409 tty_flush_winch ();
410 tty_change_screen_size ();
411
412 #ifdef HAVE_SLANG
413 tty_keypad (TRUE);
414 tty_nodelay (FALSE);
415 #endif
416
417 // Inform all suspending dialogs
418 dialog_switch_got_winch ();
419
420 // Inform all running dialogs from first to last
421 for (d = g_list_last (top_dlg); d != NULL; d = g_list_previous (d))
422 dialog_switch_resize (DIALOG (d->data));
423
424 // Now, force the redraw
425 repaint_screen ();
426 }
427
428 /* --------------------------------------------------------------------------------------------- */