Merge branch '3205_eta'
[midnight-commander.git] / src / editor / editwidget.c
blob817ea944daa0e44e9779d2a5e00418fde5dfd1bb
1 /*
2 Editor initialisation and callback handler.
4 Copyright (C) 1996-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Paul Sheer, 1996, 1997
9 Andrew Borodin <aborodin@vmail.ru> 2012-2024
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(), bracketed_pasting_in_progress */
46 #include "lib/tty/color.h" /* tty_setcolor() */
47 #include "lib/skin.h"
48 #include "lib/fileloc.h" /* EDIT_HOME_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/keymap.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() */
64 #include "edit-impl.h"
65 #include "editwidget.h"
66 #include "editmacros.h" /* edit_execute_macro() */
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 /*** forward declarations (file scope functions) *************************************************/
85 /*** file scope variables ************************************************************************/
87 static unsigned int edit_dlg_init_refcounter = 0;
89 /* --------------------------------------------------------------------------------------------- */
90 /*** file scope functions ************************************************************************/
91 /* --------------------------------------------------------------------------------------------- */
92 /**
93 * Init the 'edit' subsystem
96 static void
97 edit_dlg_init (void)
99 edit_dlg_init_refcounter++;
101 if (edit_dlg_init_refcounter == 1)
103 edit_window_state_char = mc_skin_get ("widget-editor", "window-state-char", "*");
104 edit_window_close_char = mc_skin_get ("widget-editor", "window-close-char", "X");
106 #ifdef HAVE_ASPELL
107 aspell_init ();
108 #endif
112 /* --------------------------------------------------------------------------------------------- */
114 * Deinit the 'edit' subsystem
117 static void
118 edit_dlg_deinit (void)
120 if (edit_dlg_init_refcounter == 1)
122 g_free (edit_window_state_char);
123 g_free (edit_window_close_char);
125 #ifdef HAVE_ASPELL
126 aspell_clean ();
127 #endif
130 if (edit_dlg_init_refcounter != 0)
131 edit_dlg_init_refcounter--;
134 /* --------------------------------------------------------------------------------------------- */
136 * Show info about editor
139 static void
140 edit_about (void)
142 char *ver;
144 ver = g_strdup_printf ("MCEdit %s", mc_global.mc_version);
147 quick_widget_t quick_widgets[] = {
148 /* *INDENT-OFF* */
149 QUICK_LABEL (ver, NULL),
150 QUICK_SEPARATOR (TRUE),
151 QUICK_LABEL (N_("A user friendly text editor\n"
152 "written for the Midnight Commander."), NULL),
153 QUICK_SEPARATOR (FALSE),
154 QUICK_LABEL (N_("Copyright (C) 1996-2024 the Free Software Foundation"), NULL),
155 QUICK_START_BUTTONS (TRUE, TRUE),
156 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
157 QUICK_END
158 /* *INDENT-ON* */
161 WRect r = { -1, -1, 0, 40 };
163 quick_dialog_t qdlg = {
164 r, N_("About"), "[Internal File Editor]",
165 quick_widgets, NULL, NULL
168 quick_widgets[0].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
169 quick_widgets[2].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
170 quick_widgets[4].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
172 (void) quick_dialog (&qdlg);
175 g_free (ver);
178 /* --------------------------------------------------------------------------------------------- */
180 * Show a help window
183 static void
184 edit_help (const WDialog *h)
186 ev_help_t event_data = { NULL, h->help_ctx };
188 mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
191 /* --------------------------------------------------------------------------------------------- */
193 * Restore saved window size.
195 * @param edit editor object
198 static void
199 edit_restore_size (WEdit *edit)
201 Widget *w = WIDGET (edit);
203 edit->drag_state = MCEDIT_DRAG_NONE;
204 w->mouse.forced_capture = FALSE;
205 widget_set_size_rect (w, &edit->loc_prev);
206 widget_draw (WIDGET (w->owner));
209 /* --------------------------------------------------------------------------------------------- */
211 * Move window by one row or column in any direction.
213 * @param edit editor object
214 * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
217 static void
218 edit_window_move (WEdit *edit, long command)
220 Widget *we = WIDGET (edit);
221 Widget *wo = WIDGET (we->owner);
222 WRect *w = &we->rect;
223 const WRect *wh = &wo->rect;
225 switch (command)
227 case CK_Up:
228 if (w->y > wh->y + 1) /* menubar */
229 w->y--;
230 break;
231 case CK_Down:
232 if (w->y < wh->y + wh->lines - 2) /* buttonbar */
233 w->y++;
234 break;
235 case CK_Left:
236 if (w->x + wh->cols > wh->x)
237 w->x--;
238 break;
239 case CK_Right:
240 if (w->x < wh->x + wh->cols)
241 w->x++;
242 break;
243 default:
244 return;
247 edit->force |= REDRAW_PAGE;
248 widget_draw (wo);
251 /* --------------------------------------------------------------------------------------------- */
253 * Resize window by one row or column in any direction.
255 * @param edit editor object
256 * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
259 static void
260 edit_window_resize (WEdit *edit, long command)
262 Widget *we = WIDGET (edit);
263 Widget *wo = WIDGET (we->owner);
264 WRect *w = &we->rect;
265 const WRect *wh = &wo->rect;
267 switch (command)
269 case CK_Up:
270 if (w->lines > WINDOW_MIN_LINES)
271 w->lines--;
272 break;
273 case CK_Down:
274 if (w->y + w->lines < wh->y + wh->lines - 1) /* buttonbar */
275 w->lines++;
276 break;
277 case CK_Left:
278 if (w->cols > WINDOW_MIN_COLS)
279 w->cols--;
280 break;
281 case CK_Right:
282 if (w->x + w->cols < wh->x + wh->cols)
283 w->cols++;
284 break;
285 default:
286 return;
289 edit->force |= REDRAW_COMPLETELY;
290 widget_draw (wo);
293 /* --------------------------------------------------------------------------------------------- */
295 * Get hotkey by number.
297 * @param n number
298 * @return hotkey
301 static unsigned char
302 get_hotkey (int n)
304 return (n <= 9) ? '0' + n : 'a' + n - 10;
307 /* --------------------------------------------------------------------------------------------- */
309 static void
310 edit_window_list (const WDialog *h)
312 const WGroup *g = CONST_GROUP (h);
313 const size_t offset = 2; /* skip menu and buttonbar */
314 const size_t dlg_num = g_list_length (g->widgets) - offset;
315 int lines, cols;
316 Listbox *listbox;
317 GList *w;
318 WEdit *selected;
319 int i = 0;
321 lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
322 cols = COLS * 2 / 3;
324 listbox = listbox_window_new (lines, cols, _("Open files"), "[Open files]");
326 for (w = g->widgets; w != NULL; w = g_list_next (w))
327 if (edit_widget_is_editor (CONST_WIDGET (w->data)))
329 WEdit *e = EDIT (w->data);
330 char *fname;
332 if (e->filename_vpath == NULL)
333 fname = g_strdup_printf ("%c [%s]", e->modified ? '*' : ' ', _("NoName"));
334 else
335 fname =
336 g_strdup_printf ("%c%s", e->modified ? '*' : ' ',
337 vfs_path_as_str (e->filename_vpath));
339 listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++),
340 str_term_trim (fname, WIDGET (listbox->list)->rect.cols - 2), e,
341 FALSE);
342 g_free (fname);
345 selected = listbox_run_with_data (listbox, g->current->data);
346 if (selected != NULL)
347 widget_select (WIDGET (selected));
350 /* --------------------------------------------------------------------------------------------- */
352 static char *
353 edit_get_shortcut (long command)
355 const char *ext_map;
356 const char *shortcut = NULL;
358 shortcut = keybind_lookup_keymap_shortcut (editor_map, command);
359 if (shortcut != NULL)
360 return g_strdup (shortcut);
362 ext_map = keybind_lookup_keymap_shortcut (editor_map, CK_ExtendedKeyMap);
363 if (ext_map != NULL)
364 shortcut = keybind_lookup_keymap_shortcut (editor_x_map, command);
365 if (shortcut != NULL)
366 return g_strdup_printf ("%s %s", ext_map, shortcut);
368 return NULL;
371 /* --------------------------------------------------------------------------------------------- */
373 static char *
374 edit_get_title (const WDialog *h, size_t len)
376 const WEdit *edit;
377 const char *modified;
378 const char *file_label;
379 char *filename;
381 edit = edit_find_editor (h);
382 modified = edit->modified ? "(*) " : " ";
384 len -= 4;
386 if (edit->filename_vpath == NULL)
387 filename = g_strdup (_("[NoName]"));
388 else
389 filename = g_strdup (vfs_path_as_str (edit->filename_vpath));
391 file_label = str_term_trim (filename, len - str_term_width1 (_("Edit: ")));
392 g_free (filename);
394 return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL);
397 /* --------------------------------------------------------------------------------------------- */
399 static cb_ret_t
400 edit_dialog_command_execute (WDialog *h, long command)
402 WGroup *g = GROUP (h);
403 cb_ret_t ret = MSG_HANDLED;
405 switch (command)
407 case CK_EditNew:
408 edit_load_file_from_filename (h, NULL);
409 break;
410 case CK_EditFile:
411 edit_load_cmd (h);
412 break;
413 case CK_History:
414 edit_load_file_from_history (h);
415 break;
416 case CK_EditSyntaxFile:
417 edit_load_syntax_file (h);
418 break;
419 case CK_EditUserMenu:
420 edit_load_menu_file (h);
421 break;
422 case CK_Close:
423 /* if there are no opened files anymore, close MC editor */
424 if (edit_widget_is_editor (CONST_WIDGET (g->current->data)) &&
425 edit_close_cmd (EDIT (g->current->data)) && edit_find_editor (h) == NULL)
426 dlg_close (h);
427 break;
428 case CK_Help:
429 edit_help (h);
430 break;
431 case CK_Menu:
432 edit_menu_cmd (h);
433 break;
434 case CK_Quit:
435 case CK_Cancel:
436 /* don't close editor due to SIGINT, but stop move/resize window */
438 Widget *w = WIDGET (g->current->data);
440 if (edit_widget_is_editor (w) && EDIT (w)->drag_state != MCEDIT_DRAG_NONE)
441 edit_restore_size (EDIT (w));
442 else if (command == CK_Quit)
443 dlg_close (h);
445 break;
446 case CK_About:
447 edit_about ();
448 break;
449 case CK_SyntaxOnOff:
450 edit_syntax_onoff_cmd (h);
451 break;
452 case CK_ShowTabTws:
453 edit_show_tabs_tws_cmd (h);
454 break;
455 case CK_ShowMargin:
456 edit_show_margin_cmd (h);
457 break;
458 case CK_ShowNumbers:
459 edit_show_numbers_cmd (h);
460 break;
461 case CK_Refresh:
462 edit_refresh_cmd ();
463 break;
464 case CK_Shell:
465 toggle_subshell ();
466 break;
467 case CK_LearnKeys:
468 learn_keys ();
469 break;
470 case CK_WindowMove:
471 case CK_WindowResize:
472 if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
473 edit_handle_move_resize (EDIT (g->current->data), command);
474 break;
475 case CK_WindowList:
476 edit_window_list (h);
477 break;
478 case CK_WindowNext:
479 group_select_next_widget (g);
480 break;
481 case CK_WindowPrev:
482 group_select_prev_widget (g);
483 break;
484 case CK_Options:
485 edit_options_dialog (h);
486 break;
487 case CK_OptionsSaveMode:
488 edit_save_mode_cmd ();
489 break;
490 case CK_SaveSetup:
491 save_setup_cmd ();
492 break;
493 default:
494 ret = MSG_NOT_HANDLED;
495 break;
498 return ret;
501 /* --------------------------------------------------------------------------------------------- */
503 * Translate the keycode into either 'command' or 'char_for_insertion'.
504 * 'command' is one of the editor commands from lib/keybind.h.
507 static gboolean
508 edit_translate_key (WEdit *edit, long x_key, int *cmd, int *ch)
510 Widget *w = WIDGET (edit);
511 long command = CK_InsertChar;
512 int char_for_insertion = -1;
514 /* an ordinary insertable character */
515 if (!w->ext_mode && x_key < 256)
517 #ifndef HAVE_CHARSET
518 if (is_printable (x_key))
520 char_for_insertion = x_key;
521 goto fin;
523 #else
524 int c;
526 if (edit->charpoint >= MB_LEN_MAX)
528 edit->charpoint = 0;
529 edit->charbuf[edit->charpoint] = '\0';
531 if (edit->charpoint < MB_LEN_MAX)
533 edit->charbuf[edit->charpoint++] = x_key;
534 edit->charbuf[edit->charpoint] = '\0';
537 /* input from 8-bit locale */
538 if (!mc_global.utf8_display)
540 /* source is in 8-bit codeset */
541 c = convert_from_input_c (x_key);
543 if (is_printable (c))
545 if (!edit->utf8)
546 char_for_insertion = c;
547 else
548 char_for_insertion = convert_from_8bit_to_utf_c2 ((char) x_key);
549 goto fin;
552 else
554 /* UTF-8 locale */
555 int res;
557 res = str_is_valid_char (edit->charbuf, edit->charpoint);
558 if (res < 0 && res != -2)
560 edit->charpoint = 0; /* broken multibyte char, skip */
561 goto fin;
564 if (edit->utf8)
566 /* source is in UTF-8 codeset */
567 if (res < 0)
569 char_for_insertion = x_key;
570 goto fin;
573 edit->charbuf[edit->charpoint] = '\0';
574 edit->charpoint = 0;
575 if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
577 char_for_insertion = x_key;
578 goto fin;
581 else
583 /* 8-bit source */
584 if (res < 0)
586 /* not finished multibyte input (we're in the middle of multibyte utf-8 char) */
587 goto fin;
590 if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
592 c = convert_from_utf_to_current (edit->charbuf);
593 edit->charbuf[0] = '\0';
594 edit->charpoint = 0;
595 char_for_insertion = c;
596 goto fin;
599 /* non-printable utf-8 input, skip it */
600 edit->charbuf[0] = '\0';
601 edit->charpoint = 0;
604 #endif /* HAVE_CHARSET */
607 /* Commands specific to the key emulation */
608 command = widget_lookup_key (w, x_key);
609 if (command == CK_IgnoreKey)
610 command = CK_InsertChar;
612 fin:
613 *cmd = (int) command; /* FIXME */
614 *ch = char_for_insertion;
616 return !(command == CK_InsertChar && char_for_insertion == -1);
620 /* --------------------------------------------------------------------------------------------- */
622 static inline void
623 edit_quit (WDialog *h)
625 GList *l;
626 WEdit *e = NULL;
627 GSList *m = NULL;
628 GSList *me;
630 /* don't stop the dialog before final decision */
631 widget_set_state (WIDGET (h), WST_ACTIVE, TRUE);
633 /* check window state and get modified files */
634 for (l = GROUP (h)->widgets; l != NULL; l = g_list_next (l))
635 if (edit_widget_is_editor (CONST_WIDGET (l->data)))
637 e = EDIT (l->data);
639 if (e->drag_state != MCEDIT_DRAG_NONE)
641 edit_restore_size (e);
642 g_slist_free (m);
643 return;
646 /* create separate list because widget_select()
647 changes the window position in Z order */
648 if (e->modified)
649 m = g_slist_prepend (m, l->data);
652 for (me = m; me != NULL; me = g_slist_next (me))
654 e = EDIT (me->data);
656 widget_select (WIDGET (e));
658 if (!edit_ok_to_exit (e))
659 break;
662 /* if all files were checked, quit editor */
663 if (me == NULL)
664 dlg_close (h);
666 g_slist_free (m);
669 /* --------------------------------------------------------------------------------------------- */
671 static inline void
672 edit_set_buttonbar (WEdit *edit, WButtonBar *bb)
674 Widget *w = WIDGET (edit);
676 buttonbar_set_label (bb, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL);
677 buttonbar_set_label (bb, 2, Q_ ("ButtonBar|Save"), w->keymap, w);
678 buttonbar_set_label (bb, 3, Q_ ("ButtonBar|Mark"), w->keymap, w);
679 buttonbar_set_label (bb, 4, Q_ ("ButtonBar|Replac"), w->keymap, w);
680 buttonbar_set_label (bb, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
681 buttonbar_set_label (bb, 6, Q_ ("ButtonBar|Move"), w->keymap, w);
682 buttonbar_set_label (bb, 7, Q_ ("ButtonBar|Search"), w->keymap, w);
683 buttonbar_set_label (bb, 8, Q_ ("ButtonBar|Delete"), w->keymap, w);
684 buttonbar_set_label (bb, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL);
685 buttonbar_set_label (bb, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL);
688 /* --------------------------------------------------------------------------------------------- */
690 static void
691 edit_total_update (WEdit *edit)
693 edit_find_bracket (edit);
694 edit->force |= REDRAW_COMPLETELY;
695 edit_update_curs_row (edit);
696 edit_update_screen (edit);
699 /* --------------------------------------------------------------------------------------------- */
701 static gboolean
702 edit_update_cursor (WEdit *edit, const mouse_event_t *event)
704 int x, y;
705 gboolean done;
707 x = event->x - (edit->fullscreen ? 0 : 1);
708 y = event->y - (edit->fullscreen ? 0 : 1);
710 if (edit->mark2 != -1 && event->msg == MSG_MOUSE_UP)
711 return TRUE; /* don't do anything */
713 if (event->msg == MSG_MOUSE_DOWN || event->msg == MSG_MOUSE_UP)
714 edit_push_key_press (edit);
716 if (!edit_options.cursor_beyond_eol)
717 edit->prev_col = x - edit->start_col - edit_options.line_state_width;
718 else
720 long line_len;
722 line_len =
723 edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
724 edit_buffer_get_current_eol (&edit->buffer));
726 if (x > line_len - 1)
728 edit->over_col = x - line_len - edit->start_col - edit_options.line_state_width;
729 edit->prev_col = line_len;
731 else
733 edit->over_col = 0;
734 edit->prev_col = x - edit_options.line_state_width - edit->start_col;
738 if (y > edit->curs_row)
739 edit_move_down (edit, y - edit->curs_row, FALSE);
740 else if (y < edit->curs_row)
741 edit_move_up (edit, edit->curs_row - y, FALSE);
742 else
743 edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
745 if (event->msg == MSG_MOUSE_CLICK)
747 edit_mark_cmd (edit, TRUE); /* reset */
748 edit->highlight = 0;
751 done = (event->msg != MSG_MOUSE_DRAG);
752 if (done)
753 edit_mark_cmd (edit, FALSE);
755 return done;
758 /* --------------------------------------------------------------------------------------------- */
759 /** Callback for the edit dialog */
761 static cb_ret_t
762 edit_dialog_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
764 WGroup *g = GROUP (w);
765 WDialog *h = DIALOG (w);
767 switch (msg)
769 case MSG_INIT:
770 edit_dlg_init ();
771 return MSG_HANDLED;
773 case MSG_RESIZE:
774 dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL);
775 menubar_arrange (menubar_find (h));
776 return MSG_HANDLED;
778 case MSG_ACTION:
780 /* Handle shortcuts, menu, and buttonbar. */
782 cb_ret_t result;
784 result = edit_dialog_command_execute (h, parm);
786 /* We forward any commands coming from the menu, and which haven't been
787 handled by the dialog, to the focused WEdit window. */
788 if (result == MSG_NOT_HANDLED && sender == WIDGET (menubar_find (h)))
789 result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL);
791 return result;
794 case MSG_KEY:
796 Widget *we = WIDGET (g->current->data);
797 cb_ret_t ret = MSG_NOT_HANDLED;
799 if (edit_widget_is_editor (we))
801 gboolean ext_mode;
802 long command;
804 /* keep and then extmod flag */
805 ext_mode = we->ext_mode;
806 command = widget_lookup_key (we, parm);
807 we->ext_mode = ext_mode;
809 if (command == CK_IgnoreKey)
810 we->ext_mode = FALSE;
811 else
813 ret = edit_dialog_command_execute (h, command);
814 /* if command was not handled, keep the extended mode
815 for the further key processing */
816 if (ret == MSG_HANDLED)
817 we->ext_mode = FALSE;
822 * Due to the "end of bracket" escape the editor sees input with is_idle() == false
823 * (expects more characters) and hence doesn't yet refresh the screen, but then
824 * no further characters arrive (there's only an "end of bracket" which is swallowed
825 * by tty_get_event()), so you end up with a screen that's not refreshed after pasting.
826 * So let's trigger an IDLE signal.
828 if (!is_idle ())
829 widget_idle (w, TRUE);
830 return ret;
833 /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */
834 case MSG_UNHANDLED_KEY:
835 return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
837 case MSG_VALIDATE:
838 edit_quit (h);
839 return MSG_HANDLED;
841 case MSG_DESTROY:
842 edit_dlg_deinit ();
843 return MSG_HANDLED;
845 case MSG_IDLE:
846 widget_idle (w, FALSE);
847 return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL);
849 default:
850 return dlg_default_callback (w, sender, msg, parm, data);
854 /* --------------------------------------------------------------------------------------------- */
857 * Handle mouse events of editor screen.
859 * @param w Widget object (the editor)
860 * @param msg mouse event message
861 * @param event mouse event data
863 static void
864 edit_dialog_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
866 gboolean unhandled = TRUE;
868 if (msg == MSG_MOUSE_DOWN && event->y == 0)
870 WGroup *g = GROUP (w);
871 WDialog *h = DIALOG (w);
872 WMenuBar *b;
874 b = menubar_find (h);
876 if (!widget_get_state (WIDGET (b), WST_FOCUSED))
878 /* menubar */
880 GList *l;
881 GList *top = NULL;
882 int x;
884 /* Try find top fullscreen window */
885 for (l = g->widgets; l != NULL; l = g_list_next (l))
886 if (edit_widget_is_editor (CONST_WIDGET (l->data)) && EDIT (l->data)->fullscreen)
887 top = l;
889 /* Handle fullscreen/close buttons in the top line */
890 x = w->rect.cols - 6;
892 if (top != NULL && event->x >= x)
894 WEdit *e = EDIT (top->data);
896 if (top != g->current)
898 /* Window is not active. Activate it */
899 widget_select (WIDGET (e));
902 /* Handle buttons */
903 if (event->x - x <= 2)
904 edit_toggle_fullscreen (e);
905 else
906 send_message (h, NULL, MSG_ACTION, CK_Close, NULL);
908 unhandled = FALSE;
911 if (unhandled)
912 menubar_activate (b, drop_menus, -1);
916 /* Continue handling of unhandled event in window or menu */
917 event->result.abort = unhandled;
920 /* --------------------------------------------------------------------------------------------- */
922 static cb_ret_t
923 edit_dialog_bg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
925 switch (msg)
927 case MSG_INIT:
928 w->rect = WIDGET (w->owner)->rect;
929 rect_grow (&w->rect, -1, 0);
930 w->pos_flags |= WPOS_KEEP_ALL;
931 return MSG_HANDLED;
933 default:
934 return background_callback (w, sender, msg, parm, data);
938 /* --------------------------------------------------------------------------------------------- */
940 static cb_ret_t
941 edit_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
943 WEdit *e = EDIT (w);
945 switch (msg)
947 case MSG_FOCUS:
948 edit_set_buttonbar (e, buttonbar_find (DIALOG (w->owner)));
949 return MSG_HANDLED;
951 case MSG_DRAW:
952 e->force |= REDRAW_COMPLETELY;
953 edit_update_screen (e);
954 return MSG_HANDLED;
956 case MSG_KEY:
958 int cmd, ch;
959 cb_ret_t ret = MSG_NOT_HANDLED;
961 /* The user may override the access-keys for the menu bar. */
962 if (macro_index == -1 && !bracketed_pasting_in_progress && edit_execute_macro (e, parm))
964 edit_update_screen (e);
965 ret = MSG_HANDLED;
967 else if (edit_translate_key (e, parm, &cmd, &ch))
969 edit_execute_key_command (e, cmd, ch);
970 edit_update_screen (e);
971 ret = MSG_HANDLED;
974 return ret;
977 case MSG_ACTION:
978 /* command from menubar or buttonbar */
979 edit_execute_key_command (e, parm, -1);
980 edit_update_screen (e);
981 return MSG_HANDLED;
983 case MSG_CURSOR:
985 int y, x;
987 y = (e->fullscreen ? 0 : 1) + EDIT_TEXT_VERTICAL_OFFSET + e->curs_row;
988 x = (e->fullscreen ? 0 : 1) + EDIT_TEXT_HORIZONTAL_OFFSET +
989 edit_options.line_state_width + e->curs_col + e->start_col + e->over_col;
991 widget_gotoyx (w, y, x);
992 return MSG_HANDLED;
995 case MSG_IDLE:
996 edit_update_screen (e);
997 return MSG_HANDLED;
999 case MSG_DESTROY:
1000 edit_clean (e);
1001 return MSG_HANDLED;
1003 default:
1004 return widget_default_callback (w, sender, msg, parm, data);
1008 /* --------------------------------------------------------------------------------------------- */
1011 * Handle move/resize mouse events.
1013 static void
1014 edit_mouse_handle_move_resize (Widget *w, mouse_msg_t msg, mouse_event_t *event)
1016 WEdit *edit = EDIT (w);
1017 WRect *r = &w->rect;
1018 const WRect *h = &CONST_WIDGET (w->owner)->rect;
1019 int global_x, global_y;
1021 if (msg == MSG_MOUSE_UP)
1023 /* Exit move/resize mode. */
1024 edit_execute_cmd (edit, CK_Enter, -1);
1025 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
1026 return;
1029 if (msg != MSG_MOUSE_DRAG)
1031 * We ignore any other events. Specifically, MSG_MOUSE_DOWN.
1033 * When the move/resize is initiated by the menu, we let the user
1034 * stop it by clicking with the mouse. Which is why we don't want
1035 * a mouse down to affect the window.
1037 return;
1039 /* Convert point to global coordinates for easier calculations. */
1040 global_x = event->x + r->x;
1041 global_y = event->y + r->y;
1043 /* Clamp the point to the dialog's client area. */
1044 global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */
1045 global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */
1047 if (edit->drag_state == MCEDIT_DRAG_MOVE)
1049 r->y = global_y;
1050 r->x = global_x - edit->drag_state_start;
1052 else if (edit->drag_state == MCEDIT_DRAG_RESIZE)
1054 r->lines = MAX (WINDOW_MIN_LINES, global_y - r->y + 1);
1055 r->cols = MAX (WINDOW_MIN_COLS, global_x - r->x + 1);
1058 edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */
1060 /* We draw the whole dialog because dragging/resizing exposes area beneath. */
1061 widget_draw (WIDGET (w->owner));
1064 /* --------------------------------------------------------------------------------------------- */
1067 * Handle mouse events of editor window
1069 * @param w Widget object (the editor window)
1070 * @param msg mouse event message
1071 * @param event mouse event data
1073 static void
1074 edit_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
1076 WEdit *edit = EDIT (w);
1077 /* buttons' distance from right edge */
1078 int dx = edit->fullscreen ? 0 : 2;
1079 /* location of 'Close' and 'Toggle fullscreen' pictograms */
1080 int close_x, toggle_fullscreen_x;
1082 close_x = (w->rect.cols - 1) - dx - 1;
1083 toggle_fullscreen_x = close_x - 3;
1085 if (edit->drag_state != MCEDIT_DRAG_NONE)
1087 /* window is being resized/moved */
1088 edit_mouse_handle_move_resize (w, msg, event);
1089 return;
1092 /* If it's the last line on the screen, we abort the event to make the
1093 * system channel it to the overlapping buttonbar instead. We have to do
1094 * this because a WEdit has the WOP_TOP_SELECT flag, which makes it above
1095 * the buttonbar in Z-order. */
1096 if (msg == MSG_MOUSE_DOWN && (event->y + w->rect.y == LINES - 1))
1098 event->result.abort = TRUE;
1099 return;
1102 switch (msg)
1104 case MSG_MOUSE_DOWN:
1105 widget_select (w);
1106 edit_update_curs_row (edit);
1107 edit_update_curs_col (edit);
1109 if (!edit->fullscreen)
1111 if (event->y == 0)
1113 if (event->x >= close_x - 1 && event->x <= close_x + 1)
1114 ; /* do nothing (see MSG_MOUSE_CLICK) */
1115 else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
1116 ; /* do nothing (see MSG_MOUSE_CLICK) */
1117 else
1119 /* start window move */
1120 edit_execute_cmd (edit, CK_WindowMove, -1);
1121 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
1122 edit->drag_state_start = event->x;
1124 break;
1127 if (event->y == w->rect.lines - 1 && event->x == w->rect.cols - 1)
1129 /* bottom-right corner -- start window resize */
1130 edit_execute_cmd (edit, CK_WindowResize, -1);
1131 break;
1135 MC_FALLTHROUGH; /* to start/stop text selection */
1137 case MSG_MOUSE_UP:
1138 edit_update_cursor (edit, event);
1139 edit_total_update (edit);
1140 break;
1142 case MSG_MOUSE_CLICK:
1143 if (event->y == 0)
1145 if (event->x >= close_x - 1 && event->x <= close_x + 1)
1146 send_message (w->owner, NULL, MSG_ACTION, CK_Close, NULL);
1147 else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
1148 edit_toggle_fullscreen (edit);
1149 else if (!edit->fullscreen && event->count == GPM_DOUBLE)
1150 /* double click on top line (toggle fullscreen) */
1151 edit_toggle_fullscreen (edit);
1153 else if (event->count == GPM_DOUBLE)
1155 /* double click */
1156 edit_mark_current_word_cmd (edit);
1157 edit_total_update (edit);
1159 else if (event->count == GPM_TRIPLE)
1161 /* triple click: works in GPM only, not in xterm */
1162 edit_mark_current_line_cmd (edit);
1163 edit_total_update (edit);
1165 break;
1167 case MSG_MOUSE_DRAG:
1168 edit_update_cursor (edit, event);
1169 edit_total_update (edit);
1170 break;
1172 case MSG_MOUSE_SCROLL_UP:
1173 edit_move_up (edit, 2, TRUE);
1174 edit_total_update (edit);
1175 break;
1177 case MSG_MOUSE_SCROLL_DOWN:
1178 edit_move_down (edit, 2, TRUE);
1179 edit_total_update (edit);
1180 break;
1182 default:
1183 break;
1187 /* --------------------------------------------------------------------------------------------- */
1188 /*** public functions ****************************************************************************/
1189 /* --------------------------------------------------------------------------------------------- */
1191 * Edit one file.
1193 * @param file_vpath file object
1194 * @param line line number
1195 * @return TRUE if no errors was occurred, FALSE otherwise
1198 gboolean
1199 edit_file (const edit_arg_t *arg)
1201 GList *files;
1202 gboolean ok;
1204 files = g_list_prepend (NULL, (edit_arg_t *) arg);
1205 ok = edit_files (files);
1206 g_list_free (files);
1208 return ok;
1211 /* --------------------------------------------------------------------------------------------- */
1213 gboolean
1214 edit_files (const GList *files)
1216 static gboolean made_directory = FALSE;
1217 WDialog *edit_dlg;
1218 WGroup *g;
1219 WMenuBar *menubar;
1220 Widget *w, *wd;
1221 const GList *file;
1222 gboolean ok = FALSE;
1224 if (!made_directory)
1226 char *dir;
1228 dir = mc_build_filename (mc_config_get_cache_path (), EDIT_HOME_DIR, (char *) NULL);
1229 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1230 g_free (dir);
1232 dir = mc_build_filename (mc_config_get_path (), EDIT_HOME_DIR, (char *) NULL);
1233 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1234 g_free (dir);
1236 dir = mc_build_filename (mc_config_get_data_path (), EDIT_HOME_DIR, (char *) NULL);
1237 made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
1238 g_free (dir);
1241 /* Create a new dialog and add it widgets to it */
1242 edit_dlg =
1243 dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, edit_dialog_callback,
1244 edit_dialog_mouse_callback, "[Internal File Editor]", NULL);
1245 wd = WIDGET (edit_dlg);
1246 widget_want_tab (wd, TRUE);
1247 wd->keymap = editor_map;
1248 wd->ext_keymap = editor_x_map;
1250 edit_dlg->get_shortcut = edit_get_shortcut;
1251 edit_dlg->get_title = edit_get_title;
1253 g = GROUP (edit_dlg);
1255 edit_dlg->bg =
1256 WIDGET (background_new
1257 (1, 0, wd->rect.lines - 2, wd->rect.cols, EDITOR_BACKGROUND, ' ',
1258 edit_dialog_bg_callback));
1259 group_add_widget (g, edit_dlg->bg);
1261 menubar = menubar_new (NULL);
1262 w = WIDGET (menubar);
1263 group_add_widget_autopos (g, w, w->pos_flags, NULL);
1264 edit_init_menu (menubar);
1266 w = WIDGET (buttonbar_new ());
1267 group_add_widget_autopos (g, w, w->pos_flags, NULL);
1269 for (file = files; file != NULL; file = g_list_next (file))
1271 gboolean f_ok;
1273 f_ok = edit_load_file_from_filename (edit_dlg, (const edit_arg_t *) file->data);
1274 /* at least one file has been opened succefully */
1275 ok = ok || f_ok;
1278 if (ok)
1279 dlg_run (edit_dlg);
1281 if (!ok || widget_get_state (wd, WST_CLOSED))
1282 widget_destroy (wd);
1284 return ok;
1287 /* --------------------------------------------------------------------------------------------- */
1289 WEdit *
1290 edit_find_editor (const WDialog *h)
1292 const WGroup *g = CONST_GROUP (h);
1294 if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
1295 return EDIT (g->current->data);
1296 return EDIT (widget_find_by_type (CONST_WIDGET (h), edit_callback));
1299 /* --------------------------------------------------------------------------------------------- */
1301 * Check if widget is an WEdit class.
1303 * @param w probably editor object
1304 * @return TRUE if widget is an WEdit class, FALSE otherwise
1307 gboolean
1308 edit_widget_is_editor (const Widget *w)
1310 return (w != NULL && w->callback == edit_callback);
1313 /* --------------------------------------------------------------------------------------------- */
1315 void
1316 edit_update_screen (WEdit *e)
1318 edit_scroll_screen_over_cursor (e);
1319 edit_update_curs_col (e);
1320 edit_status (e, widget_get_state (WIDGET (e), WST_FOCUSED));
1322 /* pop all events for this window for internal handling */
1323 if (!is_idle ())
1324 e->force |= REDRAW_PAGE;
1325 else
1327 if ((e->force & REDRAW_COMPLETELY) != 0)
1328 e->force |= REDRAW_PAGE;
1329 edit_render_keypress (e);
1332 widget_draw (WIDGET (buttonbar_find (DIALOG (WIDGET (e)->owner))));
1335 /* --------------------------------------------------------------------------------------------- */
1337 * Save current window size.
1339 * @param edit editor object
1342 void
1343 edit_save_size (WEdit *edit)
1345 edit->loc_prev = WIDGET (edit)->rect;
1348 /* --------------------------------------------------------------------------------------------- */
1350 * Create new editor window and insert it into editor screen.
1352 * @param h editor dialog (screen)
1353 * @param y y coordinate
1354 * @param x x coordinate
1355 * @param lines window height
1356 * @param cols window width
1357 * @param f file object
1358 * @param fline line number in file
1359 * @return TRUE if new window was successfully created and inserted into editor screen,
1360 * FALSE otherwise
1363 gboolean
1364 edit_add_window (WDialog *h, const WRect *r, const edit_arg_t *arg)
1366 WEdit *edit;
1367 Widget *w;
1369 edit = edit_init (NULL, r, arg);
1370 if (edit == NULL)
1371 return FALSE;
1373 w = WIDGET (edit);
1374 w->callback = edit_callback;
1375 w->mouse_callback = edit_mouse_callback;
1377 group_add_widget_autopos (GROUP (h), w, WPOS_KEEP_ALL, NULL);
1378 edit_set_buttonbar (edit, buttonbar_find (h));
1379 widget_draw (WIDGET (h));
1381 return TRUE;
1384 /* --------------------------------------------------------------------------------------------- */
1386 * Handle move/resize events.
1388 * @param edit editor object
1389 * @param command action id
1390 * @return TRUE if the action was handled, FALSE otherwise
1393 gboolean
1394 edit_handle_move_resize (WEdit *edit, long command)
1396 Widget *w = WIDGET (edit);
1397 gboolean ret = FALSE;
1399 if (edit->fullscreen)
1401 edit->drag_state = MCEDIT_DRAG_NONE;
1402 w->mouse.forced_capture = FALSE;
1403 return ret;
1406 switch (edit->drag_state)
1408 case MCEDIT_DRAG_NONE:
1409 /* possible start move/resize */
1410 switch (command)
1412 case CK_WindowMove:
1413 edit->drag_state = MCEDIT_DRAG_MOVE;
1414 edit_save_size (edit);
1415 edit_status (edit, TRUE); /* redraw frame and status */
1417 * If a user initiates a move by the menu, not by the mouse, we
1418 * make a subsequent mouse drag pull the frame from its middle.
1419 * (We can instead choose '0' to pull it from the corner.)
1421 edit->drag_state_start = w->rect.cols / 2;
1422 ret = TRUE;
1423 break;
1424 case CK_WindowResize:
1425 edit->drag_state = MCEDIT_DRAG_RESIZE;
1426 edit_save_size (edit);
1427 edit_status (edit, TRUE); /* redraw frame and status */
1428 ret = TRUE;
1429 break;
1430 default:
1431 break;
1433 break;
1435 case MCEDIT_DRAG_MOVE:
1436 switch (command)
1438 case CK_WindowResize:
1439 edit->drag_state = MCEDIT_DRAG_RESIZE;
1440 ret = TRUE;
1441 break;
1442 case CK_Up:
1443 case CK_Down:
1444 case CK_Left:
1445 case CK_Right:
1446 edit_window_move (edit, command);
1447 ret = TRUE;
1448 break;
1449 case CK_Enter:
1450 case CK_WindowMove:
1451 edit->drag_state = MCEDIT_DRAG_NONE;
1452 edit_status (edit, TRUE); /* redraw frame and status */
1453 MC_FALLTHROUGH;
1454 default:
1455 ret = TRUE;
1456 break;
1458 break;
1460 case MCEDIT_DRAG_RESIZE:
1461 switch (command)
1463 case CK_WindowMove:
1464 edit->drag_state = MCEDIT_DRAG_MOVE;
1465 ret = TRUE;
1466 break;
1467 case CK_Up:
1468 case CK_Down:
1469 case CK_Left:
1470 case CK_Right:
1471 edit_window_resize (edit, command);
1472 ret = TRUE;
1473 break;
1474 case CK_Enter:
1475 case CK_WindowResize:
1476 edit->drag_state = MCEDIT_DRAG_NONE;
1477 edit_status (edit, TRUE); /* redraw frame and status */
1478 MC_FALLTHROUGH;
1479 default:
1480 ret = TRUE;
1481 break;
1483 break;
1485 default:
1486 break;
1490 * - We let the user stop a resize/move operation by clicking with the
1491 * mouse anywhere. ("clicking" = pressing and releasing a button.)
1492 * - We let the user perform a resize/move operation by a mouse drag
1493 * initiated anywhere.
1495 * "Anywhere" means: inside or outside the window. We make this happen
1496 * with the 'forced_capture' flag.
1498 w->mouse.forced_capture = (edit->drag_state != MCEDIT_DRAG_NONE);
1500 return ret;
1503 /* --------------------------------------------------------------------------------------------- */
1505 * Toggle window fuulscreen mode.
1507 * @param edit editor object
1510 void
1511 edit_toggle_fullscreen (WEdit *edit)
1513 Widget *w = WIDGET (edit);
1515 edit->fullscreen = !edit->fullscreen;
1516 edit->force = REDRAW_COMPLETELY;
1518 if (!edit->fullscreen)
1520 edit_restore_size (edit);
1521 /* do not follow screen size on resize */
1522 w->pos_flags = WPOS_KEEP_DEFAULT;
1524 else
1526 WRect r;
1528 edit_save_size (edit);
1529 r = WIDGET (w->owner)->rect;
1530 rect_grow (&r, -1, 0);
1531 widget_set_size_rect (w, &r);
1532 /* follow screen size on resize */
1533 w->pos_flags = WPOS_KEEP_ALL;
1534 edit->force |= REDRAW_PAGE;
1535 edit_update_screen (edit);
1539 /* --------------------------------------------------------------------------------------------- */