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         // ignore mouse wheel events in the inactive quick view panel
 171         if (widget_get_state (w, WST_FOCUSED))
 172             mcview_move_up (view, 2);
 173         else
 174             ok = FALSE;
 175         break;
 176 
 177     case MSG_MOUSE_SCROLL_DOWN:
 178         // ignore mouse wheel events in the inactive quick view panel
 179         if (widget_get_state (w, WST_FOCUSED))
 180             mcview_move_down (view, 2);
 181         else
 182             ok = FALSE;
 183         break;
 184 
 185     default:
 186         ok = FALSE;
 187         break;
 188     }
 189 
 190     if (ok)
 191         mcview_update (view);
 192 }
 193 
 194 /* --------------------------------------------------------------------------------------------- */
 195 /*** public functions ****************************************************************************/
 196 /* --------------------------------------------------------------------------------------------- */
 197 
 198 WView *
 199 mcview_new (const WRect *r, gboolean is_panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 200 {
 201     WView *view;
 202     Widget *w;
 203 
 204     view = g_new0 (WView, 1);
 205     w = WIDGET (view);
 206 
 207     widget_init (w, r, mcview_callback, mcview_mouse_callback);
 208     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
 209     w->keymap = viewer_map;
 210 
 211     mcview_clear_mode_flags (&view->mode_flags);
 212     view->hexedit_mode = FALSE;
 213     view->hex_keymap = viewer_hex_map;
 214     view->hexview_in_text = FALSE;
 215     view->locked = FALSE;
 216 
 217     view->dpy_frame_size = is_panel ? 1 : 0;
 218     view->converter = str_cnv_from_term;
 219 
 220     mcview_init (view);
 221 
 222     if (mcview_global_flags.hex)
 223         mcview_toggle_hex_mode (view);
 224     if (mcview_global_flags.nroff)
 225         mcview_toggle_nroff_mode (view);
 226     if (mcview_global_flags.wrap)
 227         mcview_toggle_wrap_mode (view);
 228     if (mcview_global_flags.magic)
 229         mcview_toggle_magic_mode (view);
 230 
 231     return view;
 232 }
 233 
 234 /* --------------------------------------------------------------------------------------------- */
 235 /** Real view only */
 236 
 237 gboolean
 238 mcview_viewer (const char *command, const vfs_path_t *file_vpath, int start_line,
     /* [previous][next][first][last][top][bottom][index][help]  */
 239                off_t search_start, off_t search_end)
 240 {
 241     gboolean succeeded;
 242     WView *lc_mcview;
 243     WDialog *view_dlg;
 244     Widget *vw, *b;
 245     WGroup *g;
 246     WRect r;
 247 
 248     // Create dialog and widgets, put them on the dialog
 249     view_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, mcview_dialog_callback,
 250                            NULL, "[Internal File Viewer]", NULL);
 251     vw = WIDGET (view_dlg);
 252     widget_want_tab (vw, TRUE);
 253 
 254     g = GROUP (view_dlg);
 255 
 256     r = vw->rect;
 257     r.lines--;
 258     lc_mcview = mcview_new (&r, FALSE);
 259     group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
 260 
 261     b = WIDGET (buttonbar_new ());
 262     group_add_widget_autopos (g, b, b->pos_flags, NULL);
 263 
 264     view_dlg->get_title = mcview_get_title;
 265 
 266     succeeded = mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line,
 267                              search_start, search_end);
 268 
 269     if (succeeded)
 270         dlg_run (view_dlg);
 271     else
 272         dlg_close (view_dlg);
 273 
 274     if (widget_get_state (vw, WST_CLOSED))
 275         widget_destroy (vw);
 276 
 277     return succeeded;
 278 }
 279 
 280 /* {{{ Miscellaneous functions }}} */
 281 
 282 /* --------------------------------------------------------------------------------------------- */
 283 
 284 gboolean
 285 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]  */
 286              off_t search_end)
 287 {
 288     gboolean retval = FALSE;
 289     vfs_path_t *vpath = NULL;
 290 
 291     g_assert (view->bytes_per_line != 0);
 292 
 293     view->filename_vpath = vfs_path_from_str (file);
 294 
 295     // get working dir
 296     if (file != NULL && file[0] != '\0')
 297     {
 298         vfs_path_free (view->workdir_vpath, TRUE);
 299 
 300         if (!g_path_is_absolute (file))
 301         {
 302             vfs_path_t *p;
 303 
 304             p = vfs_path_clone (vfs_get_raw_current_dir ());
 305             view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
 306             vfs_path_free (p, TRUE);
 307         }
 308         else
 309         {
 310             // try extract path from filename
 311             const char *fname;
 312             char *dir;
 313 
 314             fname = x_basename (file);
 315             dir = g_strndup (file, (size_t) (fname - file));
 316             view->workdir_vpath = vfs_path_from_str (dir);
 317             g_free (dir);
 318         }
 319     }
 320 
 321     if (!mcview_is_in_panel (view))
 322         view->dpy_text_column = 0;
 323 
 324     mcview_set_codeset (view);
 325 
 326     if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
 327         retval = mcview_load_command_output (view, command);
 328     else if (file != NULL && file[0] != '\0')
 329     {
 330         int fd;
 331         char tmp[BUF_MEDIUM];
 332         struct stat st;
 333 
 334         // Open the file
 335         vpath = vfs_path_from_str (file);
 336         fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
 337         if (fd == -1)
 338         {
 339             mcview_close_datasource (view);
 340             mcview_show_error (view, _ ("Cannot open\n%s"), file);
 341             vfs_path_free (view->filename_vpath, TRUE);
 342             view->filename_vpath = NULL;
 343             vfs_path_free (view->workdir_vpath, TRUE);
 344             view->workdir_vpath = NULL;
 345             goto finish;
 346         }
 347 
 348         // Make sure we are working with a regular file
 349         if (mc_fstat (fd, &st) == -1)
 350         {
 351             mc_close (fd);
 352             mcview_close_datasource (view);
 353             mcview_show_error (view, _ ("Cannot stat\n%s"), file);
 354             vfs_path_free (view->filename_vpath, TRUE);
 355             view->filename_vpath = NULL;
 356             vfs_path_free (view->workdir_vpath, TRUE);
 357             view->workdir_vpath = NULL;
 358             goto finish;
 359         }
 360 
 361         if (!S_ISREG (st.st_mode))
 362         {
 363             mc_close (fd);
 364             mcview_close_datasource (view);
 365             g_snprintf (tmp, sizeof (tmp), _ ("Cannot view\n%s\nNot a regular file"), file);
 366             mcview_show_error (view, NULL, tmp);
 367             vfs_path_free (view->filename_vpath, TRUE);
 368             view->filename_vpath = NULL;
 369             vfs_path_free (view->workdir_vpath, TRUE);
 370             view->workdir_vpath = NULL;
 371             goto finish;
 372         }
 373 
 374         if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
 375         {
 376             // Must be one of those nice files that grow (/proc)
 377             mcview_set_datasource_vfs_pipe (view, fd);
 378         }
 379         else
 380         {
 381             if (view->mode_flags.magic)
 382             {
 383                 int type;
 384 
 385                 type = get_compression_type (fd, file);
 386 
 387                 if (type != COMPRESSION_NONE)
 388                 {
 389                     char *tmp_filename;
 390                     vfs_path_t *vpath1;
 391                     int fd1;
 392 
 393                     tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
 394                     vpath1 = vfs_path_from_str (tmp_filename);
 395                     g_free (tmp_filename);
 396                     fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
 397                     vfs_path_free (vpath1, TRUE);
 398 
 399                     if (fd1 == -1)
 400                     {
 401                         mcview_close_datasource (view);
 402                         mcview_show_error (view, _ ("Cannot open\n%s\nin parse mode\n%s"), file);
 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]  */