Move widget add/del API from WDialog to WGroup.
[midnight-commander.git] / src / viewer / mcviewer.c
blobb3d1f32091b0cece0dbe5bdd26a8eafc157befc0
1 /*
2 Internal file viewer for the Midnight Commander
3 Interface functions
5 Copyright (C) 1994-2020
6 Free Software Foundation, Inc
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, 2013
18 Ilia Maslakov <il.smind@gmail.com>, 2009
20 This file is part of the Midnight Commander.
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.
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.
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/>.
36 #include <config.h>
37 #include <errno.h>
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"
46 #include "src/filemanager/layout.h"
47 #include "src/filemanager/midnight.h" /* the_menubar */
49 #include "internal.h"
51 /*** global variables ****************************************************************************/
53 mcview_mode_flags_t mcview_global_flags = {
54 .wrap = TRUE,
55 .hex = FALSE,
56 .magic = TRUE,
57 .nroff = FALSE
60 mcview_mode_flags_t mcview_altered_flags = {
61 .wrap = FALSE,
62 .hex = FALSE,
63 .magic = FALSE,
64 .nroff = FALSE
67 gboolean mcview_remember_file_position = FALSE;
69 /* Maxlimit for skipping updates */
70 int mcview_max_dirt_limit = 10;
72 /* Scrolling is done in pages or line increments */
73 gboolean mcview_mouse_move_pages = TRUE;
75 /* end of file will be showen from mcview_show_eof */
76 char *mcview_show_eof = NULL;
78 /*** file scope macro definitions ****************************************************************/
80 /*** file scope type declarations ****************************************************************/
82 /*** file scope variables ************************************************************************/
84 /* --------------------------------------------------------------------------------------------- */
85 /*** file scope functions ************************************************************************/
86 /* --------------------------------------------------------------------------------------------- */
88 static void
89 mcview_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
91 WView *view = (WView *) w;
92 gboolean ok = TRUE;
94 switch (msg)
96 case MSG_MOUSE_DOWN:
97 if (mcview_is_in_panel (view))
99 if (event->y == WIDGET (w->owner)->y)
101 /* return MOU_UNHANDLED */
102 event->result.abort = TRUE;
103 /* don't draw viewer over menu */
104 ok = FALSE;
105 break;
108 if (!widget_get_state (w, WST_FOCUSED))
110 /* Grab focus */
111 change_panel ();
114 MC_FALLTHROUGH;
116 case MSG_MOUSE_CLICK:
117 if (!view->mode_flags.wrap)
119 /* Scrolling left and right */
120 screen_dimen x;
122 x = event->x + 1; /* FIXME */
124 if (x < view->data_area.width * 1 / 4)
126 mcview_move_left (view, 1);
127 event->result.repeat = msg == MSG_MOUSE_DOWN;
129 else if (x < view->data_area.width * 3 / 4)
131 /* ignore the click */
132 ok = FALSE;
134 else
136 mcview_move_right (view, 1);
137 event->result.repeat = msg == MSG_MOUSE_DOWN;
140 else
142 /* Scrolling up and down */
143 screen_dimen y;
145 y = event->y + 1; /* FIXME */
147 if (y < view->data_area.top + view->data_area.height * 1 / 3)
149 if (mcview_mouse_move_pages)
150 mcview_move_up (view, view->data_area.height / 2);
151 else
152 mcview_move_up (view, 1);
154 event->result.repeat = msg == MSG_MOUSE_DOWN;
156 else if (y < view->data_area.top + view->data_area.height * 2 / 3)
158 /* ignore the click */
159 ok = FALSE;
161 else
163 if (mcview_mouse_move_pages)
164 mcview_move_down (view, view->data_area.height / 2);
165 else
166 mcview_move_down (view, 1);
168 event->result.repeat = msg == MSG_MOUSE_DOWN;
171 break;
173 case MSG_MOUSE_SCROLL_UP:
174 mcview_move_up (view, 2);
175 break;
177 case MSG_MOUSE_SCROLL_DOWN:
178 mcview_move_down (view, 2);
179 break;
181 default:
182 ok = FALSE;
183 break;
186 if (ok)
187 mcview_update (view);
190 /* --------------------------------------------------------------------------------------------- */
191 /*** public functions ****************************************************************************/
192 /* --------------------------------------------------------------------------------------------- */
194 WView *
195 mcview_new (int y, int x, int lines, int cols, gboolean is_panel)
197 WView *view;
198 Widget *w;
200 view = g_new0 (WView, 1);
201 w = WIDGET (view);
202 widget_init (w, y, x, lines, cols, mcview_callback, mcview_mouse_callback);
203 w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
205 mcview_clear_mode_flags (&view->mode_flags);
206 view->hexedit_mode = FALSE;
207 view->hexview_in_text = FALSE;
208 view->locked = FALSE;
210 view->dpy_frame_size = is_panel ? 1 : 0;
211 view->converter = str_cnv_from_term;
213 mcview_init (view);
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);
224 return view;
227 /* --------------------------------------------------------------------------------------------- */
228 /** Real view only */
230 gboolean
231 mcview_viewer (const char *command, const vfs_path_t * file_vpath, int start_line,
232 off_t search_start, off_t search_end)
234 gboolean succeeded;
235 WView *lc_mcview;
236 WDialog *view_dlg;
237 Widget *vw, *b;
238 WGroup *g;
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);
246 g = GROUP (view_dlg);
248 lc_mcview = mcview_new (vw->y, vw->x, vw->lines - 1, vw->cols, FALSE);
249 group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
251 b = WIDGET (buttonbar_new (TRUE));
252 group_add_widget_autopos (g, b, b->pos_flags, NULL);
254 view_dlg->get_title = mcview_get_title;
256 succeeded =
257 mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line, search_start,
258 search_end);
260 if (succeeded)
261 dlg_run (view_dlg);
262 else
263 dlg_stop (view_dlg);
265 if (widget_get_state (vw, WST_CLOSED))
266 dlg_destroy (view_dlg);
268 return succeeded;
271 /* {{{ Miscellaneous functions }}} */
273 /* --------------------------------------------------------------------------------------------- */
275 gboolean
276 mcview_load (WView * view, const char *command, const char *file, int start_line,
277 off_t search_start, off_t search_end)
279 gboolean retval = FALSE;
280 vfs_path_t *vpath = NULL;
282 g_assert (view->bytes_per_line != 0);
284 view->filename_vpath = vfs_path_from_str (file);
286 /* get working dir */
287 if (file != NULL && file[0] != '\0')
289 vfs_path_free (view->workdir_vpath);
291 if (!g_path_is_absolute (file))
293 vfs_path_t *p;
295 p = vfs_path_clone (vfs_get_raw_current_dir ());
296 view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
297 vfs_path_free (p);
299 else
301 /* try extract path from filename */
302 const char *fname;
303 char *dir;
305 fname = x_basename (file);
306 dir = g_strndup (file, (size_t) (fname - file));
307 view->workdir_vpath = vfs_path_from_str (dir);
308 g_free (dir);
312 if (!mcview_is_in_panel (view))
313 view->dpy_text_column = 0;
315 #ifdef HAVE_CHARSET
316 mcview_set_codeset (view);
317 #endif
319 if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
320 retval = mcview_load_command_output (view, command);
321 else if (file != NULL && file[0] != '\0')
323 int fd;
324 char tmp[BUF_MEDIUM];
325 struct stat st;
327 /* Open the file */
328 vpath = vfs_path_from_str (file);
329 fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
330 if (fd == -1)
332 g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\"\n%s"),
333 file, unix_error_string (errno));
334 mcview_close_datasource (view);
335 mcview_show_error (view, tmp);
336 vfs_path_free (view->filename_vpath);
337 view->filename_vpath = NULL;
338 vfs_path_free (view->workdir_vpath);
339 view->workdir_vpath = NULL;
340 goto finish;
343 /* Make sure we are working with a regular file */
344 if (mc_fstat (fd, &st) == -1)
346 mc_close (fd);
347 g_snprintf (tmp, sizeof (tmp), _("Cannot stat \"%s\"\n%s"),
348 file, unix_error_string (errno));
349 mcview_close_datasource (view);
350 mcview_show_error (view, tmp);
351 vfs_path_free (view->filename_vpath);
352 view->filename_vpath = NULL;
353 vfs_path_free (view->workdir_vpath);
354 view->workdir_vpath = NULL;
355 goto finish;
358 if (!S_ISREG (st.st_mode))
360 mc_close (fd);
361 mcview_close_datasource (view);
362 mcview_show_error (view, _("Cannot view: not a regular file"));
363 vfs_path_free (view->filename_vpath);
364 view->filename_vpath = NULL;
365 vfs_path_free (view->workdir_vpath);
366 view->workdir_vpath = NULL;
367 goto finish;
370 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
372 /* Must be one of those nice files that grow (/proc) */
373 mcview_set_datasource_vfs_pipe (view, fd);
375 else
377 if (view->mode_flags.magic)
379 int type;
381 type = get_compression_type (fd, file);
383 if (type != COMPRESSION_NONE)
385 char *tmp_filename;
386 vfs_path_t *vpath1;
387 int fd1;
389 tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
390 vpath1 = vfs_path_from_str (tmp_filename);
391 g_free (tmp_filename);
392 fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
393 vfs_path_free (vpath1);
395 if (fd1 == -1)
397 g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\" in parse mode\n%s"),
398 file, unix_error_string (errno));
399 mcview_close_datasource (view);
400 mcview_show_error (view, tmp);
402 else
404 mc_close (fd);
405 fd = fd1;
406 mc_fstat (fd, &st);
411 mcview_set_datasource_file (view, fd, &st);
413 retval = TRUE;
416 finish:
417 view->command = g_strdup (command);
418 view->dpy_start = 0;
419 view->dpy_paragraph_skip_lines = 0;
420 mcview_state_machine_init (&view->dpy_state_top, 0);
421 view->dpy_wrap_dirty = FALSE;
422 view->force_max = -1;
423 view->dpy_text_column = 0;
425 mcview_compute_areas (view);
426 mcview_update_bytes_per_line (view);
428 if (mcview_remember_file_position && view->filename_vpath != NULL && start_line == 0)
430 long line, col;
431 off_t new_offset, max_offset;
433 load_file_position (view->filename_vpath, &line, &col, &new_offset, &view->saved_bookmarks);
434 max_offset = mcview_get_filesize (view) - 1;
435 if (max_offset < 0)
436 new_offset = 0;
437 else
438 new_offset = MIN (new_offset, max_offset);
439 if (!view->mode_flags.hex)
441 view->dpy_start = mcview_bol (view, new_offset, 0);
442 view->dpy_wrap_dirty = TRUE;
444 else
446 view->dpy_start = new_offset - new_offset % view->bytes_per_line;
447 view->hex_cursor = new_offset;
450 else if (start_line > 0)
451 mcview_moveto (view, start_line - 1, 0);
453 view->search_start = search_start;
454 view->search_end = search_end;
455 view->hexedit_lownibble = FALSE;
456 view->hexview_in_text = FALSE;
457 view->change_list = NULL;
458 vfs_path_free (vpath);
459 return retval;
462 /* --------------------------------------------------------------------------------------------- */