Manual pages: mcmcdiffmceditmcview

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

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