summaryrefslogtreecommitdiffstats
path: root/src/editor/editwidget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/editor/editwidget.c')
-rw-r--r--src/editor/editwidget.c1550
1 files changed, 1550 insertions, 0 deletions
diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c
new file mode 100644
index 0000000..05f03e8
--- /dev/null
+++ b/src/editor/editwidget.c
@@ -0,0 +1,1550 @@
+/*
+ Editor initialisation and callback handler.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: editor initialisation and callback handler
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/tty/color.h" /* tty_setcolor() */
+#include "lib/skin.h"
+#include "lib/fileloc.h" /* EDIT_HOME_DIR */
+#include "lib/strutil.h" /* str_term_trim() */
+#include "lib/util.h" /* mc_build_filename() */
+#include "lib/widget.h"
+#include "lib/mcconfig.h"
+#include "lib/event.h" /* mc_event_raise() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/keymap.h" /* keybind_lookup_keymap_command() */
+#include "src/setup.h" /* home_dir */
+#include "src/execute.h" /* toggle_subshell() */
+#include "src/filemanager/cmd.h" /* save_setup_cmd() */
+#include "src/learn.h" /* learn_keys() */
+#include "src/args.h" /* mcedit_arg_t */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editmacros.h" /* edit_execute_macro() */
+#ifdef HAVE_ASPELL
+#include "spell.h"
+#endif
+
+/*** global variables ****************************************************************************/
+
+char *edit_window_state_char = NULL;
+char *edit_window_close_char = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define WINDOW_MIN_LINES (2 + 2)
+#define WINDOW_MIN_COLS (2 + LINE_STATE_WIDTH + 2)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned int edit_dlg_init_refcounter = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Init the 'edit' subsystem
+ */
+
+static void
+edit_dlg_init (void)
+{
+ edit_dlg_init_refcounter++;
+
+ if (edit_dlg_init_refcounter == 1)
+ {
+ edit_window_state_char = mc_skin_get ("widget-editor", "window-state-char", "*");
+ edit_window_close_char = mc_skin_get ("widget-editor", "window-close-char", "X");
+
+#ifdef HAVE_ASPELL
+ aspell_init ();
+#endif
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinit the 'edit' subsystem
+ */
+
+static void
+edit_dlg_deinit (void)
+{
+ if (edit_dlg_init_refcounter == 1)
+ {
+ g_free (edit_window_state_char);
+ g_free (edit_window_close_char);
+
+#ifdef HAVE_ASPELL
+ aspell_clean ();
+#endif
+ }
+
+ if (edit_dlg_init_refcounter != 0)
+ edit_dlg_init_refcounter--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show info about editor
+ */
+
+static void
+edit_about (void)
+{
+ char *ver;
+
+ ver = g_strdup_printf ("MCEdit %s", mc_global.mc_version);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (ver, NULL),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABEL (N_("A user friendly text editor\n"
+ "written for the Midnight Commander."), NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABEL (N_("Copyright (C) 1996-2023 the Free Software Foundation"), NULL),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("About"), "[Internal File Editor]",
+ quick_widgets, NULL, NULL
+ };
+
+ quick_widgets[0].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+ quick_widgets[2].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+ quick_widgets[4].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+
+ (void) quick_dialog (&qdlg);
+ }
+
+ g_free (ver);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show a help window
+ */
+
+static void
+edit_help (void)
+{
+ ev_help_t event_data = { NULL, "[Internal File Editor]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Restore saved window size.
+ *
+ * @param edit editor object
+ */
+
+static void
+edit_restore_size (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ w->mouse.forced_capture = FALSE;
+ widget_set_size_rect (w, &edit->loc_prev);
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move window by one row or column in any direction.
+ *
+ * @param edit editor object
+ * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
+ */
+
+static void
+edit_window_move (WEdit * edit, long command)
+{
+ Widget *we = WIDGET (edit);
+ Widget *wo = WIDGET (we->owner);
+ WRect *w = &we->rect;
+ const WRect *wh = &wo->rect;
+
+ switch (command)
+ {
+ case CK_Up:
+ if (w->y > wh->y + 1) /* menubar */
+ w->y--;
+ break;
+ case CK_Down:
+ if (w->y < wh->y + wh->lines - 2) /* buttonbar */
+ w->y++;
+ break;
+ case CK_Left:
+ if (w->x + wh->cols > wh->x)
+ w->x--;
+ break;
+ case CK_Right:
+ if (w->x < wh->x + wh->cols)
+ w->x++;
+ break;
+ default:
+ return;
+ }
+
+ edit->force |= REDRAW_PAGE;
+ widget_draw (wo);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Resize window by one row or column in any direction.
+ *
+ * @param edit editor object
+ * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
+ */
+
+static void
+edit_window_resize (WEdit * edit, long command)
+{
+ Widget *we = WIDGET (edit);
+ Widget *wo = WIDGET (we->owner);
+ WRect *w = &we->rect;
+ const WRect *wh = &wo->rect;
+
+ switch (command)
+ {
+ case CK_Up:
+ if (w->lines > WINDOW_MIN_LINES)
+ w->lines--;
+ break;
+ case CK_Down:
+ if (w->y + w->lines < wh->y + wh->lines - 1) /* buttonbar */
+ w->lines++;
+ break;
+ case CK_Left:
+ if (w->cols > WINDOW_MIN_COLS)
+ w->cols--;
+ break;
+ case CK_Right:
+ if (w->x + w->cols < wh->x + wh->cols)
+ w->cols++;
+ break;
+ default:
+ return;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+ widget_draw (wo);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get hotkey by number.
+ *
+ * @param n number
+ * @return hotkey
+ */
+
+static unsigned char
+get_hotkey (int n)
+{
+ return (n <= 9) ? '0' + n : 'a' + n - 10;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_window_list (const WDialog * h)
+{
+ const WGroup *g = CONST_GROUP (h);
+ const size_t offset = 2; /* skip menu and buttonbar */
+ const size_t dlg_num = g_list_length (g->widgets) - offset;
+ int lines, cols;
+ Listbox *listbox;
+ GList *w;
+ WEdit *selected;
+ int i = 0;
+
+ lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
+ cols = COLS * 2 / 3;
+
+ listbox = listbox_window_new (lines, cols, _("Open files"), "[Open files]");
+
+ for (w = g->widgets; w != NULL; w = g_list_next (w))
+ if (edit_widget_is_editor (CONST_WIDGET (w->data)))
+ {
+ WEdit *e = EDIT (w->data);
+ char *fname;
+
+ if (e->filename_vpath == NULL)
+ fname = g_strdup_printf ("%c [%s]", e->modified ? '*' : ' ', _("NoName"));
+ else
+ fname =
+ g_strdup_printf ("%c%s", e->modified ? '*' : ' ',
+ vfs_path_as_str (e->filename_vpath));
+
+ listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++),
+ str_term_trim (fname, WIDGET (listbox->list)->rect.cols - 2), e,
+ FALSE);
+ g_free (fname);
+ }
+
+ selected = listbox_run_with_data (listbox, g->current->data);
+ if (selected != NULL)
+ widget_select (WIDGET (selected));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_shortcut (long command)
+{
+ const char *ext_map;
+ const char *shortcut = NULL;
+
+ shortcut = keybind_lookup_keymap_shortcut (editor_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ ext_map = keybind_lookup_keymap_shortcut (editor_map, CK_ExtendedKeyMap);
+ if (ext_map != NULL)
+ shortcut = keybind_lookup_keymap_shortcut (editor_x_map, command);
+ if (shortcut != NULL)
+ return g_strdup_printf ("%s %s", ext_map, shortcut);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_title (const WDialog * h, size_t len)
+{
+ const WEdit *edit;
+ const char *modified;
+ const char *file_label;
+ char *filename;
+
+ edit = edit_find_editor (h);
+ modified = edit->modified ? "(*) " : " ";
+
+ len -= 4;
+
+ if (edit->filename_vpath == NULL)
+ filename = g_strdup (_("[NoName]"));
+ else
+ filename = g_strdup (vfs_path_as_str (edit->filename_vpath));
+
+ file_label = str_term_trim (filename, len - str_term_width1 (_("Edit: ")));
+ g_free (filename);
+
+ return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_dialog_command_execute (WDialog * h, long command)
+{
+ WGroup *g = GROUP (h);
+ cb_ret_t ret = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_EditNew:
+ edit_load_file_from_filename (h, NULL, 0);
+ break;
+ case CK_EditFile:
+ edit_load_cmd (h);
+ break;
+ case CK_History:
+ edit_load_file_from_history (h);
+ break;
+ case CK_EditSyntaxFile:
+ edit_load_syntax_file (h);
+ break;
+ case CK_EditUserMenu:
+ edit_load_menu_file (h);
+ break;
+ case CK_Close:
+ /* if there are no opened files anymore, close MC editor */
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)) &&
+ edit_close_cmd (EDIT (g->current->data)) && edit_find_editor (h) == NULL)
+ dlg_close (h);
+ break;
+ case CK_Help:
+ edit_help ();
+ /* edit->force |= REDRAW_COMPLETELY; */
+ break;
+ case CK_Menu:
+ edit_menu_cmd (h);
+ break;
+ case CK_Quit:
+ case CK_Cancel:
+ /* don't close editor due to SIGINT, but stop move/resize window */
+ {
+ Widget *w = WIDGET (g->current->data);
+
+ if (edit_widget_is_editor (w) && EDIT (w)->drag_state != MCEDIT_DRAG_NONE)
+ edit_restore_size (EDIT (w));
+ else if (command == CK_Quit)
+ dlg_close (h);
+ }
+ break;
+ case CK_About:
+ edit_about ();
+ break;
+ case CK_SyntaxOnOff:
+ edit_syntax_onoff_cmd (h);
+ break;
+ case CK_ShowTabTws:
+ edit_show_tabs_tws_cmd (h);
+ break;
+ case CK_ShowMargin:
+ edit_show_margin_cmd (h);
+ break;
+ case CK_ShowNumbers:
+ edit_show_numbers_cmd (h);
+ break;
+ case CK_Refresh:
+ edit_refresh_cmd ();
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_LearnKeys:
+ learn_keys ();
+ break;
+ case CK_WindowMove:
+ case CK_WindowResize:
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ edit_handle_move_resize (EDIT (g->current->data), command);
+ break;
+ case CK_WindowList:
+ edit_window_list (h);
+ break;
+ case CK_WindowNext:
+ group_select_next_widget (g);
+ break;
+ case CK_WindowPrev:
+ group_select_prev_widget (g);
+ break;
+ case CK_Options:
+ edit_options_dialog (h);
+ break;
+ case CK_OptionsSaveMode:
+ edit_save_mode_cmd ();
+ break;
+ case CK_SaveSetup:
+ save_setup_cmd ();
+ break;
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Translate the keycode into either 'command' or 'char_for_insertion'.
+ * 'command' is one of the editor commands from lib/keybind.h.
+ */
+
+static gboolean
+edit_translate_key (WEdit * edit, long x_key, int *cmd, int *ch)
+{
+ Widget *w = WIDGET (edit);
+ long command = CK_InsertChar;
+ int char_for_insertion = -1;
+
+ /* an ordinary insertable character */
+ if (!w->ext_mode && x_key < 256)
+ {
+#ifndef HAVE_CHARSET
+ if (is_printable (x_key))
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+#else
+ int c;
+
+ if (edit->charpoint >= MB_LEN_MAX)
+ {
+ edit->charpoint = 0;
+ edit->charbuf[edit->charpoint] = '\0';
+ }
+ if (edit->charpoint < MB_LEN_MAX)
+ {
+ edit->charbuf[edit->charpoint++] = x_key;
+ edit->charbuf[edit->charpoint] = '\0';
+ }
+
+ /* input from 8-bit locale */
+ if (!mc_global.utf8_display)
+ {
+ /* source is in 8-bit codeset */
+ c = convert_from_input_c (x_key);
+
+ if (is_printable (c))
+ {
+ if (!edit->utf8)
+ char_for_insertion = c;
+ else
+ char_for_insertion = convert_from_8bit_to_utf_c2 ((char) x_key);
+ goto fin;
+ }
+ }
+ else
+ {
+ /* UTF-8 locale */
+ int res;
+
+ res = str_is_valid_char (edit->charbuf, edit->charpoint);
+ if (res < 0 && res != -2)
+ {
+ edit->charpoint = 0; /* broken multibyte char, skip */
+ goto fin;
+ }
+
+ if (edit->utf8)
+ {
+ /* source is in UTF-8 codeset */
+ if (res < 0)
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+
+ edit->charbuf[edit->charpoint] = '\0';
+ edit->charpoint = 0;
+ if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+ }
+ else
+ {
+ /* 8-bit source */
+ if (res < 0)
+ {
+ /* not finished multibyte input (we're in the middle of multibyte utf-8 char) */
+ goto fin;
+ }
+
+ if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
+ {
+ c = convert_from_utf_to_current (edit->charbuf);
+ edit->charbuf[0] = '\0';
+ edit->charpoint = 0;
+ char_for_insertion = c;
+ goto fin;
+ }
+
+ /* non-printable utf-8 input, skip it */
+ edit->charbuf[0] = '\0';
+ edit->charpoint = 0;
+ }
+ }
+#endif /* HAVE_CHARSET */
+ }
+
+ /* Commands specific to the key emulation */
+ command = widget_lookup_key (w, x_key);
+ if (command == CK_IgnoreKey)
+ command = CK_InsertChar;
+
+ fin:
+ *cmd = (int) command; /* FIXME */
+ *ch = char_for_insertion;
+
+ return !(command == CK_InsertChar && char_for_insertion == -1);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_quit (WDialog * h)
+{
+ GList *l;
+ WEdit *e = NULL;
+ GSList *m = NULL;
+ GSList *me;
+
+ /* don't stop the dialog before final decision */
+ widget_set_state (WIDGET (h), WST_ACTIVE, TRUE);
+
+ /* check window state and get modified files */
+ for (l = GROUP (h)->widgets; l != NULL; l = g_list_next (l))
+ if (edit_widget_is_editor (CONST_WIDGET (l->data)))
+ {
+ e = EDIT (l->data);
+
+ if (e->drag_state != MCEDIT_DRAG_NONE)
+ {
+ edit_restore_size (e);
+ g_slist_free (m);
+ return;
+ }
+
+ /* create separate list because widget_select()
+ changes the window position in Z order */
+ if (e->modified)
+ m = g_slist_prepend (m, l->data);
+ }
+
+ for (me = m; me != NULL; me = g_slist_next (me))
+ {
+ e = EDIT (me->data);
+
+ widget_select (WIDGET (e));
+
+ if (!edit_ok_to_exit (e))
+ break;
+ }
+
+ /* if all files were checked, quit editor */
+ if (me == NULL)
+ dlg_close (h);
+
+ g_slist_free (m);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_set_buttonbar (WEdit * edit, WButtonBar * bb)
+{
+ Widget *w = WIDGET (edit);
+
+ buttonbar_set_label (bb, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL);
+ buttonbar_set_label (bb, 2, Q_ ("ButtonBar|Save"), w->keymap, w);
+ buttonbar_set_label (bb, 3, Q_ ("ButtonBar|Mark"), w->keymap, w);
+ buttonbar_set_label (bb, 4, Q_ ("ButtonBar|Replac"), w->keymap, w);
+ buttonbar_set_label (bb, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
+ buttonbar_set_label (bb, 6, Q_ ("ButtonBar|Move"), w->keymap, w);
+ buttonbar_set_label (bb, 7, Q_ ("ButtonBar|Search"), w->keymap, w);
+ buttonbar_set_label (bb, 8, Q_ ("ButtonBar|Delete"), w->keymap, w);
+ buttonbar_set_label (bb, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL);
+ buttonbar_set_label (bb, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_total_update (WEdit * edit)
+{
+ edit_find_bracket (edit);
+ edit->force |= REDRAW_COMPLETELY;
+ edit_update_curs_row (edit);
+ edit_update_screen (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_update_cursor (WEdit * edit, const mouse_event_t * event)
+{
+ int x, y;
+ gboolean done;
+
+ x = event->x - (edit->fullscreen ? 0 : 1);
+ y = event->y - (edit->fullscreen ? 0 : 1);
+
+ if (edit->mark2 != -1 && event->msg == MSG_MOUSE_UP)
+ return TRUE; /* don't do anything */
+
+ if (event->msg == MSG_MOUSE_DOWN || event->msg == MSG_MOUSE_UP)
+ edit_push_key_press (edit);
+
+ if (!edit_options.cursor_beyond_eol)
+ edit->prev_col = x - edit->start_col - edit_options.line_state_width;
+ else
+ {
+ long line_len;
+
+ line_len =
+ edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+
+ if (x > line_len - 1)
+ {
+ edit->over_col = x - line_len - edit->start_col - edit_options.line_state_width;
+ edit->prev_col = line_len;
+ }
+ else
+ {
+ edit->over_col = 0;
+ edit->prev_col = x - edit_options.line_state_width - edit->start_col;
+ }
+ }
+
+ if (y > edit->curs_row)
+ edit_move_down (edit, y - edit->curs_row, FALSE);
+ else if (y < edit->curs_row)
+ edit_move_up (edit, edit->curs_row - y, FALSE);
+ else
+ edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
+
+ if (event->msg == MSG_MOUSE_CLICK)
+ {
+ edit_mark_cmd (edit, TRUE); /* reset */
+ edit->highlight = 0;
+ }
+
+ done = (event->msg != MSG_MOUSE_DRAG);
+ if (done)
+ edit_mark_cmd (edit, FALSE);
+
+ return done;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Callback for the edit dialog */
+
+static cb_ret_t
+edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ edit_dlg_init ();
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL);
+ menubar_arrange (menubar_find (h));
+ return MSG_HANDLED;
+
+ case MSG_ACTION:
+ {
+ /* Handle shortcuts, menu, and buttonbar. */
+
+ cb_ret_t result;
+
+ result = edit_dialog_command_execute (h, parm);
+
+ /* We forward any commands coming from the menu, and which haven't been
+ handled by the dialog, to the focused WEdit window. */
+ if (result == MSG_NOT_HANDLED && sender == WIDGET (menubar_find (h)))
+ result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL);
+
+ return result;
+ }
+
+ case MSG_KEY:
+ {
+ Widget *we = WIDGET (g->current->data);
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ if (edit_widget_is_editor (we))
+ {
+ gboolean ext_mode;
+ long command;
+
+ /* keep and then extmod flag */
+ ext_mode = we->ext_mode;
+ command = widget_lookup_key (we, parm);
+ we->ext_mode = ext_mode;
+
+ if (command == CK_IgnoreKey)
+ we->ext_mode = FALSE;
+ else
+ {
+ ret = edit_dialog_command_execute (h, command);
+ /* if command was not handled, keep the extended mode
+ for the further key processing */
+ if (ret == MSG_HANDLED)
+ we->ext_mode = FALSE;
+ }
+ }
+
+ /*
+ * Due to the "end of bracket" escape the editor sees input with is_idle() == false
+ * (expects more characters) and hence doesn't yet refresh the screen, but then
+ * no further characters arrive (there's only an "end of bracket" which is swallowed
+ * by tty_get_event()), so you end up with a screen that's not refreshed after pasting.
+ * So let's trigger an IDLE signal.
+ */
+ if (!is_idle ())
+ widget_idle (w, TRUE);
+ return ret;
+ }
+
+ /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */
+ case MSG_UNHANDLED_KEY:
+ return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case MSG_VALIDATE:
+ edit_quit (h);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ edit_dlg_deinit ();
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ widget_idle (w, FALSE);
+ return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL);
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle mouse events of editor screen.
+ *
+ * @param w Widget object (the editor)
+ * @param msg mouse event message
+ * @param event mouse event data
+ */
+static void
+edit_dialog_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ gboolean unhandled = TRUE;
+
+ if (msg == MSG_MOUSE_DOWN && event->y == 0)
+ {
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+ WMenuBar *b;
+
+ b = menubar_find (h);
+
+ if (!widget_get_state (WIDGET (b), WST_FOCUSED))
+ {
+ /* menubar */
+
+ GList *l;
+ GList *top = NULL;
+ int x;
+
+ /* Try find top fullscreen window */
+ for (l = g->widgets; l != NULL; l = g_list_next (l))
+ if (edit_widget_is_editor (CONST_WIDGET (l->data)) && EDIT (l->data)->fullscreen)
+ top = l;
+
+ /* Handle fullscreen/close buttons in the top line */
+ x = w->rect.cols - 6;
+
+ if (top != NULL && event->x >= x)
+ {
+ WEdit *e = EDIT (top->data);
+
+ if (top != g->current)
+ {
+ /* Window is not active. Activate it */
+ widget_select (WIDGET (e));
+ }
+
+ /* Handle buttons */
+ if (event->x - x <= 2)
+ edit_toggle_fullscreen (e);
+ else
+ send_message (h, NULL, MSG_ACTION, CK_Close, NULL);
+
+ unhandled = FALSE;
+ }
+
+ if (unhandled)
+ menubar_activate (b, drop_menus, -1);
+ }
+ }
+
+ /* Continue handling of unhandled event in window or menu */
+ event->result.abort = unhandled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_dialog_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ w->rect = WIDGET (w->owner)->rect;
+ rect_grow (&w->rect, -1, 0);
+ w->pos_flags |= WPOS_KEEP_ALL;
+ return MSG_HANDLED;
+
+ default:
+ return background_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WEdit *e = EDIT (w);
+
+ switch (msg)
+ {
+ case MSG_FOCUS:
+ edit_set_buttonbar (e, buttonbar_find (DIALOG (w->owner)));
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ e->force |= REDRAW_COMPLETELY;
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ {
+ int cmd, ch;
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ /* The user may override the access-keys for the menu bar. */
+ if (macro_index == -1 && edit_execute_macro (e, parm))
+ {
+ edit_update_screen (e);
+ ret = MSG_HANDLED;
+ }
+ else if (edit_translate_key (e, parm, &cmd, &ch))
+ {
+ edit_execute_key_command (e, cmd, ch);
+ edit_update_screen (e);
+ ret = MSG_HANDLED;
+ }
+
+ return ret;
+ }
+
+ case MSG_ACTION:
+ /* command from menubar or buttonbar */
+ edit_execute_key_command (e, parm, -1);
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ {
+ int y, x;
+
+ y = (e->fullscreen ? 0 : 1) + EDIT_TEXT_VERTICAL_OFFSET + e->curs_row;
+ x = (e->fullscreen ? 0 : 1) + EDIT_TEXT_HORIZONTAL_OFFSET +
+ edit_options.line_state_width + e->curs_col + e->start_col + e->over_col;
+
+ widget_gotoyx (w, y, x);
+ return MSG_HANDLED;
+ }
+
+ case MSG_IDLE:
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ edit_clean (e);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle move/resize mouse events.
+ */
+static void
+edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WEdit *edit = EDIT (w);
+ WRect *r = &w->rect;
+ const WRect *h = &CONST_WIDGET (w->owner)->rect;
+ int global_x, global_y;
+
+ if (msg == MSG_MOUSE_UP)
+ {
+ /* Exit move/resize mode. */
+ edit_execute_cmd (edit, CK_Enter, -1);
+ edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
+ return;
+ }
+
+ if (msg != MSG_MOUSE_DRAG)
+ /**
+ * We ignore any other events. Specifically, MSG_MOUSE_DOWN.
+ *
+ * When the move/resize is initiated by the menu, we let the user
+ * stop it by clicking with the mouse. Which is why we don't want
+ * a mouse down to affect the window.
+ */
+ return;
+
+ /* Convert point to global coordinates for easier calculations. */
+ global_x = event->x + r->x;
+ global_y = event->y + r->y;
+
+ /* Clamp the point to the dialog's client area. */
+ global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */
+ global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */
+
+ if (edit->drag_state == MCEDIT_DRAG_MOVE)
+ {
+ r->y = global_y;
+ r->x = global_x - edit->drag_state_start;
+ }
+ else if (edit->drag_state == MCEDIT_DRAG_RESIZE)
+ {
+ r->lines = MAX (WINDOW_MIN_LINES, global_y - r->y + 1);
+ r->cols = MAX (WINDOW_MIN_COLS, global_x - r->x + 1);
+ }
+
+ edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */
+
+ /* We draw the whole dialog because dragging/resizing exposes area beneath. */
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle mouse events of editor window
+ *
+ * @param w Widget object (the editor window)
+ * @param msg mouse event message
+ * @param event mouse event data
+ */
+static void
+edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WEdit *edit = EDIT (w);
+ /* buttons' distance from right edge */
+ int dx = edit->fullscreen ? 0 : 2;
+ /* location of 'Close' and 'Toggle fullscreen' pictograms */
+ int close_x, toggle_fullscreen_x;
+
+ close_x = (w->rect.cols - 1) - dx - 1;
+ toggle_fullscreen_x = close_x - 3;
+
+ if (edit->drag_state != MCEDIT_DRAG_NONE)
+ {
+ /* window is being resized/moved */
+ edit_mouse_handle_move_resize (w, msg, event);
+ return;
+ }
+
+ /* If it's the last line on the screen, we abort the event to make the
+ * system channel it to the overlapping buttonbar instead. We have to do
+ * this because a WEdit has the WOP_TOP_SELECT flag, which makes it above
+ * the buttonbar in Z-order. */
+ if (msg == MSG_MOUSE_DOWN && (event->y + w->rect.y == LINES - 1))
+ {
+ event->result.abort = TRUE;
+ return;
+ }
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ edit_update_curs_row (edit);
+ edit_update_curs_col (edit);
+
+ if (!edit->fullscreen)
+ {
+ if (event->y == 0)
+ {
+ if (event->x >= close_x - 1 && event->x <= close_x + 1)
+ ; /* do nothing (see MSG_MOUSE_CLICK) */
+ else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
+ ; /* do nothing (see MSG_MOUSE_CLICK) */
+ else
+ {
+ /* start window move */
+ edit_execute_cmd (edit, CK_WindowMove, -1);
+ edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
+ edit->drag_state_start = event->x;
+ }
+ break;
+ }
+
+ if (event->y == w->rect.lines - 1 && event->x == w->rect.cols - 1)
+ {
+ /* bottom-right corner -- start window resize */
+ edit_execute_cmd (edit, CK_WindowResize, -1);
+ break;
+ }
+ }
+
+ MC_FALLTHROUGH; /* to start/stop text selection */
+
+ case MSG_MOUSE_UP:
+ edit_update_cursor (edit, event);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ if (event->y == 0)
+ {
+ if (event->x >= close_x - 1 && event->x <= close_x + 1)
+ send_message (w->owner, NULL, MSG_ACTION, CK_Close, NULL);
+ else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
+ edit_toggle_fullscreen (edit);
+ else if (!edit->fullscreen && event->count == GPM_DOUBLE)
+ /* double click on top line (toggle fullscreen) */
+ edit_toggle_fullscreen (edit);
+ }
+ else if (event->count == GPM_DOUBLE)
+ {
+ /* double click */
+ edit_mark_current_word_cmd (edit);
+ edit_total_update (edit);
+ }
+ else if (event->count == GPM_TRIPLE)
+ {
+ /* triple click: works in GPM only, not in xterm */
+ edit_mark_current_line_cmd (edit);
+ edit_total_update (edit);
+ }
+ break;
+
+ case MSG_MOUSE_DRAG:
+ edit_update_cursor (edit, event);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ edit_move_up (edit, 2, TRUE);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ edit_move_down (edit, 2, TRUE);
+ edit_total_update (edit);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Edit one file.
+ *
+ * @param file_vpath file object
+ * @param line line number
+ * @return TRUE if no errors was occurred, FALSE otherwise
+ */
+
+gboolean
+edit_file (const vfs_path_t * file_vpath, long line)
+{
+ mcedit_arg_t arg = { (vfs_path_t *) file_vpath, line };
+ GList *files;
+ gboolean ok;
+
+ files = g_list_prepend (NULL, &arg);
+ ok = edit_files (files);
+ g_list_free (files);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_files (const GList * files)
+{
+ static gboolean made_directory = FALSE;
+ WDialog *edit_dlg;
+ WGroup *g;
+ WMenuBar *menubar;
+ Widget *w, *wd;
+ const GList *file;
+ gboolean ok = FALSE;
+
+ if (!made_directory)
+ {
+ char *dir;
+
+ dir = mc_build_filename (mc_config_get_cache_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+
+ dir = mc_build_filename (mc_config_get_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+
+ dir = mc_build_filename (mc_config_get_data_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+ }
+
+ /* Create a new dialog and add it widgets to it */
+ edit_dlg =
+ dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, edit_dialog_callback,
+ edit_dialog_mouse_callback, "[Internal File Editor]", NULL);
+ wd = WIDGET (edit_dlg);
+ widget_want_tab (wd, TRUE);
+ wd->keymap = editor_map;
+ wd->ext_keymap = editor_x_map;
+
+ edit_dlg->get_shortcut = edit_get_shortcut;
+ edit_dlg->get_title = edit_get_title;
+
+ g = GROUP (edit_dlg);
+
+ edit_dlg->bg =
+ WIDGET (background_new
+ (1, 0, wd->rect.lines - 2, wd->rect.cols, EDITOR_BACKGROUND, ' ',
+ edit_dialog_bg_callback));
+ group_add_widget (g, edit_dlg->bg);
+
+ menubar = menubar_new (NULL);
+ w = WIDGET (menubar);
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+ edit_init_menu (menubar);
+
+ w = WIDGET (buttonbar_new ());
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+
+ for (file = files; file != NULL; file = g_list_next (file))
+ {
+ mcedit_arg_t *f = (mcedit_arg_t *) file->data;
+ gboolean f_ok;
+
+ f_ok = edit_load_file_from_filename (edit_dlg, f->file_vpath, f->line_number);
+ /* at least one file has been opened succefully */
+ ok = ok || f_ok;
+ }
+
+ if (ok)
+ dlg_run (edit_dlg);
+
+ if (!ok || widget_get_state (wd, WST_CLOSED))
+ widget_destroy (wd);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+edit_get_file_name (const WEdit * edit)
+{
+ return vfs_path_as_str (edit->filename_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WEdit *
+edit_find_editor (const WDialog * h)
+{
+ const WGroup *g = CONST_GROUP (h);
+
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ return EDIT (g->current->data);
+ return EDIT (widget_find_by_type (CONST_WIDGET (h), edit_callback));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if widget is an WEdit class.
+ *
+ * @param w probably editor object
+ * @return TRUE if widget is an WEdit class, FALSE otherwise
+ */
+
+gboolean
+edit_widget_is_editor (const Widget * w)
+{
+ return (w != NULL && w->callback == edit_callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_screen (WEdit * e)
+{
+ edit_scroll_screen_over_cursor (e);
+ edit_update_curs_col (e);
+ edit_status (e, widget_get_state (WIDGET (e), WST_FOCUSED));
+
+ /* pop all events for this window for internal handling */
+ if (!is_idle ())
+ e->force |= REDRAW_PAGE;
+ else
+ {
+ if ((e->force & REDRAW_COMPLETELY) != 0)
+ e->force |= REDRAW_PAGE;
+ edit_render_keypress (e);
+ }
+
+ widget_draw (WIDGET (buttonbar_find (DIALOG (WIDGET (e)->owner))));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Save current window size.
+ *
+ * @param edit editor object
+ */
+
+void
+edit_save_size (WEdit * edit)
+{
+ edit->loc_prev = WIDGET (edit)->rect;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create new editor window and insert it into editor screen.
+ *
+ * @param h editor dialog (screen)
+ * @param y y coordinate
+ * @param x x coordinate
+ * @param lines window height
+ * @param cols window width
+ * @param f file object
+ * @param fline line number in file
+ * @return TRUE if new window was successfully created and inserted into editor screen,
+ * FALSE otherwise
+ */
+
+gboolean
+edit_add_window (WDialog * h, const WRect * r, const vfs_path_t * f, long fline)
+{
+ WEdit *edit;
+ Widget *w;
+
+ edit = edit_init (NULL, r, f, fline);
+ if (edit == NULL)
+ return FALSE;
+
+ w = WIDGET (edit);
+ w->callback = edit_callback;
+ w->mouse_callback = edit_mouse_callback;
+
+ group_add_widget_autopos (GROUP (h), w, WPOS_KEEP_ALL, NULL);
+ edit_set_buttonbar (edit, buttonbar_find (h));
+ widget_draw (WIDGET (h));
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Handle move/resize events.
+ *
+ * @param edit editor object
+ * @param command action id
+ * @return TRUE if the action was handled, FALSE otherwise
+ */
+
+gboolean
+edit_handle_move_resize (WEdit * edit, long command)
+{
+ Widget *w = WIDGET (edit);
+ gboolean ret = FALSE;
+
+ if (edit->fullscreen)
+ {
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ w->mouse.forced_capture = FALSE;
+ return ret;
+ }
+
+ switch (edit->drag_state)
+ {
+ case MCEDIT_DRAG_NONE:
+ /* possible start move/resize */
+ switch (command)
+ {
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_MOVE;
+ edit_save_size (edit);
+ edit_status (edit, TRUE); /* redraw frame and status */
+ /**
+ * If a user initiates a move by the menu, not by the mouse, we
+ * make a subsequent mouse drag pull the frame from its middle.
+ * (We can instead choose '0' to pull it from the corner.)
+ */
+ edit->drag_state_start = w->rect.cols / 2;
+ ret = TRUE;
+ break;
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_RESIZE;
+ edit_save_size (edit);
+ edit_status (edit, TRUE); /* redraw frame and status */
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case MCEDIT_DRAG_MOVE:
+ switch (command)
+ {
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_RESIZE;
+ ret = TRUE;
+ break;
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ edit_window_move (edit, command);
+ ret = TRUE;
+ break;
+ case CK_Enter:
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ edit_status (edit, TRUE); /* redraw frame and status */
+ MC_FALLTHROUGH;
+ default:
+ ret = TRUE;
+ break;
+ }
+ break;
+
+ case MCEDIT_DRAG_RESIZE:
+ switch (command)
+ {
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_MOVE;
+ ret = TRUE;
+ break;
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ edit_window_resize (edit, command);
+ ret = TRUE;
+ break;
+ case CK_Enter:
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ edit_status (edit, TRUE); /* redraw frame and status */
+ MC_FALLTHROUGH;
+ default:
+ ret = TRUE;
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /**
+ * - We let the user stop a resize/move operation by clicking with the
+ * mouse anywhere. ("clicking" = pressing and releasing a button.)
+ * - We let the user perform a resize/move operation by a mouse drag
+ * initiated anywhere.
+ *
+ * "Anywhere" means: inside or outside the window. We make this happen
+ * with the 'forced_capture' flag.
+ */
+ w->mouse.forced_capture = (edit->drag_state != MCEDIT_DRAG_NONE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle window fuulscreen mode.
+ *
+ * @param edit editor object
+ */
+
+void
+edit_toggle_fullscreen (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+
+ edit->fullscreen = !edit->fullscreen;
+ edit->force = REDRAW_COMPLETELY;
+
+ if (!edit->fullscreen)
+ {
+ edit_restore_size (edit);
+ /* do not follow screen size on resize */
+ w->pos_flags = WPOS_KEEP_DEFAULT;
+ }
+ else
+ {
+ WRect r;
+
+ edit_save_size (edit);
+ r = WIDGET (w->owner)->rect;
+ rect_grow (&r, -1, 0);
+ widget_set_size_rect (w, &r);
+ /* follow screen size on resize */
+ w->pos_flags = WPOS_KEEP_ALL;
+ edit->force |= REDRAW_PAGE;
+ edit_update_screen (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */