root/src/viewer/mcviewer.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. mcview_mouse_callback
  2. mcview_new
  3. mcview_viewer
  4. mcview_load

   1 /*
   2    Internal file viewer for the Midnight Commander
   3    Interface functions
   4 
   5    Copyright (C) 1994-2025
   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, 2013
  17    Andrew Borodin <aborodin@vmail.ru>, 2009-2022
  18    Ilia Maslakov <il.smind@gmail.com>, 2009
  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 <https://www.gnu.org/licenses/>.
  34  */
  35 
  36 #include <config.h>
  37 #include <errno.h>
  38 
  39 #include "lib/global.h"
  40 #include "lib/tty/tty.h"
  41 #include "lib/vfs/vfs.h"
  42 #include "lib/strutil.h"
  43 #include "lib/util.h"  // load_file_position()
  44 #include "lib/widget.h"
  45 
  46 #include "src/filemanager/layout.h"
  47 #include "src/filemanager/filemanager.h"  // the_menubar
  48 
  49 #include "internal.h"
  50 
  51 /*** global variables ****************************************************************************/
  52 
  53 mcview_mode_flags_t mcview_global_flags = {
  54     .wrap = TRUE, .hex = FALSE, .magic = TRUE, .nroff = FALSE
  55 };
  56 
  57 mcview_mode_flags_t mcview_altered_flags = {
  58     .wrap = FALSE, .hex = FALSE, .magic = FALSE, .nroff = FALSE
  59 };
  60 
  61 gboolean mcview_remember_file_position = FALSE;
  62 
  63 /* Maxlimit for skipping updates */
  64 int mcview_max_dirt_limit = 10;
  65 
  66 /* Scrolling is done in pages or line increments */
  67 gboolean mcview_mouse_move_pages = TRUE;
  68 
  69 /* end of file will be showen from mcview_show_eof */
  70 char *mcview_show_eof = NULL;
  71 
  72 /*** file scope macro definitions ****************************************************************/
  73 
  74 /*** file scope type declarations ****************************************************************/
  75 
  76 /*** forward declarations (file scope functions) *************************************************/
  77 
  78 /*** file scope variables ************************************************************************/
  79 
  80 /* --------------------------------------------------------------------------------------------- */
  81 /*** file scope functions ************************************************************************/
  82 /* --------------------------------------------------------------------------------------------- */
  83 
  84 static void
  85 mcview_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
  86 {
  87     WView *view = (WView *) w;
  88     const WRect *r = &view->data_area;
  89     gboolean ok = TRUE;
  90 
  91     switch (msg)
  92     {
  93     case MSG_MOUSE_DOWN:
  94         if (mcview_is_in_panel (view))
  95         {
  96             if (event->y == WIDGET (w->owner)->rect.y)
  97             {
  98                 // return MOU_UNHANDLED
  99                 event->result.abort = TRUE;
 100                 // don't draw viewer over menu
 101                 ok = FALSE;
 102                 break;
 103             }
 104 
 105             if (!widget_get_state (w, WST_FOCUSED))
 106             {
 107                 // Grab focus
 108                 (void) change_panel ();
 109             }
 110         }
 111         MC_FALLTHROUGH;
 112 
 113     case MSG_MOUSE_CLICK:
 114         if (!view->mode_flags.wrap)
 115         {
 116             // Scrolling left and right
 117             int x;
 118 
 119             x = event->x + 1;  // FIXME
 120 
 121             if (x < r->cols * 1 / 4)
 122             {
 123                 mcview_move_left (view, 1);
 124                 event->result.repeat = msg == MSG_MOUSE_DOWN;
 125             }
 126             else if (x < r->cols * 3 / 4)
 127             {
 128                 // ignore the click
 129                 ok = FALSE;
 130             }
 131             else
 132             {
 133                 mcview_move_right (view, 1);
 134                 event->result.repeat = msg == MSG_MOUSE_DOWN;
 135             }
 136         }
 137         else
 138         {
 139             // Scrolling up and down
 140             int y;
 141 
 142             y = event->y + 1;  // FIXME
 143 
 144             if (y < r->y + r->lines * 1 / 3)
 145             {
 146                 if (mcview_mouse_move_pages)
 147                     mcview_move_up (view, r->lines / 2);
 148                 else
 149                     mcview_move_up (view, 1);
 150 
 151                 event->result.repeat = msg == MSG_MOUSE_DOWN;
 152             }
 153             else if (y < r->y + r->lines * 2 / 3)
 154             {
 155                 // ignore the click
 156                 ok = FALSE;
 157             }
 158             else
 159             {
 160                 if (mcview_mouse_move_pages)
 161                     mcview_move_down (view, r->lines / 2);
 162                 else
 163                     mcview_move_down (view, 1);
 164 
 165                 event->result.repeat = msg == MSG_MOUSE_DOWN;
 166             }
 167         }
 168         break;
 169 
 170     case MSG_MOUSE_SCROLL_UP:
 171         mcview_move_up (view, 2);
 172         break;
 173 
 174     case MSG_MOUSE_SCROLL_DOWN:
 175         mcview_move_down (view, 2);
 176         break;
 177 
 178     default:
 179         ok = FALSE;
 180         break;
 181     }
 182 
 183     if (ok)
 184         mcview_update (view);
 185 }
 186 
 187 /* --------------------------------------------------------------------------------------------- */
 188 /*** public functions ****************************************************************************/
 189 /* --------------------------------------------------------------------------------------------- */
 190 
 191 WView *
 192 mcview_new (const WRect *r, gboolean is_panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 193 {
 194     WView *view;
 195     Widget *w;
 196 
 197     view = g_new0 (WView, 1);
 198     w = WIDGET (view);
 199 
 200     widget_init (w, r, mcview_callback, mcview_mouse_callback);
 201     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
 202     w->keymap = viewer_map;
 203 
 204     mcview_clear_mode_flags (&view->mode_flags);
 205     view->hexedit_mode = FALSE;
 206     view->hex_keymap = viewer_hex_map;
 207     view->hexview_in_text = FALSE;
 208     view->locked = FALSE;
 209 
 210     view->dpy_frame_size = is_panel ? 1 : 0;
 211     view->converter = str_cnv_from_term;
 212 
 213     mcview_init (view);
 214 
 215     if (mcview_global_flags.hex)
 216         mcview_toggle_hex_mode (view);
 217     if (mcview_global_flags.nroff)
 218         mcview_toggle_nroff_mode (view);
 219     if (mcview_global_flags.wrap)
 220         mcview_toggle_wrap_mode (view);
 221     if (mcview_global_flags.magic)
 222         mcview_toggle_magic_mode (view);
 223 
 224     return view;
 225 }
 226 
 227 /* --------------------------------------------------------------------------------------------- */
 228 /** Real view only */
 229 
 230 gboolean
 231 mcview_viewer (const char *command, const vfs_path_t *file_vpath, int start_line,
     /* [previous][next][first][last][top][bottom][index][help]  */
 232                off_t search_start, off_t search_end)
 233 {
 234     gboolean succeeded;
 235     WView *lc_mcview;
 236     WDialog *view_dlg;
 237     Widget *vw, *b;
 238     WGroup *g;
 239     WRect r;
 240 
 241     // Create dialog and widgets, put them on the dialog
 242     view_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, mcview_dialog_callback,
 243                            NULL, "[Internal File Viewer]", NULL);
 244     vw = WIDGET (view_dlg);
 245     widget_want_tab (vw, TRUE);
 246 
 247     g = GROUP (view_dlg);
 248 
 249     r = vw->rect;
 250     r.lines--;
 251     lc_mcview = mcview_new (&r, FALSE);
 252     group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
 253 
 254     b = WIDGET (buttonbar_new ());
 255     group_add_widget_autopos (g, b, b->pos_flags, NULL);
 256 
 257     view_dlg->get_title = mcview_get_title;
 258 
 259     succeeded = mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line,
 260                              search_start, search_end);
 261 
 262     if (succeeded)
 263         dlg_run (view_dlg);
 264     else
 265         dlg_close (view_dlg);
 266 
 267     if (widget_get_state (vw, WST_CLOSED))
 268         widget_destroy (vw);
 269 
 270     return succeeded;
 271 }
 272 
 273 /* {{{ Miscellaneous functions }}} */
 274 
 275 /* --------------------------------------------------------------------------------------------- */
 276 
 277 gboolean
 278 mcview_load (WView *view, const char *command, const char *file, int start_line, off_t search_start,
     /* [previous][next][first][last][top][bottom][index][help]  */
 279              off_t search_end)
 280 {
 281     gboolean retval = FALSE;
 282     vfs_path_t *vpath = NULL;
 283 
 284     g_assert (view->bytes_per_line != 0);
 285 
 286     view->filename_vpath = vfs_path_from_str (file);
 287 
 288     // get working dir
 289     if (file != NULL && file[0] != '\0')
 290     {
 291         vfs_path_free (view->workdir_vpath, TRUE);
 292 
 293         if (!g_path_is_absolute (file))
 294         {
 295             vfs_path_t *p;
 296 
 297             p = vfs_path_clone (vfs_get_raw_current_dir ());
 298             view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
 299             vfs_path_free (p, TRUE);
 300         }
 301         else
 302         {
 303             // try extract path from filename
 304             const char *fname;
 305             char *dir;
 306 
 307             fname = x_basename (file);
 308             dir = g_strndup (file, (size_t) (fname - file));
 309             view->workdir_vpath = vfs_path_from_str (dir);
 310             g_free (dir);
 311         }
 312     }
 313 
 314     if (!mcview_is_in_panel (view))
 315         view->dpy_text_column = 0;
 316 
 317 #ifdef HAVE_CHARSET
 318     mcview_set_codeset (view);
 319 #endif
 320 
 321     if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
 322         retval = mcview_load_command_output (view, command);
 323     else if (file != NULL && file[0] != '\0')
 324     {
 325         int fd;
 326         char tmp[BUF_MEDIUM];
 327         struct stat st;
 328 
 329         // Open the file
 330         vpath = vfs_path_from_str (file);
 331         fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
 332         if (fd == -1)
 333         {
 334             g_snprintf (tmp, sizeof (tmp), _ ("Cannot open \"%s\"\n%s"), file,
 335                         unix_error_string (errno));
 336             mcview_close_datasource (view);
 337             mcview_show_error (view, tmp);
 338             vfs_path_free (view->filename_vpath, TRUE);
 339             view->filename_vpath = NULL;
 340             vfs_path_free (view->workdir_vpath, TRUE);
 341             view->workdir_vpath = NULL;
 342             goto finish;
 343         }
 344 
 345         // Make sure we are working with a regular file
 346         if (mc_fstat (fd, &st) == -1)
 347         {
 348             mc_close (fd);
 349             g_snprintf (tmp, sizeof (tmp), _ ("Cannot stat \"%s\"\n%s"), file,
 350                         unix_error_string (errno));
 351             mcview_close_datasource (view);
 352             mcview_show_error (view, tmp);
 353             vfs_path_free (view->filename_vpath, TRUE);
 354             view->filename_vpath = NULL;
 355             vfs_path_free (view->workdir_vpath, TRUE);
 356             view->workdir_vpath = NULL;
 357             goto finish;
 358         }
 359 
 360         if (!S_ISREG (st.st_mode))
 361         {
 362             mc_close (fd);
 363             mcview_close_datasource (view);
 364             mcview_show_error (view, _ ("Cannot view: not a regular file"));
 365             vfs_path_free (view->filename_vpath, TRUE);
 366             view->filename_vpath = NULL;
 367             vfs_path_free (view->workdir_vpath, TRUE);
 368             view->workdir_vpath = NULL;
 369             goto finish;
 370         }
 371 
 372         if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
 373         {
 374             // Must be one of those nice files that grow (/proc)
 375             mcview_set_datasource_vfs_pipe (view, fd);
 376         }
 377         else
 378         {
 379             if (view->mode_flags.magic)
 380             {
 381                 int type;
 382 
 383                 type = get_compression_type (fd, file);
 384 
 385                 if (type != COMPRESSION_NONE)
 386                 {
 387                     char *tmp_filename;
 388                     vfs_path_t *vpath1;
 389                     int fd1;
 390 
 391                     tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
 392                     vpath1 = vfs_path_from_str (tmp_filename);
 393                     g_free (tmp_filename);
 394                     fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
 395                     vfs_path_free (vpath1, TRUE);
 396 
 397                     if (fd1 == -1)
 398                     {
 399                         g_snprintf (tmp, sizeof (tmp), _ ("Cannot open \"%s\" in parse mode\n%s"),
 400                                     file, unix_error_string (errno));
 401                         mcview_close_datasource (view);
 402                         mcview_show_error (view, tmp);
 403                     }
 404                     else
 405                     {
 406                         mc_close (fd);
 407                         fd = fd1;
 408                         mc_fstat (fd, &st);
 409                     }
 410                 }
 411             }
 412 
 413             mcview_set_datasource_file (view, fd, &st);
 414         }
 415         retval = TRUE;
 416     }
 417 
 418 finish:
 419     view->command = g_strdup (command);
 420     view->dpy_start = 0;
 421     view->dpy_paragraph_skip_lines = 0;
 422     mcview_state_machine_init (&view->dpy_state_top, 0);
 423     view->dpy_wrap_dirty = FALSE;
 424     view->force_max = -1;
 425     view->dpy_text_column = 0;
 426 
 427     mcview_compute_areas (view);
 428     mcview_update_bytes_per_line (view);
 429 
 430     if (mcview_remember_file_position && view->filename_vpath != NULL && start_line == 0)
 431     {
 432         long line, col;
 433         off_t new_offset, max_offset;
 434 
 435         load_file_position (view->filename_vpath, &line, &col, &new_offset, &view->saved_bookmarks);
 436         max_offset = mcview_get_filesize (view) - 1;
 437         if (max_offset < 0)
 438             new_offset = 0;
 439         else
 440             new_offset = MIN (new_offset, max_offset);
 441         if (!view->mode_flags.hex)
 442         {
 443             view->dpy_start = mcview_bol (view, new_offset, 0);
 444             view->dpy_wrap_dirty = TRUE;
 445         }
 446         else
 447         {
 448             view->dpy_start = new_offset - new_offset % view->bytes_per_line;
 449             view->hex_cursor = new_offset;
 450         }
 451     }
 452     else if (start_line > 0)
 453         mcview_moveto (view, start_line - 1, 0);
 454 
 455     view->search_start = search_start;
 456     view->search_end = search_end;
 457     view->hexedit_lownibble = FALSE;
 458     view->hexview_in_text = FALSE;
 459     view->change_list = NULL;
 460     vfs_path_free (vpath, TRUE);
 461     return retval;
 462 }
 463 
 464 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */