Move widget add/del API from WDialog to WGroup.
[midnight-commander.git] / src / editor / editwidget.c
blobd9b267f89cb0aeb1153d7be33cad9f4b3ad8a052
1 /*
2 Editor initialisation and callback handler.
4 Copyright (C) 1996-2020
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/execute.h" /* toggle_subshell() */
61 #include "src/filemanager/cmd.h" /* save_setup_cmd() */
62 #include "src/learn.h" /* learn_keys() */
63 #include "src/args.h" /* mcedit_arg_t */
65 #include "edit-impl.h"
66 #include "editwidget.h"
67 #ifdef HAVE_ASPELL
68 #include "spell.h"
69 #endif
71 /*** global variables ****************************************************************************/
73 char *edit_window_state_char = NULL;
74 char *edit_window_close_char = NULL;
76 /*** file scope macro definitions ****************************************************************/
78 #define WINDOW_MIN_LINES (2 + 2)
79 #define WINDOW_MIN_COLS (2 + LINE_STATE_WIDTH + 2)
81 /*** file scope type declarations ****************************************************************/
83 /*** file scope variables ************************************************************************/
84 static unsigned int edit_dlg_init_refcounter = 0;
86 /*** file scope functions ************************************************************************/
88 static cb_ret_t edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
89 void *data);
91 /* --------------------------------------------------------------------------------------------- */
92 /**
93 * Init the 'edit' subsystem
96 static void
97 edit_dlg_init (void)
99 if (edit_dlg_init_refcounter == 0)
101 edit_window_state_char = mc_skin_get ("widget-editor", "window-state-char", "*");
102 edit_window_close_char = mc_skin_get ("widget-editor", "window-close-char", "X");
104 #ifdef HAVE_ASPELL
105 aspell_init ();
106 #endif
109 edit_dlg_init_refcounter++;
112 /* --------------------------------------------------------------------------------------------- */
114 * Deinit the 'edit' subsystem
117 static void
118 edit_dlg_deinit (void)
120 if (edit_dlg_init_refcounter != 0)
121 edit_dlg_init_refcounter--;
123 if (edit_dlg_init_refcounter == 0)
125 g_free (edit_window_state_char);
126 g_free (edit_window_close_char);
128 #ifdef HAVE_ASPELL
129 aspell_clean ();
130 #endif
134 /* --------------------------------------------------------------------------------------------- */
136 * Show info about editor
139 static void
140 edit_about (void)
142 quick_widget_t quick_widgets[] = {
143 /* *INDENT-OFF* */
144 QUICK_LABEL ("MCEdit " VERSION, NULL),
145 QUICK_SEPARATOR (TRUE),
146 QUICK_LABEL (N_("A user friendly text editor\n"
147 "written for the Midnight Commander."), NULL),
148 QUICK_SEPARATOR (FALSE),
149 QUICK_LABEL (N_("Copyright (C) 1996-2020 the Free Software Foundation"), NULL),
150 QUICK_START_BUTTONS (TRUE, TRUE),
151 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
152 QUICK_END
153 /* *INDENT-ON* */
156 quick_dialog_t qdlg = {
157 -1, -1, 40,
158 N_("About"), "[Internal File Editor]",
159 quick_widgets, NULL, NULL
162 quick_widgets[0].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
163 quick_widgets[2].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
164 quick_widgets[4].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
166 (void) quick_dialog (&qdlg);
169 /* --------------------------------------------------------------------------------------------- */
171 * Show a help window
174 static void
175 edit_help (void)
177 ev_help_t event_data = { NULL, "[Internal File Editor]" };
178 mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
181 /* --------------------------------------------------------------------------------------------- */
183 * Restore saved window size.
185 * @param edit editor object
188 static void
189 edit_restore_size (WEdit * edit)
191 Widget *w = WIDGET (edit);
193 edit->drag_state = MCEDIT_DRAG_NONE;
194 w->mouse.forced_capture = FALSE;
195 widget_set_size (w, edit->y_prev, edit->x_prev, edit->lines_prev, edit->cols_prev);
196 dlg_draw (DIALOG (w->owner));
199 /* --------------------------------------------------------------------------------------------- */
201 * Move window by one row or column in any direction.
203 * @param edit editor object
204 * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
207 static void
208 edit_window_move (WEdit * edit, long command)
210 Widget *w = WIDGET (edit);
211 Widget *wh = WIDGET (w->owner);
213 switch (command)
215 case CK_Up:
216 if (w->y > wh->y + 1) /* menubar */
217 w->y--;
218 break;
219 case CK_Down:
220 if (w->y < wh->y + wh->lines - 2) /* buttonbar */
221 w->y++;
222 break;
223 case CK_Left:
224 if (w->x + w->cols > wh->x)
225 w->x--;
226 break;
227 case CK_Right:
228 if (w->x < wh->x + wh->cols)
229 w->x++;
230 break;
231 default:
232 return;
235 edit->force |= REDRAW_PAGE;
236 dlg_draw (DIALOG (w->owner));
239 /* --------------------------------------------------------------------------------------------- */
241 * Resize window by one row or column in any direction.
243 * @param edit editor object
244 * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
247 static void
248 edit_window_resize (WEdit * edit, long command)
250 Widget *w = WIDGET (edit);
251 Widget *wh = WIDGET (w->owner);
253 switch (command)
255 case CK_Up:
256 if (w->lines > WINDOW_MIN_LINES)
257 w->lines--;
258 break;
259 case CK_Down:
260 if (w->y + w->lines < wh->y + wh->lines - 1) /* buttonbar */
261 w->lines++;
262 break;
263 case CK_Left:
264 if (w->cols > WINDOW_MIN_COLS)
265 w->cols--;
266 break;
267 case CK_Right:
268 if (w->x + w->cols < wh->x + wh->cols)
269 w->cols++;
270 break;
271 default:
272 return;
275 edit->force |= REDRAW_COMPLETELY;
276 dlg_draw (DIALOG (w->owner));
279 /* --------------------------------------------------------------------------------------------- */
281 * Get hotkey by number.
283 * @param n number
284 * @return hotkey
287 static unsigned char
288 get_hotkey (int n)
290 return (n <= 9) ? '0' + n : 'a' + n - 10;
293 /* --------------------------------------------------------------------------------------------- */
295 static void
296 edit_window_list (const WDialog * h)
298 const WGroup *g = CONST_GROUP (h);
299 const size_t offset = 2; /* skip menu and buttonbar */
300 const size_t dlg_num = g_list_length (g->widgets) - offset;
301 int lines, cols;
302 Listbox *listbox;
303 GList *w;
304 WEdit *selected;
305 int i = 0;
307 lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
308 cols = COLS * 2 / 3;
310 listbox = create_listbox_window (lines, cols, _("Open files"), "[Open files]");
312 for (w = g->widgets; w != NULL; w = g_list_next (w))
313 if (edit_widget_is_editor (CONST_WIDGET (w->data)))
315 WEdit *e = (WEdit *) w->data;
316 char *fname;
318 if (e->filename_vpath == NULL)
319 fname = g_strdup_printf ("%c [%s]", e->modified ? '*' : ' ', _("NoName"));
320 else
321 fname =
322 g_strdup_printf ("%c%s", e->modified ? '*' : ' ',
323 vfs_path_as_str (e->filename_vpath));
325 listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++),
326 str_term_trim (fname, WIDGET (listbox->list)->cols - 2), e, FALSE);
327 g_free (fname);
330 selected = run_listbox_with_data (listbox, g->current->data);
331 if (selected != NULL)
332 widget_select (WIDGET (selected));
335 /* --------------------------------------------------------------------------------------------- */
337 static char *
338 edit_get_shortcut (long command)
340 const char *ext_map;
341 const char *shortcut = NULL;
343 shortcut = keybind_lookup_keymap_shortcut (editor_map, command);
344 if (shortcut != NULL)
345 return g_strdup (shortcut);
347 ext_map = keybind_lookup_keymap_shortcut (editor_map, CK_ExtendedKeyMap);
348 if (ext_map != NULL)
349 shortcut = keybind_lookup_keymap_shortcut (editor_x_map, command);
350 if (shortcut != NULL)
351 return g_strdup_printf ("%s %s", ext_map, shortcut);
353 return NULL;
356 /* --------------------------------------------------------------------------------------------- */
358 static char *
359 edit_get_title (const WDialog * h, size_t len)
361 const WEdit *edit = find_editor (h);
362 const char *modified = edit->modified ? "(*) " : " ";
363 const char *file_label;
364 char *filename;
366 len -= 4;
368 if (edit->filename_vpath == NULL)
369 filename = g_strdup (_("[NoName]"));
370 else
371 filename = g_strdup (vfs_path_as_str (edit->filename_vpath));
373 file_label = str_term_trim (filename, len - str_term_width1 (_("Edit: ")));
374 g_free (filename);
376 return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL);
379 /* --------------------------------------------------------------------------------------------- */
381 static cb_ret_t
382 edit_dialog_command_execute (WDialog * h, long command)
384 WGroup *g = GROUP (h);
385 Widget *wh = WIDGET (h);
386 cb_ret_t ret = MSG_HANDLED;
388 switch (command)
390 case CK_EditNew:
391 edit_add_window (h, wh->y + 1, wh->x, wh->lines - 2, wh->cols, NULL, 0);
392 break;
393 case CK_EditFile:
394 edit_load_cmd (h);
395 break;
396 case CK_History:
397 edit_load_file_from_history (h);
398 break;
399 case CK_EditSyntaxFile:
400 edit_load_syntax_file (h);
401 break;
402 case CK_EditUserMenu:
403 edit_load_menu_file (h);
404 break;
405 case CK_Close:
406 /* if there are no opened files anymore, close MC editor */
407 if (edit_widget_is_editor (CONST_WIDGET (g->current->data)) &&
408 edit_close_cmd ((WEdit *) g->current->data) && find_editor (h) == NULL)
409 dlg_stop (h);
410 break;
411 case CK_Help:
412 edit_help ();
413 /* edit->force |= REDRAW_COMPLETELY; */
414 break;
415 case CK_Menu:
416 edit_menu_cmd (h);
417 break;
418 case CK_Quit:
419 case CK_Cancel:
420 /* don't close editor due to SIGINT, but stop move/resize window */
422 Widget *w = WIDGET (g->current->data);
424 if (edit_widget_is_editor (w) && ((WEdit *) w)->drag_state != MCEDIT_DRAG_NONE)
425 edit_restore_size ((WEdit *) w);
426 else if (command == CK_Quit)
427 dlg_stop (h);
429 break;
430 case CK_About:
431 edit_about ();
432 break;
433 case CK_SyntaxOnOff:
434 edit_syntax_onoff_cmd (h);
435 break;
436 case CK_ShowTabTws:
437 edit_show_tabs_tws_cmd (h);
438 break;
439 case CK_ShowMargin:
440 edit_show_margin_cmd (h);
441 break;
442 case CK_ShowNumbers:
443 edit_show_numbers_cmd (h);
444 break;
445 case CK_Refresh:
446 edit_refresh_cmd ();
447 break;
448 case CK_Shell:
449 toggle_subshell ();
450 break;
451 case CK_LearnKeys:
452 learn_keys ();
453 break;
454 case CK_WindowMove:
455 case CK_WindowResize:
456 if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
457 edit_handle_move_resize ((WEdit *) g->current->data, command);
458 break;
459 case CK_WindowList:
460 edit_window_list (h);
461 break;
462 case CK_WindowNext:
463 group_select_next_widget (g);
464 break;
465 case CK_WindowPrev:
466 group_select_prev_widget (g);
467 break;
468 case CK_Options:
469 edit_options_dialog (h);
470 break;
471 case CK_OptionsSaveMode:
472 edit_save_mode_cmd ();
473 break;
474 case CK_SaveSetup:
475 save_setup_cmd ();
476 break;
477 default:
478 ret = MSG_NOT_HANDLED;
479 break;
482 return ret;
485 /* --------------------------------------------------------------------------------------------- */
487 * Translate the keycode into either 'command' or 'char_for_insertion'.
488 * 'command' is one of the editor commands from cmddef.h.
491 static gboolean
492 edit_translate_key (WEdit * edit, long x_key, int *cmd, int *ch)
494 long command = CK_InsertChar;
495 int char_for_insertion = -1;
497 /* an ordinary insertable character */
498 if (!edit->extmod && x_key < 256)
500 #ifndef HAVE_CHARSET
501 if (is_printable (x_key))
503 char_for_insertion = x_key;
504 goto fin;
506 #else
507 int c;
509 if (edit->charpoint >= 4)
511 edit->charpoint = 0;
512 edit->charbuf[edit->charpoint] = '\0';
514 if (edit->charpoint < 4)
516 edit->charbuf[edit->charpoint++] = x_key;
517 edit->charbuf[edit->charpoint] = '\0';
520 /* input from 8-bit locale */
521 if (!mc_global.utf8_display)
523 /* source in 8-bit codeset */
524 c = convert_from_input_c (x_key);
526 if (is_printable (c))
528 if (!edit->utf8)
529 char_for_insertion = c;
530 else
531 char_for_insertion = convert_from_8bit_to_utf_c2 ((char) x_key);
532 goto fin;
535 else
537 /* UTF-8 locale */
538 int res;
540 res = str_is_valid_char (edit->charbuf, edit->charpoint);
541 if (res < 0 && res != -2)
543 edit->charpoint = 0; /* broken multibyte char, skip */
544 goto fin;
547 if (edit->utf8)
549 /* source in UTF-8 codeset */
550 if (res < 0)
552 char_for_insertion = x_key;
553 goto fin;
556 edit->charbuf[edit->charpoint] = '\0';
557 edit->charpoint = 0;
558 if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
560 char_for_insertion = x_key;
561 goto fin;
564 else
566 /* 8-bit source */
567 if (res < 0)
569 /* not finised multibyte input (in meddle multibyte utf-8 char) */
570 goto fin;
573 if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
575 c = convert_from_utf_to_current (edit->charbuf);
576 edit->charbuf[0] = '\0';
577 edit->charpoint = 0;
578 char_for_insertion = c;
579 goto fin;
582 /* unprinteble utf input, skip it */
583 edit->charbuf[0] = '\0';
584 edit->charpoint = 0;
587 #endif /* HAVE_CHARSET */
590 /* Commands specific to the key emulation */
591 if (edit->extmod)
593 edit->extmod = FALSE;
594 command = keybind_lookup_keymap_command (editor_x_map, x_key);
596 else
597 command = keybind_lookup_keymap_command (editor_map, x_key);
599 if (command == CK_IgnoreKey)
600 command = CK_InsertChar;
602 fin:
603 *cmd = (int) command; /* FIXME */
604 *ch = char_for_insertion;
606 return !(command == CK_InsertChar && char_for_insertion == -1);
610 /* --------------------------------------------------------------------------------------------- */
612 static inline void
613 edit_quit (WDialog * h)
615 GList *l;
616 WEdit *e = NULL;
617 GSList *m = NULL;
618 GSList *me;
620 /* don't stop the dialog before final decision */
621 widget_set_state (WIDGET (h), WST_ACTIVE, TRUE);
623 /* check window state and get modified files */
624 for (l = GROUP (h)->widgets; l != NULL; l = g_list_next (l))
625 if (edit_widget_is_editor (CONST_WIDGET (l->data)))
627 e = (WEdit *) l->data;
629 if (e->drag_state != MCEDIT_DRAG_NONE)
631 edit_restore_size (e);
632 g_slist_free (m);
633 return;
636 /* create separate list because widget_select()
637 changes the window position in Z order */
638 if (e->modified)
639 m = g_slist_prepend (m, l->data);
642 for (me = m; me != NULL; me = g_slist_next (me))
644 e = (WEdit *) me->data;
646 widget_select (WIDGET (e));
648 if (!edit_ok_to_exit (e))
649 break;
652 /* if all files were checked, quit editor */
653 if (me == NULL)
654 dlg_stop (h);
656 g_slist_free (m);
659 /* --------------------------------------------------------------------------------------------- */
661 static inline void
662 edit_set_buttonbar (WEdit * edit, WButtonBar * bb)
664 buttonbar_set_label (bb, 1, Q_ ("ButtonBar|Help"), editor_map, NULL);
665 buttonbar_set_label (bb, 2, Q_ ("ButtonBar|Save"), editor_map, WIDGET (edit));
666 buttonbar_set_label (bb, 3, Q_ ("ButtonBar|Mark"), editor_map, WIDGET (edit));
667 buttonbar_set_label (bb, 4, Q_ ("ButtonBar|Replac"), editor_map, WIDGET (edit));
668 buttonbar_set_label (bb, 5, Q_ ("ButtonBar|Copy"), editor_map, WIDGET (edit));
669 buttonbar_set_label (bb, 6, Q_ ("ButtonBar|Move"), editor_map, WIDGET (edit));
670 buttonbar_set_label (bb, 7, Q_ ("ButtonBar|Search"), editor_map, WIDGET (edit));
671 buttonbar_set_label (bb, 8, Q_ ("ButtonBar|Delete"), editor_map, WIDGET (edit));
672 buttonbar_set_label (bb, 9, Q_ ("ButtonBar|PullDn"), editor_map, NULL);
673 buttonbar_set_label (bb, 10, Q_ ("ButtonBar|Quit"), editor_map, NULL);
676 /* --------------------------------------------------------------------------------------------- */
678 static void
679 edit_total_update (WEdit * edit)
681 edit_find_bracket (edit);
682 edit->force |= REDRAW_COMPLETELY;
683 edit_update_curs_row (edit);
684 edit_update_screen (edit);
687 /* --------------------------------------------------------------------------------------------- */
689 static gboolean
690 edit_update_cursor (WEdit * edit, const mouse_event_t * event)
692 int x, y;
693 gboolean done;
695 x = event->x - (edit->fullscreen ? 0 : 1);
696 y = event->y - (edit->fullscreen ? 0 : 1);
698 if (edit->mark2 != -1 && event->msg == MSG_MOUSE_UP)
699 return TRUE; /* don't do anything */
701 if (event->msg == MSG_MOUSE_DOWN || event->msg == MSG_MOUSE_UP)
702 edit_push_key_press (edit);
704 if (!option_cursor_beyond_eol)
705 edit->prev_col = x - edit->start_col - option_line_state_width;
706 else
708 long line_len;
710 line_len =
711 edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
712 edit_buffer_get_current_eol (&edit->buffer));
714 if (x > line_len - 1)
716 edit->over_col = x - line_len - edit->start_col - option_line_state_width;
717 edit->prev_col = line_len;
719 else
721 edit->over_col = 0;
722 edit->prev_col = x - option_line_state_width - edit->start_col;
726 if (y > edit->curs_row)
727 edit_move_down (edit, y - edit->curs_row, FALSE);
728 else if (y < edit->curs_row)
729 edit_move_up (edit, edit->curs_row - y, FALSE);
730 else
731 edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
733 if (event->msg == MSG_MOUSE_CLICK)
735 edit_mark_cmd (edit, TRUE); /* reset */
736 edit->highlight = 0;
739 done = (event->msg != MSG_MOUSE_DRAG);
740 if (done)
741 edit_mark_cmd (edit, FALSE);
743 return done;
746 /* --------------------------------------------------------------------------------------------- */
747 /** Callback for the edit dialog */
749 static cb_ret_t
750 edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
752 WGroup *g = GROUP (w);
753 WDialog *h = DIALOG (w);
755 switch (msg)
757 case MSG_INIT:
758 edit_dlg_init ();
759 return MSG_HANDLED;
761 case MSG_DRAW:
762 /* don't use dlg_default_repaint() -- we don't need a frame */
763 tty_setcolor (EDITOR_BACKGROUND);
764 dlg_erase (h);
765 return MSG_HANDLED;
767 case MSG_RESIZE:
768 dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL);
769 menubar_arrange (find_menubar (h));
770 return MSG_HANDLED;
772 case MSG_ACTION:
774 /* Handle shortcuts, menu, and buttonbar. */
776 cb_ret_t result;
778 result = edit_dialog_command_execute (h, parm);
780 /* We forward any commands coming from the menu, and which haven't been
781 handled by the dialog, to the focused WEdit window. */
782 if (result == MSG_NOT_HANDLED && sender == WIDGET (find_menubar (h)))
783 result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL);
785 return result;
788 case MSG_KEY:
790 Widget *we = WIDGET (g->current->data);
791 cb_ret_t ret = MSG_NOT_HANDLED;
793 if (edit_widget_is_editor (we))
795 WEdit *e = (WEdit *) we;
796 long command;
798 if (!e->extmod)
799 command = keybind_lookup_keymap_command (editor_map, parm);
800 else
801 command = keybind_lookup_keymap_command (editor_x_map, parm);
803 if (command == CK_IgnoreKey)
804 e->extmod = FALSE;
805 else
807 ret = edit_dialog_command_execute (h, command);
808 /* if command was not handled, keep the extended mode
809 for the further key processing */
810 if (ret == MSG_HANDLED)
811 e->extmod = FALSE;
816 * Due to the "end of bracket" escape the editor sees input with is_idle() == false
817 * (expects more characters) and hence doesn't yet refresh the screen, but then
818 * no further characters arrive (there's only an "end of bracket" which is swallowed
819 * by tty_get_event()), so you end up with a screen that's not refreshed after pasting.
820 * So let's trigger an IDLE signal.
822 if (!is_idle ())
823 widget_idle (w, TRUE);
824 return ret;
827 /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */
828 case MSG_UNHANDLED_KEY:
829 return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
831 case MSG_VALIDATE:
832 edit_quit (h);
833 return MSG_HANDLED;
835 case MSG_END:
836 edit_dlg_deinit ();
837 return MSG_HANDLED;
839 case MSG_IDLE:
840 widget_idle (w, FALSE);
841 return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL);
843 default:
844 return dlg_default_callback (w, sender, msg, parm, data);
848 /* --------------------------------------------------------------------------------------------- */
851 * Handle mouse events of editor screen.
853 * @param w Widget object (the editor)
854 * @param msg mouse event message
855 * @param event mouse event data
857 static void
858 edit_dialog_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
860 gboolean unhandled = TRUE;
862 if (msg == MSG_MOUSE_DOWN && event->y == 0)
864 WGroup *g = GROUP (w);
865 WDialog *h = DIALOG (w);
866 WMenuBar *b;
868 b = find_menubar (h);
870 if (!widget_get_state (WIDGET (b), WST_FOCUSED))
872 /* menubar */
874 GList *l;
875 GList *top = NULL;
876 int x;
878 /* Try find top fullscreen window */
879 for (l = g->widgets; l != NULL; l = g_list_next (l))
880 if (edit_widget_is_editor (CONST_WIDGET (l->data))
881 && ((WEdit *) l->data)->fullscreen)
882 top = l;
884 /* Handle fullscreen/close buttons in the top line */
885 x = w->cols - 6;
887 if (top != NULL && event->x >= x)
889 WEdit *e = (WEdit *) top->data;
891 if (top != g->current)
893 /* Window is not active. Activate it */
894 widget_select (WIDGET (e));
897 /* Handle buttons */
898 if (event->x - x <= 2)
899 edit_toggle_fullscreen (e);
900 else
901 send_message (h, NULL, MSG_ACTION, CK_Close, NULL);
903 unhandled = FALSE;
906 if (unhandled)
907 menubar_activate (b, drop_menus, -1);
911 /* Continue handling of unhandled event in window or menu */
912 event->result.abort = unhandled;
915 /* --------------------------------------------------------------------------------------------- */
917 static cb_ret_t
918 edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
920 WEdit *e = (WEdit *) w;
922 switch (msg)
924 case MSG_FOCUS:
925 edit_set_buttonbar (e, find_buttonbar (DIALOG (w->owner)));
926 return MSG_HANDLED;
928 case MSG_DRAW:
929 e->force |= REDRAW_COMPLETELY;
930 edit_update_screen (e);
931 return MSG_HANDLED;
933 case MSG_KEY:
935 int cmd, ch;
936 cb_ret_t ret = MSG_NOT_HANDLED;
938 /* The user may override the access-keys for the menu bar. */
939 if (macro_index == -1 && edit_execute_macro (e, parm))
941 edit_update_screen (e);
942 ret = MSG_HANDLED;
944 else if (edit_translate_key (e, parm, &cmd, &ch))
946 edit_execute_key_command (e, cmd, ch);
947 edit_update_screen (e);
948 ret = MSG_HANDLED;
951 return ret;
954 case MSG_ACTION:
955 /* command from menubar or buttonbar */
956 edit_execute_key_command (e, parm, -1);
957 edit_update_screen (e);
958 return MSG_HANDLED;
960 case MSG_CURSOR:
962 int y, x;
964 y = (e->fullscreen ? 0 : 1) + EDIT_TEXT_VERTICAL_OFFSET + e->curs_row;
965 x = (e->fullscreen ? 0 : 1) + EDIT_TEXT_HORIZONTAL_OFFSET + option_line_state_width +
966 e->curs_col + e->start_col + e->over_col;
968 widget_gotoyx (w, y, x);
969 return MSG_HANDLED;
972 case MSG_IDLE:
973 edit_update_screen (e);
974 return MSG_HANDLED;
976 case MSG_DESTROY:
977 edit_clean (e);
978 return MSG_HANDLED;
980 default:
981 return widget_default_callback (w, sender, msg, parm, data);
985 /* --------------------------------------------------------------------------------------------- */
988 * Handle move/resize mouse events.
990 static void
991 edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event)
993 WEdit *edit = (WEdit *) (w);
994 Widget *h = WIDGET (w->owner);
995 int global_x, global_y;
997 if (msg == MSG_MOUSE_UP)
999 /* Exit move/resize mode. */
1000 edit_execute_cmd (edit, CK_Enter, -1);
1001 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
1002 return;
1005 if (msg != MSG_MOUSE_DRAG)
1007 * We ignore any other events. Specifically, MSG_MOUSE_DOWN.
1009 * When the move/resize is initiated by the menu, we let the user
1010 * stop it by clicking with the mouse. Which is why we don't want
1011 * a mouse down to affect the window.
1013 return;
1015 /* Convert point to global coordinates for easier calculations. */
1016 global_x = event->x + w->x;
1017 global_y = event->y + w->y;
1019 /* Clamp the point to the dialog's client area. */
1020 global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */
1021 global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */
1023 if (edit->drag_state == MCEDIT_DRAG_MOVE)
1025 w->y = global_y;
1026 w->x = global_x - edit->drag_state_start;
1028 else if (edit->drag_state == MCEDIT_DRAG_RESIZE)
1030 w->lines = MAX (WINDOW_MIN_LINES, global_y - w->y + 1);
1031 w->cols = MAX (WINDOW_MIN_COLS, global_x - w->x + 1);
1034 edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */
1036 /* We draw the whole dialog because dragging/resizing exposes area beneath. */
1037 dlg_draw (DIALOG (w->owner));
1040 /* --------------------------------------------------------------------------------------------- */
1043 * Handle mouse events of editor window
1045 * @param w Widget object (the editor window)
1046 * @param msg mouse event message
1047 * @param event mouse event data
1049 static void
1050 edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
1052 WEdit *edit = (WEdit *) w;
1053 /* buttons' distance from right edge */
1054 int dx = edit->fullscreen ? 0 : 2;
1055 /* location of 'Close' and 'Toggle fullscreen' pictograms */
1056 int close_x, toggle_fullscreen_x;
1058 close_x = (w->cols - 1) - dx - 1;
1059 toggle_fullscreen_x = close_x - 3;
1061 if (edit->drag_state != MCEDIT_DRAG_NONE)
1063 /* window is being resized/moved */
1064 edit_mouse_handle_move_resize (w, msg, event);
1065 return;
1068 /* If it's the last line on the screen, we abort the event to make the
1069 * system channel it to the overlapping buttonbar instead. We have to do
1070 * this because a WEdit has the WOP_TOP_SELECT flag, which makes it above
1071 * the buttonbar in Z-order. */
1072 if (msg == MSG_MOUSE_DOWN && (event->y + w->y == LINES - 1))
1074 event->result.abort = TRUE;
1075 return;
1078 switch (msg)
1080 case MSG_MOUSE_DOWN:
1081 widget_select (w);
1082 edit_update_curs_row (edit);
1083 edit_update_curs_col (edit);
1085 if (!edit->fullscreen)
1087 if (event->y == 0)
1089 if (event->x >= close_x - 1 && event->x <= close_x + 1)
1090 ; /* do nothing (see MSG_MOUSE_CLICK) */
1091 else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
1092 ; /* do nothing (see MSG_MOUSE_CLICK) */
1093 else
1095 /* start window move */
1096 edit_execute_cmd (edit, CK_WindowMove, -1);
1097 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
1098 edit->drag_state_start = event->x;
1100 break;
1103 if (event->y == w->lines - 1 && event->x == w->cols - 1)
1105 /* bottom-right corner -- start window resize */
1106 edit_execute_cmd (edit, CK_WindowResize, -1);
1107 break;
1111 MC_FALLTHROUGH; /* to start/stop text selection */
1113 case MSG_MOUSE_UP:
1114 edit_update_cursor (edit, event);
1115 edit_total_update (edit);
1116 break;
1118 case MSG_MOUSE_CLICK:
1119 if (event->y == 0)
1121 if (event->x >= close_x - 1 && event->x <= close_x + 1)
1122 send_message (w->owner, NULL, MSG_ACTION, CK_Close, NULL);
1123 else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
1124 edit_toggle_fullscreen (edit);
1125 else if (!edit->fullscreen && event->count == GPM_DOUBLE)
1126 /* double click on top line (toggle fullscreen) */
1127 edit_toggle_fullscreen (edit);
1129 else if (event->count == GPM_DOUBLE)
1131 /* double click */
1132 edit_mark_current_word_cmd (edit);
1133 edit_total_update (edit);
1135 else if (event->count == GPM_TRIPLE)
1137 /* triple click: works in GPM only, not in xterm */
1138 edit_mark_current_line_cmd (edit);
1139 edit_total_update (edit);
1141 break;
1143 case MSG_MOUSE_DRAG:
1144 edit_update_cursor (edit, event);
1145 edit_total_update (edit);
1146 break;
1148 case MSG_MOUSE_SCROLL_UP:
1149 edit_move_up (edit, 2, TRUE);
1150 edit_total_update (edit);
1151 break;
1153 case MSG_MOUSE_SCROLL_DOWN:
1154 edit_move_down (edit, 2, TRUE);
1155 edit_total_update (edit);
1156 break;
1158 default:
1159 break;
1163 /* --------------------------------------------------------------------------------------------- */
1164 /*** public functions ****************************************************************************/
1165 /* --------------------------------------------------------------------------------------------- */
1167 * Edit one file.
1169 * @param file_vpath file object
1170 * @param line line number
1171 * @return TRUE if no errors was occurred, FALSE otherwise
1174 gboolean
1175 edit_file (const vfs_path_t * file_vpath, long line)
1177 mcedit_arg_t arg = { (vfs_path_t *) file_vpath, line };
1178 GList *files;
1179 gboolean ok;
1181 files = g_list_prepend (NULL, &arg);
1182 ok = edit_files (files);
1183 g_list_free (files);
1185 return ok;
1188 /* --------------------------------------------------------------------------------------------- */
1190 gboolean
1191 edit_files (const GList * files)
1193 static gboolean made_directory = FALSE;
1194 WDialog *edit_dlg;
1195 WGroup *g;
1196 WMenuBar *menubar;
1197 Widget *w, *wd;
1198 const GList *file;
1199 gboolean ok = FALSE;
1201 if (!made_directory)
1203 char *dir;
1205 dir = mc_build_filename (mc_config_get_cache_path (), EDIT_DIR, (char *) NULL);
1206 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1207 g_free (dir);
1209 dir = mc_build_filename (mc_config_get_path (), EDIT_DIR, (char *) NULL);
1210 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1211 g_free (dir);
1213 dir = mc_build_filename (mc_config_get_data_path (), EDIT_DIR, (char *) NULL);
1214 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1215 g_free (dir);
1218 /* Create a new dialog and add it widgets to it */
1219 edit_dlg =
1220 dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, edit_dialog_callback,
1221 edit_dialog_mouse_callback, "[Internal File Editor]", NULL);
1222 wd = WIDGET (edit_dlg);
1223 widget_want_tab (wd, TRUE);
1225 edit_dlg->get_shortcut = edit_get_shortcut;
1226 edit_dlg->get_title = edit_get_title;
1228 g = GROUP (edit_dlg);
1230 menubar = menubar_new (NULL, TRUE);
1231 w = WIDGET (menubar);
1232 group_add_widget_autopos (g, w, w->pos_flags, NULL);
1233 edit_init_menu (menubar);
1235 w = WIDGET (buttonbar_new (TRUE));
1236 group_add_widget_autopos (g, w, w->pos_flags, NULL);
1238 for (file = files; file != NULL; file = g_list_next (file))
1240 mcedit_arg_t *f = (mcedit_arg_t *) file->data;
1241 gboolean f_ok;
1243 f_ok = edit_add_window (edit_dlg, wd->y + 1, wd->x, wd->lines - 2, wd->cols, f->file_vpath,
1244 f->line_number);
1245 /* at least one file has been opened succefully */
1246 ok = ok || f_ok;
1249 if (ok)
1250 dlg_run (edit_dlg);
1252 if (!ok || widget_get_state (wd, WST_CLOSED))
1253 dlg_destroy (edit_dlg);
1255 return ok;
1258 /* --------------------------------------------------------------------------------------------- */
1260 const char *
1261 edit_get_file_name (const WEdit * edit)
1263 return vfs_path_as_str (edit->filename_vpath);
1266 /* --------------------------------------------------------------------------------------------- */
1268 WEdit *
1269 find_editor (const WDialog * h)
1271 const WGroup *g = CONST_GROUP (h);
1273 if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
1274 return (WEdit *) g->current->data;
1275 return (WEdit *) find_widget_type (h, edit_callback);
1278 /* --------------------------------------------------------------------------------------------- */
1280 * Check if widget is an WEdit class.
1282 * @param w probably editor object
1283 * @return TRUE if widget is an WEdit class, FALSE otherwise
1286 gboolean
1287 edit_widget_is_editor (const Widget * w)
1289 return (w != NULL && w->callback == edit_callback);
1292 /* --------------------------------------------------------------------------------------------- */
1294 void
1295 edit_update_screen (WEdit * e)
1297 edit_scroll_screen_over_cursor (e);
1298 edit_update_curs_col (e);
1299 edit_status (e, widget_get_state (WIDGET (e), WST_FOCUSED));
1301 /* pop all events for this window for internal handling */
1302 if (!is_idle ())
1303 e->force |= REDRAW_PAGE;
1304 else
1306 if ((e->force & REDRAW_COMPLETELY) != 0)
1307 e->force |= REDRAW_PAGE;
1308 edit_render_keypress (e);
1311 widget_draw (WIDGET (find_buttonbar (DIALOG (WIDGET (e)->owner))));
1314 /* --------------------------------------------------------------------------------------------- */
1316 * Save current window size.
1318 * @param edit editor object
1321 void
1322 edit_save_size (WEdit * edit)
1324 Widget *w = WIDGET (edit);
1326 edit->y_prev = w->y;
1327 edit->x_prev = w->x;
1328 edit->lines_prev = w->lines;
1329 edit->cols_prev = w->cols;
1332 /* --------------------------------------------------------------------------------------------- */
1334 * Create new editor window and insert it into editor screen.
1336 * @param h editor dialog (screen)
1337 * @param y y coordinate
1338 * @param x x coordinate
1339 * @param lines window height
1340 * @param cols window width
1341 * @param f file object
1342 * @param fline line number in file
1343 * @return TRUE if new window was successfully created and inserted into editor screen,
1344 * FALSE otherwise
1347 gboolean
1348 edit_add_window (WDialog * h, int y, int x, int lines, int cols, const vfs_path_t * f, long fline)
1350 WEdit *edit;
1351 Widget *w;
1353 edit = edit_init (NULL, y, x, lines, cols, f, fline);
1354 if (edit == NULL)
1355 return FALSE;
1357 w = WIDGET (edit);
1358 w->callback = edit_callback;
1359 w->mouse_callback = edit_mouse_callback;
1361 group_add_widget_autopos (GROUP (h), w, WPOS_KEEP_ALL, NULL);
1362 edit_set_buttonbar (edit, find_buttonbar (h));
1363 dlg_draw (h);
1365 return TRUE;
1368 /* --------------------------------------------------------------------------------------------- */
1370 * Handle move/resize events.
1372 * @param edit editor object
1373 * @param command action id
1374 * @return TRUE if the action was handled, FALSE otherwise
1377 gboolean
1378 edit_handle_move_resize (WEdit * edit, long command)
1380 Widget *w = WIDGET (edit);
1381 gboolean ret = FALSE;
1383 if (edit->fullscreen)
1385 edit->drag_state = MCEDIT_DRAG_NONE;
1386 w->mouse.forced_capture = FALSE;
1387 return ret;
1390 switch (edit->drag_state)
1392 case MCEDIT_DRAG_NONE:
1393 /* possible start move/resize */
1394 switch (command)
1396 case CK_WindowMove:
1397 edit->drag_state = MCEDIT_DRAG_MOVE;
1398 edit_save_size (edit);
1399 edit_status (edit, TRUE); /* redraw frame and status */
1401 * If a user initiates a move by the menu, not by the mouse, we
1402 * make a subsequent mouse drag pull the frame from its middle.
1403 * (We can instead choose '0' to pull it from the corner.)
1405 edit->drag_state_start = w->cols / 2;
1406 ret = TRUE;
1407 break;
1408 case CK_WindowResize:
1409 edit->drag_state = MCEDIT_DRAG_RESIZE;
1410 edit_save_size (edit);
1411 edit_status (edit, TRUE); /* redraw frame and status */
1412 ret = TRUE;
1413 break;
1414 default:
1415 break;
1417 break;
1419 case MCEDIT_DRAG_MOVE:
1420 switch (command)
1422 case CK_WindowResize:
1423 edit->drag_state = MCEDIT_DRAG_RESIZE;
1424 ret = TRUE;
1425 break;
1426 case CK_Up:
1427 case CK_Down:
1428 case CK_Left:
1429 case CK_Right:
1430 edit_window_move (edit, command);
1431 ret = TRUE;
1432 break;
1433 case CK_Enter:
1434 case CK_WindowMove:
1435 edit->drag_state = MCEDIT_DRAG_NONE;
1436 edit_status (edit, TRUE); /* redraw frame and status */
1437 MC_FALLTHROUGH;
1438 default:
1439 ret = TRUE;
1440 break;
1442 break;
1444 case MCEDIT_DRAG_RESIZE:
1445 switch (command)
1447 case CK_WindowMove:
1448 edit->drag_state = MCEDIT_DRAG_MOVE;
1449 ret = TRUE;
1450 break;
1451 case CK_Up:
1452 case CK_Down:
1453 case CK_Left:
1454 case CK_Right:
1455 edit_window_resize (edit, command);
1456 ret = TRUE;
1457 break;
1458 case CK_Enter:
1459 case CK_WindowResize:
1460 edit->drag_state = MCEDIT_DRAG_NONE;
1461 edit_status (edit, TRUE); /* redraw frame and status */
1462 MC_FALLTHROUGH;
1463 default:
1464 ret = TRUE;
1465 break;
1467 break;
1469 default:
1470 break;
1474 * - We let the user stop a resize/move operation by clicking with the
1475 * mouse anywhere. ("clicking" = pressing and releasing a button.)
1476 * - We let the user perform a resize/move operation by a mouse drag
1477 * initiated anywhere.
1479 * "Anywhere" means: inside or outside the window. We make this happen
1480 * with the 'forced_capture' flag.
1482 w->mouse.forced_capture = (edit->drag_state != MCEDIT_DRAG_NONE);
1484 return ret;
1487 /* --------------------------------------------------------------------------------------------- */
1489 * Toggle window fuulscreen mode.
1491 * @param edit editor object
1494 void
1495 edit_toggle_fullscreen (WEdit * edit)
1497 Widget *w = WIDGET (edit);
1499 edit->fullscreen = !edit->fullscreen;
1500 edit->force = REDRAW_COMPLETELY;
1502 if (!edit->fullscreen)
1504 edit_restore_size (edit);
1505 /* do not follow screen size on resize */
1506 w->pos_flags = WPOS_KEEP_DEFAULT;
1508 else
1510 Widget *h = WIDGET (w->owner);
1512 edit_save_size (edit);
1513 widget_set_size (w, h->y + 1, h->x, h->lines - 2, h->cols);
1514 /* follow screen size on resize */
1515 w->pos_flags = WPOS_KEEP_ALL;
1516 edit->force |= REDRAW_PAGE;
1517 edit_update_screen (edit);
1521 /* --------------------------------------------------------------------------------------------- */