1 /*
2    Internal file viewer for the Midnight Commander
3    Function for whow info on display
4 
5    Copyright (C) 1994-2021
6    Free Software Foundation, Inc.
7 
8    Written by:
9    Miguel de Icaza, 1994, 1995, 1998
10    Janne Kukonlehto, 1994, 1995
11    Jakub Jelinek, 1995
12    Joseph M. Hinkle, 1996
13    Norbert Warmuth, 1997
14    Pavel Machek, 1998
15    Roland Illig <roland.illig@gmx.de>, 2004, 2005
16    Slava Zanko <slavazanko@google.com>, 2009
17    Andrew Borodin <aborodin@vmail.ru>, 2009, 2013
18    Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
19 
20    This file is part of the Midnight Commander.
21 
22    The Midnight Commander is free software: you can redistribute it
23    and/or modify it under the terms of the GNU General Public License as
24    published by the Free Software Foundation, either version 3 of the License,
25    or (at your option) any later version.
26 
27    The Midnight Commander is distributed in the hope that it will be useful,
28    but WITHOUT ANY WARRANTY; without even the implied warranty of
29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30    GNU General Public License for more details.
31 
32    You should have received a copy of the GNU General Public License
33    along with this program.  If not, see <http://www.gnu.org/licenses/>.
34  */
35 
36 #include <config.h>
37 #include <inttypes.h>           /* uintmax_t */
38 
39 #include "lib/global.h"
40 #include "lib/skin.h"
41 #include "lib/tty/tty.h"
42 #include "lib/tty/key.h"
43 #include "lib/strutil.h"
44 #include "lib/util.h"
45 #include "lib/widget.h"
46 #ifdef HAVE_CHARSET
47 #include "lib/charsets.h"
48 #endif
49 
50 #include "src/setup.h"          /* panels_options */
51 #include "src/keymap.h"
52 
53 #include "internal.h"
54 
55 /*** global variables ****************************************************************************/
56 
57 /*** file scope macro definitions ****************************************************************/
58 
59 #define BUF_TRUNC_LEN 5         /* The length of the line displays the file size */
60 
61 /*** file scope type declarations ****************************************************************/
62 
63 /*** file scope variables ************************************************************************/
64 
65 /* If set, show a ruler */
66 static enum ruler_type
67 {
68     RULER_NONE,
69     RULER_TOP,
70     RULER_BOTTOM
71 } ruler = RULER_NONE;
72 
73 /*** file scope functions ************************************************************************/
74 /* --------------------------------------------------------------------------------------------- */
75 
76 /** Define labels and handlers for functional keys */
77 
78 static void
mcview_set_buttonbar(WView * view)79 mcview_set_buttonbar (WView * view)
80 {
81     Widget *w = WIDGET (view);
82     WDialog *h = DIALOG (w->owner);
83     WButtonBar *b;
84     const global_keymap_t *keymap = view->mode_flags.hex ? view->hex_keymap : w->keymap;
85 
86     b = find_buttonbar (h);
87     buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), keymap, w);
88 
89     if (view->mode_flags.hex)
90     {
91         if (view->hexedit_mode)
92             buttonbar_set_label (b, 2, Q_ ("ButtonBar|View"), keymap, w);
93         else if (view->datasource == DS_FILE)
94             buttonbar_set_label (b, 2, Q_ ("ButtonBar|Edit"), keymap, w);
95         else
96             buttonbar_set_label (b, 2, "", keymap, WIDGET (view));
97 
98         buttonbar_set_label (b, 4, Q_ ("ButtonBar|Ascii"), keymap, w);
99         buttonbar_set_label (b, 6, Q_ ("ButtonBar|Save"), keymap, w);
100         buttonbar_set_label (b, 7, Q_ ("ButtonBar|HxSrch"), keymap, w);
101 
102     }
103     else
104     {
105         buttonbar_set_label (b, 2, view->mode_flags.wrap ? Q_ ("ButtonBar|UnWrap")
106                              : Q_ ("ButtonBar|Wrap"), keymap, w);
107         buttonbar_set_label (b, 4, Q_ ("ButtonBar|Hex"), keymap, w);
108         buttonbar_set_label (b, 6, "", keymap, WIDGET (view));
109         buttonbar_set_label (b, 7, Q_ ("ButtonBar|Search"), keymap, w);
110     }
111 
112     buttonbar_set_label (b, 5, Q_ ("ButtonBar|Goto"), keymap, w);
113     buttonbar_set_label (b, 8, view->mode_flags.magic ? Q_ ("ButtonBar|Raw")
114                          : Q_ ("ButtonBar|Parse"), keymap, w);
115 
116     if (!mcview_is_in_panel (view))     /* don't override some panel buttonbar keys  */
117     {
118         buttonbar_set_label (b, 3, Q_ ("ButtonBar|Quit"), keymap, w);
119         buttonbar_set_label (b, 9, view->mode_flags.nroff ? Q_ ("ButtonBar|Unform")
120                              : Q_ ("ButtonBar|Format"), keymap, w);
121         buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), keymap, w);
122     }
123 }
124 
125 /* --------------------------------------------------------------------------------------------- */
126 
127 static void
mcview_display_percent(WView * view,off_t p)128 mcview_display_percent (WView * view, off_t p)
129 {
130     int percent;
131 
132     percent = mcview_calc_percent (view, p);
133     if (percent >= 0)
134     {
135         const screen_dimen top = view->status_area.top;
136         const screen_dimen right = view->status_area.left + view->status_area.width;
137 
138         widget_gotoyx (view, top, right - 4);
139         tty_printf ("%3d%%", percent);
140         /* avoid cursor wrapping in NCurses-base MC */
141         widget_gotoyx (view, top, right - 1);
142     }
143 }
144 
145 /* --------------------------------------------------------------------------------------------- */
146 
147 static void
mcview_display_status(WView * view)148 mcview_display_status (WView * view)
149 {
150     const screen_dimen top = view->status_area.top;
151     const screen_dimen left = view->status_area.left;
152     const screen_dimen width = view->status_area.width;
153     const screen_dimen height = view->status_area.height;
154     const char *file_label;
155 
156     if (height < 1)
157         return;
158 
159     tty_setcolor (STATUSBAR_COLOR);
160     tty_draw_hline (WIDGET (view)->y + top, WIDGET (view)->x + left, ' ', width);
161 
162     file_label =
163         view->filename_vpath != NULL ?
164         vfs_path_get_last_path_str (view->filename_vpath) : view->command != NULL ?
165         view->command : "";
166 
167     if (width > 40)
168     {
169         widget_gotoyx (view, top, width - 32);
170         if (view->mode_flags.hex)
171             tty_printf ("0x%08" PRIxMAX, (uintmax_t) view->hex_cursor);
172         else
173         {
174             char buffer[BUF_TRUNC_LEN + 1];
175 
176             size_trunc_len (buffer, BUF_TRUNC_LEN, mcview_get_filesize (view), 0,
177                             panels_options.kilobyte_si);
178             tty_printf ("%9" PRIuMAX "/%s%s %s", (uintmax_t) view->dpy_end,
179                         buffer, mcview_may_still_grow (view) ? "+" : " ",
180 #ifdef HAVE_CHARSET
181                         mc_global.source_codepage >= 0 ?
182                         get_codepage_id (mc_global.source_codepage) :
183 #endif
184                         "");
185         }
186     }
187     widget_gotoyx (view, top, left);
188     if (width > 40)
189         tty_print_string (str_fit_to_term (file_label, width - 34, J_LEFT_FIT));
190     else
191         tty_print_string (str_fit_to_term (file_label, width - 5, J_LEFT_FIT));
192     if (width > 26)
193         mcview_display_percent (view, view->mode_flags.hex ? view->hex_cursor : view->dpy_end);
194 }
195 
196 /* --------------------------------------------------------------------------------------------- */
197 /*** public functions ****************************************************************************/
198 /* --------------------------------------------------------------------------------------------- */
199 
200 void
mcview_update(WView * view)201 mcview_update (WView * view)
202 {
203     static int dirt_limit = 1;
204 
205     if (view->dpy_bbar_dirty)
206     {
207         view->dpy_bbar_dirty = FALSE;
208         mcview_set_buttonbar (view);
209         widget_draw (WIDGET (find_buttonbar (DIALOG (WIDGET (view)->owner))));
210     }
211 
212     if (view->dirty > dirt_limit)
213     {
214         /* Too many updates skipped -> force a update */
215         mcview_display (view);
216         view->dirty = 0;
217         /* Raise the update skipping limit */
218         dirt_limit++;
219         if (dirt_limit > mcview_max_dirt_limit)
220             dirt_limit = mcview_max_dirt_limit;
221     }
222     else if (view->dirty > 0)
223     {
224         if (is_idle ())
225         {
226             /* We have time to update the screen properly */
227             mcview_display (view);
228             view->dirty = 0;
229             if (dirt_limit > 1)
230                 dirt_limit--;
231         }
232         else
233         {
234             /* We are busy -> skipping full update,
235                only the status line is updated */
236             mcview_display_status (view);
237         }
238         /* Here we had a refresh, if fast scrolling does not work
239            restore the refresh, although this should not happen */
240     }
241 }
242 
243 /* --------------------------------------------------------------------------------------------- */
244 /** Displays as much data from view->dpy_start as fits on the screen */
245 
246 void
mcview_display(WView * view)247 mcview_display (WView * view)
248 {
249     if (view->mode_flags.hex)
250         mcview_display_hex (view);
251     else
252         mcview_display_text (view);
253     mcview_display_status (view);
254 }
255 
256 /* --------------------------------------------------------------------------------------------- */
257 
258 void
mcview_compute_areas(WView * view)259 mcview_compute_areas (WView * view)
260 {
261     struct area view_area;
262     screen_dimen height, rest, y;
263 
264     /* The viewer is surrounded by a frame of size view->dpy_frame_size.
265      * Inside that frame, there are: The status line (at the top),
266      * the data area and an optional ruler, which is shown above or
267      * below the data area. */
268 
269     view_area.top = view->dpy_frame_size;
270     view_area.left = view->dpy_frame_size;
271     view_area.height = DOZ ((screen_dimen) WIDGET (view)->lines, 2 * view->dpy_frame_size);
272     view_area.width = DOZ ((screen_dimen) WIDGET (view)->cols, 2 * view->dpy_frame_size);
273 
274     /* Most coordinates of the areas equal those of the whole viewer */
275     view->status_area = view_area;
276     view->ruler_area = view_area;
277     view->data_area = view_area;
278 
279     /* Compute the heights of the areas */
280     rest = view_area.height;
281 
282     height = MIN (rest, 1);
283     view->status_area.height = height;
284     rest -= height;
285 
286     height = (ruler == RULER_NONE || view->mode_flags.hex) ? 0 : 2;
287     height = MIN (rest, height);
288     view->ruler_area.height = height;
289     rest -= height;
290 
291     view->data_area.height = rest;
292 
293     /* Compute the position of the areas */
294     y = view_area.top;
295 
296     view->status_area.top = y;
297     y += view->status_area.height;
298 
299     if (ruler == RULER_TOP)
300     {
301         view->ruler_area.top = y;
302         y += view->ruler_area.height;
303     }
304 
305     view->data_area.top = y;
306     y += view->data_area.height;
307 
308     if (ruler == RULER_BOTTOM)
309         view->ruler_area.top = y;
310 }
311 
312 /* --------------------------------------------------------------------------------------------- */
313 
314 void
mcview_update_bytes_per_line(WView * view)315 mcview_update_bytes_per_line (WView * view)
316 {
317     const screen_dimen cols = view->data_area.width;
318     int bytes;
319 
320     if (cols < 9 + 17)
321         bytes = 4;
322     else
323         bytes = 4 * ((cols - 9) / ((cols <= 80) ? 17 : 18));
324 
325     g_assert (bytes != 0);
326 
327     view->bytes_per_line = bytes;
328     view->dirty = mcview_max_dirt_limit + 1;    /* To force refresh */
329 }
330 
331 /* --------------------------------------------------------------------------------------------- */
332 
333 void
mcview_display_toggle_ruler(WView * view)334 mcview_display_toggle_ruler (WView * view)
335 {
336     static const enum ruler_type next[3] =
337     {
338         RULER_TOP,
339         RULER_BOTTOM,
340         RULER_NONE
341     };
342 
343     g_assert ((size_t) ruler < 3);
344 
345     ruler = next[(size_t) ruler];
346     mcview_compute_areas (view);
347     view->dirty++;
348 }
349 
350 /* --------------------------------------------------------------------------------------------- */
351 
352 void
mcview_display_clean(WView * view)353 mcview_display_clean (WView * view)
354 {
355     Widget *w = WIDGET (view);
356 
357     tty_setcolor (VIEW_NORMAL_COLOR);
358     widget_erase (w);
359     if (view->dpy_frame_size != 0)
360         tty_draw_box (w->y, w->x, w->lines, w->cols, FALSE);
361 }
362 
363 /* --------------------------------------------------------------------------------------------- */
364 
365 void
mcview_display_ruler(WView * view)366 mcview_display_ruler (WView * view)
367 {
368     static const char ruler_chars[] = "|----*----";
369     const screen_dimen top = view->ruler_area.top;
370     const screen_dimen left = view->ruler_area.left;
371     const screen_dimen width = view->ruler_area.width;
372     const screen_dimen height = view->ruler_area.height;
373     const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
374     const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
375 
376     char r_buff[10];
377     off_t cl;
378     screen_dimen c;
379 
380     if (ruler == RULER_NONE || height < 1)
381         return;
382 
383     tty_setcolor (VIEW_BOLD_COLOR);
384     for (c = 0; c < width; c++)
385     {
386         cl = view->dpy_text_column + c;
387         if (line_row < height)
388         {
389             widget_gotoyx (view, top + line_row, left + c);
390             tty_print_char (ruler_chars[cl % 10]);
391         }
392 
393         if ((cl != 0) && (cl % 10) == 0)
394         {
395             g_snprintf (r_buff, sizeof (r_buff), "%" PRIuMAX, (uintmax_t) cl);
396             if (nums_row < height)
397             {
398                 widget_gotoyx (view, top + nums_row, left + c - 1);
399                 tty_print_string (r_buff);
400             }
401         }
402     }
403     tty_setcolor (VIEW_NORMAL_COLOR);
404 }
405 
406 /* --------------------------------------------------------------------------------------------- */
407