Fix mode switch after CK_FileNext/CK_FilePrev.
[midnight-commander.git] / src / viewer / actions_cmd.c
blob502454202434c0f472121e0278607f6dd7c67973
1 /*
2 Internal file viewer for the Midnight Commander
3 Callback function for some actions (hotkeys, menu)
5 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
6 2004, 2005, 2006, 2007, 2009, 2011, 2013
7 The Free Software Foundation, Inc.
9 Written by:
10 Miguel de Icaza, 1994, 1995, 1998
11 Janne Kukonlehto, 1994, 1995
12 Jakub Jelinek, 1995
13 Joseph M. Hinkle, 1996
14 Norbert Warmuth, 1997
15 Pavel Machek, 1998
16 Roland Illig <roland.illig@gmx.de>, 2004, 2005
17 Slava Zanko <slavazanko@google.com>, 2009
18 Andrew Borodin <aborodin@vmail.ru>, 2009, 2013
19 Ilia Maslakov <il.smind@gmail.com>, 2009
21 This file is part of the Midnight Commander.
23 The Midnight Commander is free software: you can redistribute it
24 and/or modify it under the terms of the GNU General Public License as
25 published by the Free Software Foundation, either version 3 of the License,
26 or (at your option) any later version.
28 The Midnight Commander is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
33 You should have received a copy of the GNU General Public License
34 along with this program. If not, see <http://www.gnu.org/licenses/>.
38 The functions in this section can be bound to hotkeys. They are all
39 of the same type (taking a pointer to mcview_t as parameter and
40 returning void). TODO: In the not-too-distant future, these commands
41 will become fully configurable, like they already are in the
42 internal editor. By convention, all the function names end in
43 "_cmd".
46 #include <config.h>
48 #include <errno.h>
49 #include <stdlib.h>
51 #include "lib/global.h"
53 #include "lib/tty/tty.h"
54 #include "lib/tty/key.h" /* is_idle() */
55 #include "lib/lock.h" /* lock_file() */
56 #include "lib/util.h"
57 #include "lib/widget.h"
58 #ifdef HAVE_CHARSET
59 #include "lib/charsets.h"
60 #endif
61 #include "lib/event.h" /* mc_event_raise() */
63 #include "src/filemanager/layout.h"
64 #include "src/filemanager/cmd.h"
65 #include "src/filemanager/midnight.h" /* current_panel */
66 #include "src/filemanager/ext.h" /* regex_command_for() */
68 #include "src/history.h"
69 #include "src/execute.h"
70 #include "src/keybind-defaults.h"
72 #include "internal.h"
74 /*** global variables ****************************************************************************/
76 /*** file scope macro definitions ****************************************************************/
78 /*** file scope type declarations ****************************************************************/
80 /*** file scope variables ************************************************************************/
82 /* --------------------------------------------------------------------------------------------- */
83 /*** file scope functions ************************************************************************/
84 /* --------------------------------------------------------------------------------------------- */
86 static void
87 mcview_remove_ext_script (mcview_t * view)
89 if (view->ext_script != NULL)
91 mc_unlink (view->ext_script);
92 vfs_path_free (view->ext_script);
93 view->ext_script = NULL;
97 /* --------------------------------------------------------------------------------------------- */
99 /* Both views */
100 static void
101 mcview_search (mcview_t * view)
103 if (mcview_dialog_search (view))
104 mcview_do_search (view);
107 /* --------------------------------------------------------------------------------------------- */
109 static void
110 mcview_continue_search_cmd (mcview_t * view)
112 if (view->last_search_string != NULL)
114 mcview_do_search (view);
116 else
118 /* find last search string in history */
119 GList *history;
120 history = history_get (MC_HISTORY_SHARED_SEARCH);
121 if (history != NULL && history->data != NULL)
123 view->last_search_string = (gchar *) g_strdup (history->data);
124 history = g_list_first (history);
125 g_list_foreach (history, (GFunc) g_free, NULL);
126 g_list_free (history);
128 view->search = mc_search_new (view->last_search_string, -1);
129 view->search_nroff_seq = mcview_nroff_seq_new (view);
131 if (!view->search)
133 /* if not... then ask for an expression */
134 g_free (view->last_search_string);
135 view->last_search_string = NULL;
136 mcview_search (view);
138 else
140 view->search->search_type = mcview_search_options.type;
141 view->search->is_all_charsets = mcview_search_options.all_codepages;
142 view->search->is_case_sensitive = mcview_search_options.case_sens;
143 view->search->whole_words = mcview_search_options.whole_words;
144 view->search->search_fn = mcview_search_cmd_callback;
145 view->search->update_fn = mcview_search_update_cmd_callback;
147 mcview_do_search (view);
150 else
152 /* if not... then ask for an expression */
153 g_free (view->last_search_string);
154 view->last_search_string = NULL;
155 mcview_search (view);
160 /* --------------------------------------------------------------------------------------------- */
162 static void
163 mcview_hook (void *v)
165 mcview_t *view = (mcview_t *) v;
166 WPanel *panel;
168 /* If the user is busy typing, wait until he finishes to update the
169 screen */
170 if (!is_idle ())
172 if (!hook_present (idle_hook, mcview_hook))
173 add_hook (&idle_hook, mcview_hook, v);
174 return;
177 delete_hook (&idle_hook, mcview_hook);
179 if (get_current_type () == view_listing)
180 panel = current_panel;
181 else if (get_other_type () == view_listing)
182 panel = other_panel;
183 else
184 return;
186 mcview_done (view);
187 mcview_init (view);
188 mcview_load (view, 0, panel->dir.list[panel->selected].fname, 0);
189 mcview_display (view);
192 /* --------------------------------------------------------------------------------------------- */
194 static cb_ret_t
195 mcview_handle_editkey (mcview_t * view, int key)
197 struct hexedit_change_node *node;
198 int byte_val;
200 /* Has there been a change at this position? */
201 node = view->change_list;
202 while ((node != NULL) && (node->offset != view->hex_cursor))
203 node = node->next;
205 if (!view->hexview_in_text)
207 /* Hex editing */
208 unsigned int hexvalue = 0;
210 if (key >= '0' && key <= '9')
211 hexvalue = 0 + (key - '0');
212 else if (key >= 'A' && key <= 'F')
213 hexvalue = 10 + (key - 'A');
214 else if (key >= 'a' && key <= 'f')
215 hexvalue = 10 + (key - 'a');
216 else
217 return MSG_NOT_HANDLED;
219 if (node != NULL)
220 byte_val = node->value;
221 else
222 mcview_get_byte (view, view->hex_cursor, &byte_val);
224 if (view->hexedit_lownibble)
225 byte_val = (byte_val & 0xf0) | (hexvalue);
226 else
227 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
229 else
231 /* Text editing */
232 if (key < 256 && ((key == '\n') || is_printable (key)))
233 byte_val = key;
234 else
235 return MSG_NOT_HANDLED;
238 if ((view->filename_vpath != NULL)
239 && (*(vfs_path_get_last_path_str (view->filename_vpath)) != '\0')
240 && (view->change_list == NULL))
241 view->locked = lock_file (view->filename_vpath);
243 if (node == NULL)
245 node = g_new (struct hexedit_change_node, 1);
246 node->offset = view->hex_cursor;
247 node->value = byte_val;
248 mcview_enqueue_change (&view->change_list, node);
250 else
251 node->value = byte_val;
253 view->dirty++;
254 mcview_move_right (view, 1);
256 return MSG_HANDLED;
259 /* --------------------------------------------------------------------------------------------- */
261 static void
262 mcview_load_next_prev_init (mcview_t * view)
264 if (mc_global.mc_run_mode != MC_RUN_VIEWER)
266 /* get file list from current panel. Update it each time */
267 view->dir = &current_panel->dir;
268 view->dir_count = &current_panel->count;
269 view->dir_idx = &current_panel->selected;
271 else if (view->dir == NULL)
273 /* Run from command line */
274 /* Run 1st time. Load/get directory */
276 /* TODO: check mtime of directory to reload it */
278 char *full_fname;
279 const char *fname;
280 size_t fname_len;
281 int i;
283 /* load directory where requested file is */
284 view->dir = g_new0 (dir_list, 1);
285 view->dir_count = g_new (int, 1);
286 view->dir_idx = g_new (int, 1);
288 *view->dir_count = do_load_dir (view->workdir_vpath, view->dir, (sortfn *) sort_name, FALSE,
289 TRUE, FALSE, NULL);
291 full_fname = vfs_path_to_str (view->filename_vpath);
292 fname = x_basename (full_fname);
293 fname_len = strlen (fname);
295 /* search current file in the list */
296 for (i = 0; i != *view->dir_count; i++)
298 const file_entry *fe = &view->dir->list[i];
300 if (fname_len == fe->fnamelen && strncmp (fname, fe->fname, fname_len) == 0)
301 break;
304 g_free (full_fname);
306 *view->dir_idx = i;
310 /* --------------------------------------------------------------------------------------------- */
312 static void
313 mcview_scan_for_file (mcview_t * view, int direction)
315 int i;
317 for (i = *view->dir_idx + direction; i != *view->dir_idx; i += direction)
319 if (i < 0)
320 i = *view->dir_count - 1;
321 if (i == *view->dir_count)
322 i = 0;
323 if (!S_ISDIR (view->dir->list[i].st.st_mode))
324 break;
327 *view->dir_idx = i;
330 /* --------------------------------------------------------------------------------------------- */
332 static void
333 mcview_load_next_prev (mcview_t * view, int direction)
335 dir_list *dir;
336 int *dir_count, *dir_idx;
337 vfs_path_t *vfile;
338 vfs_path_t *ext_script = NULL;
340 mcview_load_next_prev_init (view);
341 mcview_scan_for_file (view, direction);
343 /* reinit view */
344 dir = view->dir;
345 dir_count = view->dir_count;
346 dir_idx = view->dir_idx;
347 view->dir = NULL;
348 view->dir_count = NULL;
349 view->dir_idx = NULL;
350 vfile = vfs_path_append_new (view->workdir_vpath, dir->list[*dir_idx].fname, (char *) NULL);
351 mcview_done (view);
352 mcview_remove_ext_script (view);
353 mcview_init (view);
354 if (regex_command_for (view, vfile, "View", &ext_script) == 0)
356 char *file;
358 file = vfs_path_to_str (vfile);
359 mcview_load (view, NULL, file, 0);
360 g_free (file);
362 vfs_path_free (vfile);
363 view->dir = dir;
364 view->dir_count = dir_count;
365 view->dir_idx = dir_idx;
366 view->ext_script = ext_script;
368 view->dpy_bbar_dirty = FALSE; /* FIXME */
369 view->dirty++;
372 /* --------------------------------------------------------------------------------------------- */
374 static cb_ret_t
375 mcview_execute_cmd (mcview_t * view, unsigned long command)
377 int res = MSG_HANDLED;
379 switch (command)
381 case CK_Help:
383 ev_help_t event_data = { NULL, "[Internal File Viewer]" };
384 mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
386 break;
387 case CK_WrapMode:
388 /* Toggle between wrapped and unwrapped view */
389 mcview_toggle_wrap_mode (view);
390 break;
391 case CK_HexEditMode:
392 /* Toggle between hexview and hexedit mode */
393 mcview_toggle_hexedit_mode (view);
394 break;
395 case CK_HexMode:
396 /* Toggle between hex view and text view */
397 mcview_toggle_hex_mode (view);
398 break;
399 case CK_Goto:
401 off_t addr;
403 if (mcview_dialog_goto (view, &addr))
405 if (addr >= 0)
406 mcview_moveto_offset (view, addr);
407 else
409 message (D_ERROR, _("Warning"), _("Invalid value"));
410 view->dirty++;
413 break;
415 case CK_Save:
416 mcview_hexedit_save_changes (view);
417 break;
418 case CK_Search:
419 mcview_search (view);
420 break;
421 case CK_SearchForward:
422 mcview_search_options.backwards = FALSE;
423 mcview_search (view);
424 break;
425 case CK_SearchBackward:
426 mcview_search_options.backwards = TRUE;
427 mcview_search (view);
428 break;
429 case CK_MagicMode:
430 mcview_toggle_magic_mode (view);
431 break;
432 case CK_NroffMode:
433 mcview_toggle_nroff_mode (view);
434 break;
435 case CK_ToggleNavigation:
436 view->hexview_in_text = !view->hexview_in_text;
437 view->dirty++;
438 break;
439 case CK_Home:
440 mcview_moveto_bol (view);
441 break;
442 case CK_End:
443 mcview_moveto_eol (view);
444 break;
445 case CK_Left:
446 mcview_move_left (view, 1);
447 break;
448 case CK_Right:
449 mcview_move_right (view, 1);
450 break;
451 case CK_LeftQuick:
452 if (!view->hex_mode)
453 mcview_move_left (view, 10);
454 break;
455 case CK_RightQuick:
456 if (!view->hex_mode)
457 mcview_move_right (view, 10);
458 break;
459 case CK_SearchContinue:
460 mcview_continue_search_cmd (view);
461 break;
462 case CK_SearchForwardContinue:
463 mcview_search_options.backwards = FALSE;
464 mcview_continue_search_cmd (view);
465 break;
466 case CK_SearchBackwardContinue:
467 mcview_search_options.backwards = TRUE;
468 mcview_continue_search_cmd (view);
469 break;
470 case CK_Ruler:
471 mcview_display_toggle_ruler (view);
472 break;
473 case CK_Up:
474 mcview_move_up (view, 1);
475 break;
476 case CK_Down:
477 mcview_move_down (view, 1);
478 break;
479 case CK_HalfPageUp:
480 mcview_move_up (view, (view->data_area.height + 1) / 2);
481 break;
482 case CK_HalfPageDown:
483 mcview_move_down (view, (view->data_area.height + 1) / 2);
484 break;
485 case CK_PageUp:
486 mcview_move_up (view, view->data_area.height);
487 break;
488 case CK_PageDown:
489 mcview_move_down (view, view->data_area.height);
490 break;
491 case CK_Top:
492 mcview_moveto_top (view);
493 break;
494 case CK_Bottom:
495 mcview_moveto_bottom (view);
496 break;
497 case CK_Shell:
498 view_other_cmd ();
499 break;
500 case CK_BookmarkGoto:
501 view->marks[view->marker] = view->dpy_start;
502 break;
503 case CK_Bookmark:
504 view->dpy_start = view->marks[view->marker];
505 view->dirty++;
506 break;
507 #ifdef HAVE_CHARSET
508 case CK_SelectCodepage:
509 mcview_select_encoding (view);
510 view->dirty++;
511 break;
512 #endif
513 case CK_FileNext:
514 case CK_FilePrev:
515 /* Does not work in panel mode */
516 if (!mcview_is_in_panel (view))
517 mcview_load_next_prev (view, command == CK_FileNext ? 1 : -1);
518 break;
519 case CK_Quit:
520 if (!mcview_is_in_panel (view))
521 dlg_stop (WIDGET (view)->owner);
522 break;
523 case CK_Cancel:
524 /* don't close viewer due to SIGINT */
525 break;
526 default:
527 res = MSG_NOT_HANDLED;
529 return res;
532 /* --------------------------------------------------------------------------------------------- */
533 /** Both views */
534 static cb_ret_t
535 mcview_handle_key (mcview_t * view, int key)
537 unsigned long command;
539 #ifdef HAVE_CHARSET
540 key = convert_from_input_c (key);
541 #endif
543 if (view->hex_mode)
545 if (view->hexedit_mode && (mcview_handle_editkey (view, key) == MSG_HANDLED))
546 return MSG_HANDLED;
548 command = keybind_lookup_keymap_command (viewer_hex_map, key);
549 if ((command != CK_IgnoreKey) && (mcview_execute_cmd (view, command) == MSG_HANDLED))
550 return MSG_HANDLED;
553 command = keybind_lookup_keymap_command (viewer_map, key);
554 if ((command != CK_IgnoreKey) && (mcview_execute_cmd (view, command) == MSG_HANDLED))
555 return MSG_HANDLED;
557 #ifdef MC_ENABLE_DEBUGGING_CODE
558 if (c == 't')
559 { /* mnemonic: "test" */
560 mcview_ccache_dump (view);
561 return MSG_HANDLED;
563 #endif
564 if (key >= '0' && key <= '9')
565 view->marker = key - '0';
567 /* Key not used */
568 return MSG_NOT_HANDLED;
572 /* --------------------------------------------------------------------------------------------- */
574 static inline void
575 mcview_adjust_size (WDialog * h)
577 mcview_t *view;
578 WButtonBar *b;
580 /* Look up the viewer and the buttonbar, we assume only two widgets here */
581 view = (mcview_t *) find_widget_type (h, mcview_callback);
582 b = find_buttonbar (h);
584 widget_set_size (WIDGET (view), 0, 0, LINES - 1, COLS);
585 widget_set_size (WIDGET (b), LINES - 1, 0, 1, COLS);
587 mcview_compute_areas (view);
588 mcview_update_bytes_per_line (view);
592 /* --------------------------------------------------------------------------------------------- */
593 /*** public functions ****************************************************************************/
594 /* --------------------------------------------------------------------------------------------- */
596 cb_ret_t
597 mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
599 mcview_t *view = (mcview_t *) w;
600 cb_ret_t i;
602 mcview_compute_areas (view);
603 mcview_update_bytes_per_line (view);
605 switch (msg)
607 case MSG_INIT:
608 if (mcview_is_in_panel (view))
609 add_hook (&select_file_hook, mcview_hook, view);
610 else
611 view->dpy_bbar_dirty = TRUE;
612 return MSG_HANDLED;
614 case MSG_DRAW:
615 mcview_display (view);
616 return MSG_HANDLED;
618 case MSG_CURSOR:
619 if (view->hex_mode)
620 mcview_place_cursor (view);
621 return MSG_HANDLED;
623 case MSG_KEY:
624 i = mcview_handle_key (view, parm);
625 mcview_update (view);
626 return i;
628 case MSG_ACTION:
629 i = mcview_execute_cmd (view, parm);
630 mcview_update (view);
631 return i;
633 case MSG_FOCUS:
634 view->dpy_bbar_dirty = TRUE;
635 mcview_update (view);
636 return MSG_HANDLED;
638 case MSG_DESTROY:
639 if (mcview_is_in_panel (view))
641 delete_hook (&select_file_hook, mcview_hook);
643 if (mc_global.midnight_shutdown)
644 mcview_ok_to_quit (view);
646 mcview_done (view);
647 mcview_remove_ext_script (view);
648 return MSG_HANDLED;
650 default:
651 return widget_default_callback (w, sender, msg, parm, data);
655 /* --------------------------------------------------------------------------------------------- */
657 cb_ret_t
658 mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
660 WDialog *h = DIALOG (w);
661 mcview_t *view;
663 switch (msg)
665 case MSG_RESIZE:
666 mcview_adjust_size (h);
667 return MSG_HANDLED;
669 case MSG_ACTION:
670 /* shortcut */
671 if (sender == NULL)
672 return mcview_execute_cmd (NULL, parm);
673 /* message from buttonbar */
674 if (sender == WIDGET (find_buttonbar (h)))
676 if (data != NULL)
677 return send_message (data, NULL, MSG_ACTION, parm, NULL);
679 view = (mcview_t *) find_widget_type (h, mcview_callback);
680 return mcview_execute_cmd (view, parm);
682 return MSG_NOT_HANDLED;
684 case MSG_VALIDATE:
685 view = (mcview_t *) find_widget_type (h, mcview_callback);
686 h->state = DLG_ACTIVE; /* don't stop the dialog before final decision */
687 if (mcview_ok_to_quit (view))
688 h->state = DLG_CLOSED;
689 else
690 mcview_update (view);
691 return MSG_HANDLED;
693 default:
694 return dlg_default_callback (w, sender, msg, parm, data);
698 /* --------------------------------------------------------------------------------------------- */