Ticket #3897: mcedit: broken extended key mode.
[midnight-commander.git] / src / editor / editwidget.c
bloba6b02b16fc5b6bd70c06dff93ec5021d42edae19
1 /*
2 Editor initialisation and callback handler.
4 Copyright (C) 1996-2018
5 Free Software Foundation, Inc.
7 Written by:
8 Paul Sheer, 1996, 1997
9 Andrew Borodin <aborodin@vmail.ru> 2012, 2013
11 This file is part of the Midnight Commander.
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /** \file
28 * \brief Source: editor initialisation and callback handler
29 * \author Paul Sheer
30 * \date 1996, 1997
33 #include <config.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/types.h>
40 #include <unistd.h>
42 #include "lib/global.h"
44 #include "lib/tty/tty.h" /* LINES, COLS */
45 #include "lib/tty/key.h" /* is_idle() */
46 #include "lib/tty/color.h" /* tty_setcolor() */
47 #include "lib/skin.h"
48 #include "lib/fileloc.h" /* EDIT_DIR */
49 #include "lib/strutil.h" /* str_term_trim() */
50 #include "lib/util.h" /* mc_build_filename() */
51 #include "lib/widget.h"
52 #include "lib/mcconfig.h"
53 #include "lib/event.h" /* mc_event_raise() */
54 #ifdef HAVE_CHARSET
55 #include "lib/charsets.h"
56 #endif
58 #include "src/keybind-defaults.h" /* keybind_lookup_keymap_command() */
59 #include "src/setup.h" /* home_dir */
60 #include "src/filemanager/cmd.h" /* view_other_cmd(), save_setup_cmd() */
61 #include "src/learn.h" /* learn_keys() */
62 #include "src/args.h" /* mcedit_arg_t */
64 #include "edit-impl.h"
65 #include "editwidget.h"
66 #ifdef HAVE_ASPELL
67 #include "spell.h"
68 #endif
70 /*** global variables ****************************************************************************/
72 char *edit_window_state_char = NULL;
73 char *edit_window_close_char = NULL;
75 /*** file scope macro definitions ****************************************************************/
77 #define WINDOW_MIN_LINES (2 + 2)
78 #define WINDOW_MIN_COLS (2 + LINE_STATE_WIDTH + 2)
80 /*** file scope type declarations ****************************************************************/
82 /*** file scope variables ************************************************************************/
83 static unsigned int edit_dlg_init_refcounter = 0;
85 /*** file scope functions ************************************************************************/
87 static cb_ret_t edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
88 void *data);
90 /* --------------------------------------------------------------------------------------------- */
91 /**
92 * Init the 'edit' subsystem
95 static void
96 edit_dlg_init (void)
98 if (edit_dlg_init_refcounter == 0)
100 edit_window_state_char = mc_skin_get ("widget-editor", "window-state-char", "*");
101 edit_window_close_char = mc_skin_get ("widget-editor", "window-close-char", "X");
103 #ifdef HAVE_ASPELL
104 aspell_init ();
105 #endif
108 edit_dlg_init_refcounter++;
111 /* --------------------------------------------------------------------------------------------- */
113 * Deinit the 'edit' subsystem
116 static void
117 edit_dlg_deinit (void)
119 if (edit_dlg_init_refcounter != 0)
120 edit_dlg_init_refcounter--;
122 if (edit_dlg_init_refcounter == 0)
124 g_free (edit_window_state_char);
125 g_free (edit_window_close_char);
127 #ifdef HAVE_ASPELL
128 aspell_clean ();
129 #endif
133 /* --------------------------------------------------------------------------------------------- */
135 * Show info about editor
138 static void
139 edit_about (void)
141 quick_widget_t quick_widgets[] = {
142 /* *INDENT-OFF* */
143 QUICK_LABEL ("MCEdit " VERSION, NULL),
144 QUICK_SEPARATOR (TRUE),
145 QUICK_LABEL (N_("A user friendly text editor\n"
146 "written for the Midnight Commander."), NULL),
147 QUICK_SEPARATOR (FALSE),
148 QUICK_LABEL (N_("Copyright (C) 1996-2018 the Free Software Foundation"), NULL),
149 QUICK_START_BUTTONS (TRUE, TRUE),
150 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
151 QUICK_END
152 /* *INDENT-ON* */
155 quick_dialog_t qdlg = {
156 -1, -1, 40,
157 N_("About"), "[Internal File Editor]",
158 quick_widgets, NULL, NULL
161 quick_widgets[0].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
162 quick_widgets[2].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
163 quick_widgets[4].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
165 (void) quick_dialog (&qdlg);
168 /* --------------------------------------------------------------------------------------------- */
170 * Show a help window
173 static void
174 edit_help (void)
176 ev_help_t event_data = { NULL, "[Internal File Editor]" };
177 mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
180 /* --------------------------------------------------------------------------------------------- */
182 * Callback for the iteration of objects in the 'editors' array.
183 * Resize the editor window.
185 * @param data probably WEdit object
186 * @param user_data unused
189 static void
190 edit_dialog_resize_cb (void *data, void *user_data)
192 Widget *w = WIDGET (data);
194 (void) user_data;
196 if (edit_widget_is_editor (w) && ((WEdit *) w)->fullscreen)
198 Widget *wh = WIDGET (w->owner);
200 w->lines = wh->lines - 2;
201 w->cols = wh->cols;
205 /* --------------------------------------------------------------------------------------------- */
207 * Restore saved window size.
209 * @param edit editor object
212 static void
213 edit_restore_size (WEdit * edit)
215 Widget *w = WIDGET (edit);
217 edit->drag_state = MCEDIT_DRAG_NONE;
218 w->mouse.forced_capture = FALSE;
219 widget_set_size (w, edit->y_prev, edit->x_prev, edit->lines_prev, edit->cols_prev);
220 dlg_redraw (w->owner);
223 /* --------------------------------------------------------------------------------------------- */
225 * Move window by one row or column in any direction.
227 * @param edit editor object
228 * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
231 static void
232 edit_window_move (WEdit * edit, long command)
234 Widget *w = WIDGET (edit);
235 Widget *wh = WIDGET (w->owner);
237 switch (command)
239 case CK_Up:
240 if (w->y > wh->y + 1) /* menubar */
241 w->y--;
242 break;
243 case CK_Down:
244 if (w->y < wh->y + wh->lines - 2) /* buttonbar */
245 w->y++;
246 break;
247 case CK_Left:
248 if (w->x + w->cols > wh->x)
249 w->x--;
250 break;
251 case CK_Right:
252 if (w->x < wh->x + wh->cols)
253 w->x++;
254 break;
255 default:
256 return;
259 edit->force |= REDRAW_PAGE;
260 dlg_redraw (w->owner);
263 /* --------------------------------------------------------------------------------------------- */
265 * Resize window by one row or column in any direction.
267 * @param edit editor object
268 * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
271 static void
272 edit_window_resize (WEdit * edit, long command)
274 Widget *w = WIDGET (edit);
275 Widget *wh = WIDGET (w->owner);
277 switch (command)
279 case CK_Up:
280 if (w->lines > WINDOW_MIN_LINES)
281 w->lines--;
282 break;
283 case CK_Down:
284 if (w->y + w->lines < wh->y + wh->lines - 1) /* buttonbar */
285 w->lines++;
286 break;
287 case CK_Left:
288 if (w->cols > WINDOW_MIN_COLS)
289 w->cols--;
290 break;
291 case CK_Right:
292 if (w->x + w->cols < wh->x + wh->cols)
293 w->cols++;
294 break;
295 default:
296 return;
299 edit->force |= REDRAW_COMPLETELY;
300 dlg_redraw (w->owner);
303 /* --------------------------------------------------------------------------------------------- */
305 * Get hotkey by number.
307 * @param n number
308 * @return hotkey
311 static unsigned char
312 get_hotkey (int n)
314 return (n <= 9) ? '0' + n : 'a' + n - 10;
317 /* --------------------------------------------------------------------------------------------- */
319 static void
320 edit_window_list (const WDialog * h)
322 const size_t dlg_num = g_list_length (h->widgets) - 2; /* 2 = skip menu and buttonbar */
323 int lines, cols;
324 Listbox *listbox;
325 GList *w;
326 WEdit *selected;
327 int i = 0;
329 lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
330 cols = COLS * 2 / 3;
332 listbox = create_listbox_window (lines, cols, _("Open files"), "[Open files]");
334 for (w = h->widgets; w != NULL; w = g_list_next (w))
335 if (edit_widget_is_editor (CONST_WIDGET (w->data)))
337 WEdit *e = (WEdit *) w->data;
338 char *fname;
340 if (e->filename_vpath == NULL)
341 fname = g_strdup_printf ("%c [%s]", e->modified ? '*' : ' ', _("NoName"));
342 else
343 fname =
344 g_strdup_printf ("%c%s", e->modified ? '*' : ' ',
345 vfs_path_as_str (e->filename_vpath));
347 listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++),
348 str_term_trim (fname, WIDGET (listbox->list)->cols - 2), e, FALSE);
349 g_free (fname);
352 selected = run_listbox_with_data (listbox, h->current->data);
353 if (selected != NULL)
354 widget_select (WIDGET (selected));
357 /* --------------------------------------------------------------------------------------------- */
359 static char *
360 edit_get_shortcut (long command)
362 const char *ext_map;
363 const char *shortcut = NULL;
365 shortcut = keybind_lookup_keymap_shortcut (editor_map, command);
366 if (shortcut != NULL)
367 return g_strdup (shortcut);
369 ext_map = keybind_lookup_keymap_shortcut (editor_map, CK_ExtendedKeyMap);
370 if (ext_map != NULL)
371 shortcut = keybind_lookup_keymap_shortcut (editor_x_map, command);
372 if (shortcut != NULL)
373 return g_strdup_printf ("%s %s", ext_map, shortcut);
375 return NULL;
378 /* --------------------------------------------------------------------------------------------- */
380 static char *
381 edit_get_title (const WDialog * h, size_t len)
383 const WEdit *edit = find_editor (h);
384 const char *modified = edit->modified ? "(*) " : " ";
385 const char *file_label;
386 char *filename;
388 len -= 4;
390 if (edit->filename_vpath == NULL)
391 filename = g_strdup (_("[NoName]"));
392 else
393 filename = g_strdup (vfs_path_as_str (edit->filename_vpath));
395 file_label = str_term_trim (filename, len - str_term_width1 (_("Edit: ")));
396 g_free (filename);
398 return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL);
401 /* --------------------------------------------------------------------------------------------- */
403 static cb_ret_t
404 edit_dialog_command_execute (WDialog * h, long command)
406 Widget *wh = WIDGET (h);
407 cb_ret_t ret = MSG_HANDLED;
409 switch (command)
411 case CK_EditNew:
412 edit_add_window (h, wh->y + 1, wh->x, wh->lines - 2, wh->cols, NULL, 0);
413 break;
414 case CK_EditFile:
415 edit_load_cmd (h);
416 break;
417 case CK_EditSyntaxFile:
418 edit_load_syntax_file (h);
419 break;
420 case CK_EditUserMenu:
421 edit_load_menu_file (h);
422 break;
423 case CK_Close:
424 /* if there are no opened files anymore, close MC editor */
425 if (edit_widget_is_editor (CONST_WIDGET (h->current->data)) &&
426 edit_close_cmd ((WEdit *) h->current->data) && find_editor (h) == NULL)
427 dlg_stop (h);
428 break;
429 case CK_Help:
430 edit_help ();
431 /* edit->force |= REDRAW_COMPLETELY; */
432 break;
433 case CK_Menu:
434 edit_menu_cmd (h);
435 break;
436 case CK_Quit:
437 case CK_Cancel:
438 /* don't close editor due to SIGINT, but stop move/resize window */
440 Widget *w = WIDGET (h->current->data);
442 if (edit_widget_is_editor (w) && ((WEdit *) w)->drag_state != MCEDIT_DRAG_NONE)
443 edit_restore_size ((WEdit *) w);
444 else if (command == CK_Quit)
445 dlg_stop (h);
447 break;
448 case CK_About:
449 edit_about ();
450 break;
451 case CK_SyntaxOnOff:
452 edit_syntax_onoff_cmd (h);
453 break;
454 case CK_ShowTabTws:
455 edit_show_tabs_tws_cmd (h);
456 break;
457 case CK_ShowMargin:
458 edit_show_margin_cmd (h);
459 break;
460 case CK_ShowNumbers:
461 edit_show_numbers_cmd (h);
462 break;
463 case CK_Refresh:
464 edit_refresh_cmd ();
465 break;
466 case CK_Shell:
467 view_other_cmd ();
468 break;
469 case CK_LearnKeys:
470 learn_keys ();
471 break;
472 case CK_WindowMove:
473 case CK_WindowResize:
474 if (edit_widget_is_editor (CONST_WIDGET (h->current->data)))
475 edit_handle_move_resize ((WEdit *) h->current->data, command);
476 break;
477 case CK_WindowList:
478 edit_window_list (h);
479 break;
480 case CK_WindowNext:
481 dlg_select_next_widget (h);
482 break;
483 case CK_WindowPrev:
484 dlg_select_prev_widget (h);
485 break;
486 case CK_Options:
487 edit_options_dialog (h);
488 break;
489 case CK_OptionsSaveMode:
490 edit_save_mode_cmd ();
491 break;
492 case CK_SaveSetup:
493 save_setup_cmd ();
494 break;
495 default:
496 ret = MSG_NOT_HANDLED;
497 break;
500 return ret;
503 /* --------------------------------------------------------------------------------------------- */
505 * Translate the keycode into either 'command' or 'char_for_insertion'.
506 * 'command' is one of the editor commands from cmddef.h.
509 static gboolean
510 edit_translate_key (WEdit * edit, long x_key, int *cmd, int *ch)
512 long command = CK_InsertChar;
513 int char_for_insertion = -1;
515 /* an ordinary insertable character */
516 if (!edit->extmod && x_key < 256)
518 #ifndef HAVE_CHARSET
519 if (is_printable (x_key))
521 char_for_insertion = x_key;
522 goto fin;
524 #else
525 int c;
527 if (edit->charpoint >= 4)
529 edit->charpoint = 0;
530 edit->charbuf[edit->charpoint] = '\0';
532 if (edit->charpoint < 4)
534 edit->charbuf[edit->charpoint++] = x_key;
535 edit->charbuf[edit->charpoint] = '\0';
538 /* input from 8-bit locale */
539 if (!mc_global.utf8_display)
541 /* source in 8-bit codeset */
542 c = convert_from_input_c (x_key);
544 if (is_printable (c))
546 if (!edit->utf8)
547 char_for_insertion = c;
548 else
549 char_for_insertion = convert_from_8bit_to_utf_c2 ((char) x_key);
550 goto fin;
553 else
555 /* UTF-8 locale */
556 int res;
558 res = str_is_valid_char (edit->charbuf, edit->charpoint);
559 if (res < 0 && res != -2)
561 edit->charpoint = 0; /* broken multibyte char, skip */
562 goto fin;
565 if (edit->utf8)
567 /* source in UTF-8 codeset */
568 if (res < 0)
570 char_for_insertion = x_key;
571 goto fin;
574 edit->charbuf[edit->charpoint] = '\0';
575 edit->charpoint = 0;
576 if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
578 char_for_insertion = x_key;
579 goto fin;
582 else
584 /* 8-bit source */
585 if (res < 0)
587 /* not finised multibyte input (in meddle multibyte utf-8 char) */
588 goto fin;
591 if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
593 c = convert_from_utf_to_current (edit->charbuf);
594 edit->charbuf[0] = '\0';
595 edit->charpoint = 0;
596 char_for_insertion = c;
597 goto fin;
600 /* unprinteble utf input, skip it */
601 edit->charbuf[0] = '\0';
602 edit->charpoint = 0;
605 #endif /* HAVE_CHARSET */
608 /* Commands specific to the key emulation */
609 if (edit->extmod)
611 edit->extmod = FALSE;
612 command = keybind_lookup_keymap_command (editor_x_map, x_key);
614 else
615 command = keybind_lookup_keymap_command (editor_map, x_key);
617 if (command == CK_IgnoreKey)
618 command = CK_InsertChar;
620 fin:
621 *cmd = (int) command; /* FIXME */
622 *ch = char_for_insertion;
624 return !(command == CK_InsertChar && char_for_insertion == -1);
628 /* --------------------------------------------------------------------------------------------- */
630 static inline void
631 edit_quit (WDialog * h)
633 GList *l;
634 WEdit *e = NULL;
636 /* don't stop the dialog before final decision */
637 widget_set_state (WIDGET (h), WST_ACTIVE, TRUE);
639 for (l = h->widgets; l != NULL; l = g_list_next (l))
640 if (edit_widget_is_editor (CONST_WIDGET (l->data)))
642 e = (WEdit *) l->data;
644 if (e->drag_state != MCEDIT_DRAG_NONE)
646 edit_restore_size (e);
647 return;
650 if (e->modified)
652 widget_select (WIDGET (e));
654 if (!edit_ok_to_exit (e))
655 return;
659 /* no editors in dialog at all or no any file required to be saved */
660 if (e == NULL || l == NULL)
661 dlg_stop (h);
664 /* --------------------------------------------------------------------------------------------- */
666 static inline void
667 edit_set_buttonbar (WEdit * edit, WButtonBar * bb)
669 buttonbar_set_label (bb, 1, Q_ ("ButtonBar|Help"), editor_map, NULL);
670 buttonbar_set_label (bb, 2, Q_ ("ButtonBar|Save"), editor_map, WIDGET (edit));
671 buttonbar_set_label (bb, 3, Q_ ("ButtonBar|Mark"), editor_map, WIDGET (edit));
672 buttonbar_set_label (bb, 4, Q_ ("ButtonBar|Replac"), editor_map, WIDGET (edit));
673 buttonbar_set_label (bb, 5, Q_ ("ButtonBar|Copy"), editor_map, WIDGET (edit));
674 buttonbar_set_label (bb, 6, Q_ ("ButtonBar|Move"), editor_map, WIDGET (edit));
675 buttonbar_set_label (bb, 7, Q_ ("ButtonBar|Search"), editor_map, WIDGET (edit));
676 buttonbar_set_label (bb, 8, Q_ ("ButtonBar|Delete"), editor_map, WIDGET (edit));
677 buttonbar_set_label (bb, 9, Q_ ("ButtonBar|PullDn"), editor_map, NULL);
678 buttonbar_set_label (bb, 10, Q_ ("ButtonBar|Quit"), editor_map, NULL);
681 /* --------------------------------------------------------------------------------------------- */
683 static void
684 edit_total_update (WEdit * edit)
686 edit_find_bracket (edit);
687 edit->force |= REDRAW_COMPLETELY;
688 edit_update_curs_row (edit);
689 edit_update_screen (edit);
692 /* --------------------------------------------------------------------------------------------- */
694 static gboolean
695 edit_update_cursor (WEdit * edit, const mouse_event_t * event)
697 int x, y;
698 gboolean done;
700 x = event->x - (edit->fullscreen ? 0 : 1);
701 y = event->y - (edit->fullscreen ? 0 : 1);
703 if (edit->mark2 != -1 && event->msg == MSG_MOUSE_UP)
704 return TRUE; /* don't do anything */
706 if (event->msg == MSG_MOUSE_DOWN || event->msg == MSG_MOUSE_UP)
707 edit_push_key_press (edit);
709 if (!option_cursor_beyond_eol)
710 edit->prev_col = x - edit->start_col - option_line_state_width;
711 else
713 long line_len;
715 line_len =
716 edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
717 edit_buffer_get_current_eol (&edit->buffer));
719 if (x > line_len - 1)
721 edit->over_col = x - line_len - edit->start_col - option_line_state_width;
722 edit->prev_col = line_len;
724 else
726 edit->over_col = 0;
727 edit->prev_col = x - option_line_state_width - edit->start_col;
731 if (y > edit->curs_row)
732 edit_move_down (edit, y - edit->curs_row, FALSE);
733 else if (y < edit->curs_row)
734 edit_move_up (edit, edit->curs_row - y, FALSE);
735 else
736 edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
738 if (event->msg == MSG_MOUSE_CLICK)
740 edit_mark_cmd (edit, TRUE); /* reset */
741 edit->highlight = 0;
744 done = (event->msg != MSG_MOUSE_DRAG);
745 if (done)
746 edit_mark_cmd (edit, FALSE);
748 return done;
751 /* --------------------------------------------------------------------------------------------- */
752 /** Callback for the edit dialog */
754 static cb_ret_t
755 edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
757 WMenuBar *menubar;
758 WButtonBar *buttonbar;
759 WDialog *h = DIALOG (w);
761 switch (msg)
763 case MSG_INIT:
764 edit_dlg_init ();
765 return MSG_HANDLED;
767 case MSG_DRAW:
768 /* don't use dlg_default_repaint() -- we don't need a frame */
769 tty_setcolor (EDITOR_BACKGROUND);
770 dlg_erase (h);
771 return MSG_HANDLED;
773 case MSG_RESIZE:
774 menubar = find_menubar (h);
775 buttonbar = find_buttonbar (h);
776 /* dlg_set_size() is surplus for this case */
777 w->lines = LINES;
778 w->cols = COLS;
779 widget_set_size (WIDGET (buttonbar), w->lines - 1, w->x, 1, w->cols);
780 widget_set_size (WIDGET (menubar), w->y, w->x, 1, w->cols);
781 menubar_arrange (menubar);
782 g_list_foreach (h->widgets, (GFunc) edit_dialog_resize_cb, NULL);
783 return MSG_HANDLED;
785 case MSG_ACTION:
787 /* Handle shortcuts, menu, and buttonbar. */
789 cb_ret_t result;
791 result = edit_dialog_command_execute (h, parm);
793 /* We forward any commands coming from the menu, and which haven't been
794 handled by the dialog, to the focused WEdit window. */
795 if (result == MSG_NOT_HANDLED && sender == WIDGET (find_menubar (h)))
796 result = send_message (h->current->data, NULL, MSG_ACTION, parm, NULL);
798 return result;
801 case MSG_KEY:
803 Widget *we = WIDGET (h->current->data);
804 cb_ret_t ret = MSG_NOT_HANDLED;
806 if (edit_widget_is_editor (we))
808 WEdit *e = (WEdit *) we;
809 long command;
811 if (!e->extmod)
812 command = keybind_lookup_keymap_command (editor_map, parm);
813 else
814 command = keybind_lookup_keymap_command (editor_x_map, parm);
816 if (command == CK_IgnoreKey)
817 e->extmod = FALSE;
818 else
820 ret = edit_dialog_command_execute (h, command);
821 /* if command was not handled, keep the extended mode
822 for the further key processing */
823 if (ret == MSG_HANDLED)
824 e->extmod = FALSE;
829 * Due to the "end of bracket" escape the editor sees input with is_idle() == false
830 * (expects more characters) and hence doesn't yet refresh the screen, but then
831 * no further characters arrive (there's only an "end of bracket" which is swallowed
832 * by tty_get_event()), so you end up with a screen that's not refreshed after pasting.
833 * So let's trigger an IDLE signal.
835 if (!is_idle ())
836 widget_idle (w, TRUE);
837 return ret;
840 /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */
841 case MSG_UNHANDLED_KEY:
842 return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
844 case MSG_VALIDATE:
845 edit_quit (h);
846 return MSG_HANDLED;
848 case MSG_END:
849 edit_dlg_deinit ();
850 return MSG_HANDLED;
852 case MSG_IDLE:
853 widget_idle (w, FALSE);
854 return send_message (h->current->data, NULL, MSG_IDLE, 0, NULL);
856 default:
857 return dlg_default_callback (w, sender, msg, parm, data);
861 /* --------------------------------------------------------------------------------------------- */
864 * Handle mouse events of editor screen.
866 * @param w Widget object (the editor)
867 * @param msg mouse event message
868 * @param event mouse event data
870 static void
871 edit_dialog_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
873 gboolean unhandled = TRUE;
875 if (msg == MSG_MOUSE_DOWN && event->y == 0)
877 WDialog *h = DIALOG (w);
878 WMenuBar *b;
880 b = find_menubar (h);
882 if (!widget_get_state (WIDGET (b), WST_FOCUSED))
884 /* menubar */
886 GList *l;
887 GList *top = NULL;
888 int x;
890 /* Try find top fullscreen window */
891 for (l = h->widgets; l != NULL; l = g_list_next (l))
892 if (edit_widget_is_editor (CONST_WIDGET (l->data))
893 && ((WEdit *) l->data)->fullscreen)
894 top = l;
896 /* Handle fullscreen/close buttons in the top line */
897 x = w->cols - 6;
899 if (top != NULL && event->x >= x)
901 WEdit *e = (WEdit *) top->data;
903 if (top != h->current)
905 /* Window is not active. Activate it */
906 widget_select (WIDGET (e));
909 /* Handle buttons */
910 if (event->x - x <= 2)
911 edit_toggle_fullscreen (e);
912 else
913 send_message (h, NULL, MSG_ACTION, CK_Close, NULL);
915 unhandled = FALSE;
918 if (unhandled)
919 menubar_activate (b, drop_menus, -1);
923 /* Continue handling of unhandled event in window or menu */
924 event->result.abort = unhandled;
927 /* --------------------------------------------------------------------------------------------- */
929 static cb_ret_t
930 edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
932 WEdit *e = (WEdit *) w;
934 switch (msg)
936 case MSG_FOCUS:
937 edit_set_buttonbar (e, find_buttonbar (w->owner));
938 return MSG_HANDLED;
940 case MSG_DRAW:
941 e->force |= REDRAW_COMPLETELY;
942 edit_update_screen (e);
943 return MSG_HANDLED;
945 case MSG_KEY:
947 int cmd, ch;
948 cb_ret_t ret = MSG_NOT_HANDLED;
950 /* The user may override the access-keys for the menu bar. */
951 if (macro_index == -1 && edit_execute_macro (e, parm))
953 edit_update_screen (e);
954 ret = MSG_HANDLED;
956 else if (edit_translate_key (e, parm, &cmd, &ch))
958 edit_execute_key_command (e, cmd, ch);
959 edit_update_screen (e);
960 ret = MSG_HANDLED;
963 return ret;
966 case MSG_ACTION:
967 /* command from menubar or buttonbar */
968 edit_execute_key_command (e, parm, -1);
969 edit_update_screen (e);
970 return MSG_HANDLED;
972 case MSG_CURSOR:
974 int y, x;
976 y = (e->fullscreen ? 0 : 1) + EDIT_TEXT_VERTICAL_OFFSET + e->curs_row;
977 x = (e->fullscreen ? 0 : 1) + EDIT_TEXT_HORIZONTAL_OFFSET + option_line_state_width +
978 e->curs_col + e->start_col + e->over_col;
980 widget_move (w, y, x);
981 return MSG_HANDLED;
984 case MSG_IDLE:
985 edit_update_screen (e);
986 return MSG_HANDLED;
988 case MSG_DESTROY:
989 edit_clean (e);
990 return MSG_HANDLED;
992 default:
993 return widget_default_callback (w, sender, msg, parm, data);
997 /* --------------------------------------------------------------------------------------------- */
1000 * Handle move/resize mouse events.
1002 static void
1003 edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event)
1005 WEdit *edit = (WEdit *) (w);
1006 Widget *h = WIDGET (w->owner);
1007 int global_x, global_y;
1009 if (msg == MSG_MOUSE_UP)
1011 /* Exit move/resize mode. */
1012 edit_execute_cmd (edit, CK_Enter, -1);
1013 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
1014 return;
1017 if (msg != MSG_MOUSE_DRAG)
1019 * We ignore any other events. Specifically, MSG_MOUSE_DOWN.
1021 * When the move/resize is initiated by the menu, we let the user
1022 * stop it by clicking with the mouse. Which is why we don't want
1023 * a mouse down to affect the window.
1025 return;
1027 /* Convert point to global coordinates for easier calculations. */
1028 global_x = event->x + w->x;
1029 global_y = event->y + w->y;
1031 /* Clamp the point to the dialog's client area. */
1032 global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */
1033 global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */
1035 if (edit->drag_state == MCEDIT_DRAG_MOVE)
1037 w->y = global_y;
1038 w->x = global_x - edit->drag_state_start;
1040 else if (edit->drag_state == MCEDIT_DRAG_RESIZE)
1042 w->lines = MAX (WINDOW_MIN_LINES, global_y - w->y + 1);
1043 w->cols = MAX (WINDOW_MIN_COLS, global_x - w->x + 1);
1046 edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */
1048 /* We draw the whole dialog because dragging/resizing exposes area beneath. */
1049 dlg_redraw (w->owner);
1052 /* --------------------------------------------------------------------------------------------- */
1055 * Handle mouse events of editor window
1057 * @param w Widget object (the editor window)
1058 * @param msg mouse event message
1059 * @param event mouse event data
1061 static void
1062 edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
1064 WEdit *edit = (WEdit *) w;
1065 /* buttons' distance from right edge */
1066 int dx = edit->fullscreen ? 0 : 2;
1067 /* location of 'Close' and 'Toggle fullscreen' pictograms */
1068 int close_x, toggle_fullscreen_x;
1070 close_x = (w->cols - 1) - dx - 1;
1071 toggle_fullscreen_x = close_x - 3;
1073 if (edit->drag_state != MCEDIT_DRAG_NONE)
1075 /* window is being resized/moved */
1076 edit_mouse_handle_move_resize (w, msg, event);
1077 return;
1080 /* If it's the last line on the screen, we abort the event to make the
1081 * system channel it to the overlapping buttonbar instead. We have to do
1082 * this because a WEdit has the WOP_TOP_SELECT flag, which makes it above
1083 * the buttonbar in Z-order. */
1084 if (msg == MSG_MOUSE_DOWN && (event->y + w->y == LINES - 1))
1086 event->result.abort = TRUE;
1087 return;
1090 switch (msg)
1092 case MSG_MOUSE_DOWN:
1093 widget_select (w);
1094 edit_update_curs_row (edit);
1095 edit_update_curs_col (edit);
1097 if (!edit->fullscreen)
1099 if (event->y == 0)
1101 if (event->x >= close_x - 1 && event->x <= close_x + 1)
1102 ; /* do nothing (see MSG_MOUSE_CLICK) */
1103 else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
1104 ; /* do nothing (see MSG_MOUSE_CLICK) */
1105 else
1107 /* start window move */
1108 edit_execute_cmd (edit, CK_WindowMove, -1);
1109 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
1110 edit->drag_state_start = event->x;
1112 break;
1115 if (event->y == w->lines - 1 && event->x == w->cols - 1)
1117 /* bottom-right corner -- start window resize */
1118 edit_execute_cmd (edit, CK_WindowResize, -1);
1119 break;
1123 MC_FALLTHROUGH; /* to start/stop text selection */
1125 case MSG_MOUSE_UP:
1126 edit_update_cursor (edit, event);
1127 edit_total_update (edit);
1128 break;
1130 case MSG_MOUSE_CLICK:
1131 if (event->y == 0)
1133 if (event->x >= close_x - 1 && event->x <= close_x + 1)
1134 send_message (w->owner, NULL, MSG_ACTION, CK_Close, NULL);
1135 else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
1136 edit_toggle_fullscreen (edit);
1137 else if (!edit->fullscreen && event->count == GPM_DOUBLE)
1138 /* double click on top line (toggle fullscreen) */
1139 edit_toggle_fullscreen (edit);
1141 else if (event->count == GPM_DOUBLE)
1143 /* double click */
1144 edit_mark_current_word_cmd (edit);
1145 edit_total_update (edit);
1147 else if (event->count == GPM_TRIPLE)
1149 /* triple click: works in GPM only, not in xterm */
1150 edit_mark_current_line_cmd (edit);
1151 edit_total_update (edit);
1153 break;
1155 case MSG_MOUSE_DRAG:
1156 edit_update_cursor (edit, event);
1157 edit_total_update (edit);
1158 break;
1160 case MSG_MOUSE_SCROLL_UP:
1161 edit_move_up (edit, 2, TRUE);
1162 edit_total_update (edit);
1163 break;
1165 case MSG_MOUSE_SCROLL_DOWN:
1166 edit_move_down (edit, 2, TRUE);
1167 edit_total_update (edit);
1168 break;
1170 default:
1171 break;
1175 /* --------------------------------------------------------------------------------------------- */
1176 /*** public functions ****************************************************************************/
1177 /* --------------------------------------------------------------------------------------------- */
1179 * Edit one file.
1181 * @param file_vpath file object
1182 * @param line line number
1183 * @return TRUE if no errors was occurred, FALSE otherwise
1186 gboolean
1187 edit_file (const vfs_path_t * file_vpath, long line)
1189 mcedit_arg_t arg = { (vfs_path_t *) file_vpath, line };
1190 GList *files;
1191 gboolean ok;
1193 files = g_list_prepend (NULL, &arg);
1194 ok = edit_files (files);
1195 g_list_free (files);
1197 return ok;
1200 /* --------------------------------------------------------------------------------------------- */
1202 gboolean
1203 edit_files (const GList * files)
1205 static gboolean made_directory = FALSE;
1206 WDialog *edit_dlg;
1207 WMenuBar *menubar;
1208 const GList *file;
1209 gboolean ok = FALSE;
1211 if (!made_directory)
1213 char *dir;
1215 dir = mc_build_filename (mc_config_get_cache_path (), EDIT_DIR, (char *) NULL);
1216 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1217 g_free (dir);
1219 dir = mc_build_filename (mc_config_get_path (), EDIT_DIR, (char *) NULL);
1220 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1221 g_free (dir);
1223 dir = mc_build_filename (mc_config_get_data_path (), EDIT_DIR, (char *) NULL);
1224 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1225 g_free (dir);
1228 /* Create a new dialog and add it widgets to it */
1229 edit_dlg =
1230 dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, edit_dialog_callback,
1231 edit_dialog_mouse_callback, "[Internal File Editor]", NULL);
1232 widget_want_tab (WIDGET (edit_dlg), TRUE);
1234 edit_dlg->get_shortcut = edit_get_shortcut;
1235 edit_dlg->get_title = edit_get_title;
1237 menubar = menubar_new (0, 0, COLS, NULL, TRUE);
1238 add_widget (edit_dlg, menubar);
1239 edit_init_menu (menubar);
1241 add_widget (edit_dlg, buttonbar_new (TRUE));
1243 for (file = files; file != NULL; file = g_list_next (file))
1245 Widget *w = WIDGET (edit_dlg);
1246 mcedit_arg_t *f = (mcedit_arg_t *) file->data;
1247 gboolean f_ok;
1249 f_ok = edit_add_window (edit_dlg, w->y + 1, w->x, w->lines - 2, w->cols, f->file_vpath,
1250 f->line_number);
1251 /* at least one file has been opened succefully */
1252 ok = ok || f_ok;
1255 if (ok)
1256 dlg_run (edit_dlg);
1258 if (!ok || widget_get_state (WIDGET (edit_dlg), WST_CLOSED))
1259 dlg_destroy (edit_dlg);
1261 return ok;
1264 /* --------------------------------------------------------------------------------------------- */
1266 const char *
1267 edit_get_file_name (const WEdit * edit)
1269 return vfs_path_as_str (edit->filename_vpath);
1272 /* --------------------------------------------------------------------------------------------- */
1274 WEdit *
1275 find_editor (const WDialog * h)
1277 if (edit_widget_is_editor (CONST_WIDGET (h->current->data)))
1278 return (WEdit *) h->current->data;
1279 return (WEdit *) find_widget_type (h, edit_callback);
1282 /* --------------------------------------------------------------------------------------------- */
1284 * Check if widget is an WEdit class.
1286 * @param w probably editor object
1287 * @return TRUE if widget is an WEdit class, FALSE otherwise
1290 gboolean
1291 edit_widget_is_editor (const Widget * w)
1293 return (w != NULL && w->callback == edit_callback);
1296 /* --------------------------------------------------------------------------------------------- */
1298 void
1299 edit_update_screen (WEdit * e)
1301 WDialog *h = WIDGET (e)->owner;
1303 edit_scroll_screen_over_cursor (e);
1304 edit_update_curs_col (e);
1305 edit_status (e, widget_get_state (WIDGET (e), WST_FOCUSED));
1307 /* pop all events for this window for internal handling */
1308 if (!is_idle ())
1309 e->force |= REDRAW_PAGE;
1310 else
1312 if ((e->force & REDRAW_COMPLETELY) != 0)
1313 e->force |= REDRAW_PAGE;
1314 edit_render_keypress (e);
1317 widget_redraw (WIDGET (find_buttonbar (h)));
1320 /* --------------------------------------------------------------------------------------------- */
1322 * Save current window size.
1324 * @param edit editor object
1327 void
1328 edit_save_size (WEdit * edit)
1330 Widget *w = WIDGET (edit);
1332 edit->y_prev = w->y;
1333 edit->x_prev = w->x;
1334 edit->lines_prev = w->lines;
1335 edit->cols_prev = w->cols;
1338 /* --------------------------------------------------------------------------------------------- */
1340 * Create new editor window and insert it into editor screen.
1342 * @param h editor dialog (screen)
1343 * @param y y coordinate
1344 * @param x x coordinate
1345 * @param lines window height
1346 * @param cols window width
1347 * @param f file object
1348 * @param fline line number in file
1349 * @return TRUE if new window was successfully created and inserted into editor screen,
1350 * FALSE otherwise
1353 gboolean
1354 edit_add_window (WDialog * h, int y, int x, int lines, int cols, const vfs_path_t * f, long fline)
1356 WEdit *edit;
1357 Widget *w;
1359 edit = edit_init (NULL, y, x, lines, cols, f, fline);
1360 if (edit == NULL)
1361 return FALSE;
1363 w = WIDGET (edit);
1364 w->callback = edit_callback;
1365 w->mouse_callback = edit_mouse_callback;
1367 add_widget (h, w);
1368 edit_set_buttonbar (edit, find_buttonbar (h));
1369 dlg_redraw (h);
1371 return TRUE;
1374 /* --------------------------------------------------------------------------------------------- */
1376 * Handle move/resize events.
1378 * @param edit editor object
1379 * @param command action id
1380 * @return TRUE if the action was handled, FALSE otherwise
1383 gboolean
1384 edit_handle_move_resize (WEdit * edit, long command)
1386 Widget *w = WIDGET (edit);
1387 gboolean ret = FALSE;
1389 if (edit->fullscreen)
1391 edit->drag_state = MCEDIT_DRAG_NONE;
1392 w->mouse.forced_capture = FALSE;
1393 return ret;
1396 switch (edit->drag_state)
1398 case MCEDIT_DRAG_NONE:
1399 /* possible start move/resize */
1400 switch (command)
1402 case CK_WindowMove:
1403 edit->drag_state = MCEDIT_DRAG_MOVE;
1404 edit_save_size (edit);
1405 edit_status (edit, TRUE); /* redraw frame and status */
1407 * If a user initiates a move by the menu, not by the mouse, we
1408 * make a subsequent mouse drag pull the frame from its middle.
1409 * (We can instead choose '0' to pull it from the corner.)
1411 edit->drag_state_start = w->cols / 2;
1412 ret = TRUE;
1413 break;
1414 case CK_WindowResize:
1415 edit->drag_state = MCEDIT_DRAG_RESIZE;
1416 edit_save_size (edit);
1417 edit_status (edit, TRUE); /* redraw frame and status */
1418 ret = TRUE;
1419 break;
1420 default:
1421 break;
1423 break;
1425 case MCEDIT_DRAG_MOVE:
1426 switch (command)
1428 case CK_WindowResize:
1429 edit->drag_state = MCEDIT_DRAG_RESIZE;
1430 ret = TRUE;
1431 break;
1432 case CK_Up:
1433 case CK_Down:
1434 case CK_Left:
1435 case CK_Right:
1436 edit_window_move (edit, command);
1437 ret = TRUE;
1438 break;
1439 case CK_Enter:
1440 case CK_WindowMove:
1441 edit->drag_state = MCEDIT_DRAG_NONE;
1442 edit_status (edit, TRUE); /* redraw frame and status */
1443 MC_FALLTHROUGH;
1444 default:
1445 ret = TRUE;
1446 break;
1448 break;
1450 case MCEDIT_DRAG_RESIZE:
1451 switch (command)
1453 case CK_WindowMove:
1454 edit->drag_state = MCEDIT_DRAG_MOVE;
1455 ret = TRUE;
1456 break;
1457 case CK_Up:
1458 case CK_Down:
1459 case CK_Left:
1460 case CK_Right:
1461 edit_window_resize (edit, command);
1462 ret = TRUE;
1463 break;
1464 case CK_Enter:
1465 case CK_WindowResize:
1466 edit->drag_state = MCEDIT_DRAG_NONE;
1467 edit_status (edit, TRUE); /* redraw frame and status */
1468 MC_FALLTHROUGH;
1469 default:
1470 ret = TRUE;
1471 break;
1473 break;
1475 default:
1476 break;
1480 * - We let the user stop a resize/move operation by clicking with the
1481 * mouse anywhere. ("clicking" = pressing and releasing a button.)
1482 * - We let the user perform a resize/move operation by a mouse drag
1483 * initiated anywhere.
1485 * "Anywhere" means: inside or outside the window. We make this happen
1486 * with the 'forced_capture' flag.
1488 w->mouse.forced_capture = (edit->drag_state != MCEDIT_DRAG_NONE);
1490 return ret;
1493 /* --------------------------------------------------------------------------------------------- */
1495 * Toggle window fuulscreen mode.
1497 * @param edit editor object
1500 void
1501 edit_toggle_fullscreen (WEdit * edit)
1503 edit->fullscreen = !edit->fullscreen;
1504 edit->force = REDRAW_COMPLETELY;
1506 if (!edit->fullscreen)
1507 edit_restore_size (edit);
1508 else
1510 Widget *w = WIDGET (edit);
1511 Widget *h = WIDGET (w->owner);
1513 edit_save_size (edit);
1514 widget_set_size (w, h->y + 1, h->x, h->lines - 2, h->cols);
1515 edit->force |= REDRAW_PAGE;
1516 edit_update_screen (edit);
1520 /* --------------------------------------------------------------------------------------------- */