{* * Copyright (C) 2024 Mikulas Patocka * * This file is part of Ajla. * * Ajla 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. * * Ajla 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 * Ajla. If not, see . *} uses ui.widget; uses spawn; uses defs; uses common; uses panel; uses prompt; uses view; uses edit; uses error; const saved_properties := [ "panel-0-sort-order", "panel-0-sort-order-flags", "panel-1-sort-order", "panel-1-sort-order-flags", "view-wrap", "view-hex", "view-goto-mode", "view-search-mode", "view-search-flags", "edit-goto-mode", "edit-search-flags", ]; record acmd_state [ ro : acmd_ro; self : wid; panels : list(wid); input : wid; fkeys : wid; ps1 : bytes; prompt : string; last_key : event_keyboard; ] fn acmd_prompt_trunc(st : acmd_state, x : int) : string [ var max := x * 3 shr 2; var l := string_length(st.prompt); if l <= max then return st.prompt; var s := `...` + st.prompt[l - max .. ]; return s; ] fn acmd_set_fkeys(implicit app : appstate) : appstate [ property_set("fkeys", property.l.([ property.s.(``), property.s.(``), property.s.(`View`), property.s.(`Edit`), property.s.(`Copy`), property.s.(`Move`), property.s.(`Mkdir`), property.s.(`Delete`), property.s.(`PullDn`), property.s.(`Quit`), property.s.(`Left`), property.s.(`Right`), ])); ] fn acmd_init(d : dhandle, h : list(handle), env : treemap(bytes, bytes), implicit w : world, implicit app : appstate, id : wid) : (world, appstate, acmd_state) [ property_set("fkeys-number", property.a.(new_property_attrib(#aaaa, #aaaa, #aaaa, #0000, #0000, #0000, 0, 0))); property_set("fkeys-text", property.a.(new_property_attrib(#0000, #0000, #0000, #0000, #aaaa, #aaaa, 0, curses_invert))); property_set("dialog", property.a.(new_property_attrib(#0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_invert))); property_set("dialog-title", property.a.(new_property_attrib(#0000, #0000, #aaaa, #aaaa, #aaaa, #aaaa, 0, curses_invert))); property_set("button", property.a.(new_property_attrib(#0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_invert))); property_set("button-selected", property.a.(new_property_attrib(#0000, #0000, #0000, #0000, #aaaa, #aaaa, 0, curses_bold))); property_set("text", property.a.(new_property_attrib(#0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_invert))); property_set("input", property.a.(new_property_attrib(#0000, #0000, #0000, #0000, #aaaa, #aaaa, 0, 0))); property_set("display", property.a.(new_property_attrib(#0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_invert))); property_set("progress", property.a.(new_property_attrib(#0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_invert))); property_set("progress-bar", property.a.(new_property_attrib(#aaaa, #aaaa, #aaaa, #0000, #0000, #0000, 0, 0))); property_set("error-dialog", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #aaaa, #0000, #0000, 0, curses_bold))); property_set("error-dialog-title", property.a.(new_property_attrib(#ffff, #ffff, #0000, #aaaa, #0000, #0000, curses_bold, curses_bold))); property_set("error-text", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #aaaa, #0000, #0000, curses_bold, curses_bold))); property_set("error-button", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #aaaa, #0000, #0000, 0, curses_invert))); property_set("error-button-selected", property.a.(new_property_attrib(#0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_bold))); property_set("mainmenu-text", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #0000, #aaaa, #aaaa, 0, curses_invert))); property_set("mainmenu-text-selected", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #0000, #0000, #0000, 0, 0))); property_set("mainmenu-text-hotkey", property.a.(new_property_attrib(#ffff, #ffff, #0000, #0000, #aaaa, #aaaa, 0, curses_bold))); property_set("mainmenu-text-selected-hotkey", property.a.(new_property_attrib(#ffff, #ffff, #0000, #0000, #0000, #0000, 0, curses_bold))); property_set("menu-frame", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #0000, #aaaa, #aaaa, 0, curses_invert))); property_set("menu-text", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #0000, #aaaa, #aaaa, 0, curses_invert))); property_set("menu-text-hotkey", property.a.(new_property_attrib(#ffff, #ffff, #0000, #0000, #aaaa, #aaaa, 0, curses_bold))); property_set("menu-text-selected", property.a.(new_property_attrib(#ffff, #ffff, #ffff, #0000, #0000, #0000, 0, 0))); property_set("menu-text-selected-hotkey", property.a.(new_property_attrib(#ffff, #ffff, #0000, #0000, #0000, #0000, 0, curses_bold))); property_set("panel-0-sort-order", property.i.(sort_name)); property_set("panel-0-sort-order-flags", property.i.(0 bts sort_flag_case_sensitive)); property_set("panel-1-sort-order", property.i.(sort_name)); property_set("panel-1-sort-order-flags", property.i.(0 bts sort_flag_case_sensitive)); property_set("select-string", property.s.(``)); property_set("select-flags", property.i.(0 bts select_flag_case_sensitive)); property_set("view-wrap", property.o.(false)); property_set("view-hex", property.o.(false)); property_set("view-goto-text", property.s.(``)); property_set("view-goto-mode", property.i.(view_goto_mode_line)); property_set("view-search-text", property.s.(``)); property_set("view-search-mode", property.i.(view_search_mode_string)); property_set("view-search-flags", property.i.(0 bts view_search_flag_case_sensitive)); property_set("view-charset", property.b.("")); property_set("edit-goto-text", property.s.(``)); property_set("edit-goto-mode", property.i.(edit_goto_mode_line)); property_set("edit-search-flags", property.i.(0 bts view_search_flag_case_sensitive)); property_set("edit-search-text", property.s.(``)); property_set("edit-search-flags", property.i.(0 bts edit_search_flag_case_sensitive)); property_set("prompt", property.s.(``)); var w1 := w; var cfgdir := path_config(env, "acmd"); var cfgfile := ropen(cfgdir, "acmd.cfg", 0); if not is_exception cfgfile then [ var cfgbytes := read_lazy(cfgfile); property_load(saved_properties, cfgbytes); ] recover_world(w1); var st := acmd_state.[ ro : acmd_ro.[ h : h, env : env, home : treemap_search(env, "HOME"), loc : locale_init(env), tz : timezone_init(d, env), ], self : id, panels : empty(wid), last_key : event_keyboard. [ key : 0, flags : 0, rep : 1 ], ]; property_observe(id, "cwd"); var ps1 := treemap_search(env, "PS1"); if ps1 is j then st.ps1 := ps1.j; else st.ps1 := "$ "; var cwd := path_get_cwd(d, env); if is_exception cwd then cwd := dpath(droot()); st.input := widget_new(id, input_not_selectable_class, input_init("acmd-prompt-", "prompt", false,,,), false); for i := 0 to 2 do [ var pid := widget_new(id, panel_class, panel_init(i, st.ro, cwd,,,), false); st.panels +<= pid; ] st.fkeys := widget_new_window(fkeys_class, fkeys_init(12, "",,,), true); st.prompt := shell_expand_prompt(st.ro, st.ps1, cwd); acmd_set_fkeys(); return st; ] fn acmd_redraw(implicit app : appstate, implicit curs : curses, com : widget_common, st : acmd_state) : curses [ //var sc := curses_get_scissors(); //eval debug("acmd redraw: " + ntos(sc.x1) + ", " + ntos(sc.x2) + ", " + ntos(sc.y1) + ", " + ntos(sc.y2)); property_set_attrib(property_get_attrib("acmd-prompt", #aaaa, #aaaa, #aaaa, #0000, #0000, #0000, 0, 0)); curses_set_pos(0, com.size_y - 1); curses_print(acmd_prompt_trunc(st, com.size_x)); widget_redraw_subwidgets(com); ] fn acmd_get_cursor(implicit app : appstate, com : widget_common, st : acmd_state) : (int, int) [ return widget_get_cursor(st.input); ] fn acmd_get_active_panel(implicit app : appstate, st : acmd_state) : int [ for p := 0 to len(st.panels) do [ if widget_is_top(st.panels[p]) then return p; ] abort internal("acmd_get_active_panel: no active panel"); ] fn acmd_chdir_panel(implicit w : world, implicit app : appstate, implicit st : acmd_state, p : int, s : bytes) : (world, appstate, acmd_state) [ widget_enqueue_event(st.panels[p], wevent.set_property.(event_set_property.[ prop : "cwd", val : property.b.(s), ])); ] fn acmd_chdir(implicit w : world, implicit app : appstate, implicit st : acmd_state, s : bytes) : (world, appstate, acmd_state) [ var p := acmd_get_active_panel(); acmd_chdir_panel(p, s); ] fn acmd_command(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : acmd_state, s : string) : (world, appstate, widget_common, acmd_state) [ if s = `cd` or s = `cd ` then [ acmd_chdir(path_expand_home(st.ro.home, "~")); return; ] if len(s) > 3, s[ .. 3] = `cd ` then [ acmd_chdir(path_expand_home(st.ro.home, string_to_locale(st.ro.loc, s[3 .. ]))); return; ] app_suspend(); var cwd := property_get("cwd").b; var d := dopen(dnone(), cwd, 0); var env := st.ro.env; env := treemap_insert(env, "PWD", cwd); var shell, cmdline := spawn_command(string_to_locale(st.ro.loc, s), env); var old_w := w; var ph := spawn(d, st.ro.h, shell, cmdline, st.ro.env); if is_exception ph then [ recover_world(old_w); app_resume(); acmd_error(`Error spawning a program "` + s + `"`, locale_to_string(st.ro.loc, exception_string(ph))); return; ] var exit_code := wait(ph); if exit_code < 0 then write(st.ro.h[1], "Command failed with signal " + ntos(-exit_code) + nl); write(st.ro.h[1], "Press any key to continue..." + nl); event_wait_for_any_key(st.ro.h[0]); app_resume(); for i := 0 to len(st.panels) do [ widget_enqueue_event(st.panels[i], wevent.set_property.(event_set_property.[ prop : "rescan-without-redraw", val : property.n, ])); ] ] fn acmd_swap(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ widget_enqueue_event(st.self, wevent.set_property.(event_set_property.[ prop : "swap-panels", val : property.n, ])); ] fn acmd_view(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var cwd := property_get("cwd").b; var cfile := property_get("cfile").b; var joined_b := path_join(cwd, cfile); if path_is_root(joined_b) then goto do_chdir; var joined := locale_to_string(st.ro.loc, joined_b); var old_w := w; var d := dopen(dnone(), cwd, 0); var ftype := stat(d, cfile, stat_flag_type); if is_exception ftype then [ recover_world(old_w); acmd_error(`Error opening ` + joined, locale_to_string(st.ro.loc, exception_string(ftype))); return; ] if ftype[0] = stat_type_directory then [ do_chdir: acmd_chdir(cfile); return; ] var w := old_w; var file := bopen(d, cfile, open_flag_read, 0); if is_exception file then [ recover_world(old_w); acmd_error(`Error opening ` + joined, locale_to_string(st.ro.loc, exception_string(file))); return; ] var file_size := bsize_lazy(file); if is_exception file_size then [ recover_world(old_w); acmd_error(`Error getting size of file ` + joined, locale_to_string(st.ro.loc, exception_string(file_size))); return; ] var winid := widget_new_window(view_class, view_init(st.ro, joined, file,,,), false); ] fn acmd_edit(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var cwd := property_get("cwd").b; var cfile := property_get("cfile").b; var joined := locale_to_string(st.ro.loc, path_join(cwd, cfile)); var old_w := w; var d := dopen(dnone(), cwd, 0); var w := old_w; var file := bopen(d, cfile, open_flag_read, 0); if is_exception file then [ recover_world(old_w); acmd_error(`Error opening ` + joined, locale_to_string(st.ro.loc, exception_string(file))); return; ] var ftype := fstat(file, stat_flag_type); if is_exception ftype then [ recover_world(old_w); acmd_error(`Error opening ` + joined, locale_to_string(st.ro.loc, exception_string(ftype))); return; ] if ftype[0] <> stat_type_file then [ acmd_error(`Can't edit non-file ` + joined, ``); return; ] var file_size := bsize_lazy(file); if is_exception file_size then [ recover_world(old_w); acmd_error(`Error getting size of file ` + joined, locale_to_string(st.ro.loc, exception_string(file_size))); return; ] var winid := widget_new_window(edit_class, edit_init(st.ro, joined, file, d, cfile,,,), false); ] fn acmd_copy(mv : bool, implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var current_panel := st.panels[acmd_get_active_panel()]; var other_panel := st.panels[acmd_get_active_panel() xor 1]; var target := widget_get_property(other_panel, "cwd").b; widget_enqueue_event(current_panel, wevent.set_property.(event_set_property.[ prop : select(mv, "copy", "move"), val : property.b.(target), ])); ] fn acmd_mkdir(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var current_panel := st.panels[acmd_get_active_panel()]; widget_enqueue_event(current_panel, wevent.set_property.(event_set_property.[ prop : "mkdir", val : property.n, ])); ] fn acmd_delete(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var current_panel := st.panels[acmd_get_active_panel()]; widget_enqueue_event(current_panel, wevent.set_property.(event_set_property.[ prop : "delete", val : property.n, ])); ] fn acmd_quit(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var buttons := [ msgbox_button.[ label : `Yes`, click : lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ widget_enqueue_event(wid_none, wevent.quit); widget_destroy_onclick(id); ], ], msgbox_button.[ label : `No`, click : lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ return widget_destroy_onclick(id); ], hotkeys : treeset_from_list([ key_esc, key_f10 ]), ] ]; var winid := msgbox_new(`Exit the Ajla Commander`, widget_align.center, `Do you want to exit the Ajla Commander?`, "", buttons); ] fn acmd_mount_points(implicit w : world) : (world, list(bytes)) [ var w1 := w; var mp := mount_points(); if is_exception w then [ recover_world(w1); return empty(bytes); ] return mp; ] fn acmd_menu_drive(implicit st : acmd_state, mp : list(bytes), panel : int, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ panel := widget_get_property(st.panels[panel], "property-index").i; var cwd := widget_get_property(st.panels[panel], "cwd").b; var init := 0; var init_len := 0; var entries := empty(vselect_entry); for i := 0 to len(mp) do [ var c := locale_to_string(st.ro.loc, mp[i]); if list_begins_with(cwd, mp[i]) then [ if len(mp[i]) > init_len then [ init_len := len(mp[i]); init := i; ] ] var de := vselect_entry.[ label : c, change_focus : button_no_action, click : lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ acmd_chdir_panel(panel, mp[i]); widget_destroy_onclick(id); ], ]; entries +<= de; ] var winid : wid; app, winid := vselect_new(app, `Select Drive`, "", true, entries, init); return app; ] fn sort_order_dialog_layout(implicit app : appstate, ids : list(wid), offs_x offs_y min_x pref_x max_x : int) : (appstate, int, int) [ var xs := 0; for i := 0 to 9 do [ xs := max(xs, widget_get_width(ids[i], pref_x)); ] xs := max(xs, widgets_get_width(ids[9 .. ], 2, pref_x)); xs := min(xs, max_x); xs := max(xs, min_x); var yp := offs_y + 1; for i := 0 to 9 do [ yp := widget_place(ids[i], offs_x, xs, yp); ] yp += 1; yp := widgets_place(ids[9 .. ], widget_align.center, 2, 1, offs_x, xs, yp); return xs, yp; ] fn acmd_menu_sort_order(implicit st : acmd_state, panel : int, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ panel := widget_get_property(st.panels[panel], "property-index").i; var prop_sort := "panel-" + ntos(panel) + "-sort-order"; var prop_sort_flags := "panel-" + ntos(panel) + "-sort-order-flags"; properties_backup([ prop_sort, prop_sort_flags ]); var entries := [ dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Name`, "", prop_sort, true, sort_name,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Extension`, "", prop_sort, true, sort_extension,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Size`, "", prop_sort, true, sort_size,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Modify time`, "", prop_sort, true, sort_mtime,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Access time`, "", prop_sort, true, sort_atime,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Change time`, "", prop_sort, true, sort_ctime,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Inode number`, "", prop_sort, true, sort_inode,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Case sensitive`, "", prop_sort_flags, false, sort_flag_case_sensitive,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Reverse`, "", prop_sort_flags, false, sort_flag_reverse,,,), ], dialog_entry.[ cls : button_class, init : button_init(`OK`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ widget_destroy_onclick(id); ],,,), hotkeys : treeset_from_list([ key_enter ]), ], dialog_entry.[ cls : button_class, init : button_init(`Cancel`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ properties_revert([ prop_sort, prop_sort_flags ]); widget_destroy_onclick(id); ],,,), hotkeys : treeset_from_list([ key_esc, key_f10 ]), ], ]; var winid := widget_new_window(dialog_class, dialog_init(`Sort Order`, entries, 0, dialog_no_event, sort_order_dialog_layout, "",,,), false); ] fn acmd_menu_rescan(implicit st : acmd_state, panel : int, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ widget_enqueue_event(st.panels[panel], wevent.set_property.(event_set_property.[ prop : "rescan", val : property.n, ])); ] fn select_dialog_layout(implicit app : appstate, ids : list(wid), offs_x offs_y min_x pref_x max_x : int) : (appstate, int, int) [ var xs := 0; xs := max(xs, widget_get_width(ids[0], pref_x)); //xs := max(xs, widget_get_width(ids[1], pref_x)); xs := max(xs, widgets_get_width(ids[2 .. 4], 2, pref_x)); xs := max(xs, widgets_get_width(ids[4 .. ], 2, pref_x)); xs := min(xs, max_x); xs := max(xs, min_x); var yp := offs_y; yp := widget_place(ids[0], offs_x, xs, yp); yp := widget_place(ids[1], offs_x, xs, yp); yp += 1; yp := widgets_place(ids[2 .. 4], widget_align.center, 2, 1, offs_x, xs, yp); yp += 1; yp := widgets_place(ids[4 .. ], widget_align.center, 2, 1, offs_x, xs, yp); return xs, yp; ] fn acmd_do_select(implicit st : acmd_state, sel : bool, implicit app : appstate) : appstate [ widget_enqueue_event(st.panels[acmd_get_active_panel()], wevent.set_property.(event_set_property.[ prop : select(sel, "mark-unselect", "mark-select"), val : property.n, ])); ] fn acmd_menu_select(implicit st : acmd_state, sel : bool, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var prop_select_string := "select-string"; var prop_select_flags := "select-flags"; properties_backup([ prop_select_string, prop_select_flags ]); var entries := [ dialog_entry.[ cls : text_class, init : text_init(widget_align.left, `Shell pattern`, "",,,), ], dialog_entry.[ cls : input_class, init : input_init("", prop_select_string, true,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Files only`, "", prop_select_flags, false, select_flag_files_only,,,), ], dialog_entry.[ cls : checkbox_class, init : checkbox_init(`Case sensitive`, "", prop_select_flags, false, select_flag_case_sensitive,,,), ], dialog_entry.[ cls : button_class, init : button_init(`OK`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ acmd_do_select(sel); widget_destroy_onclick(id); ],,,), hotkeys : treeset_from_list([ key_enter ]), ], dialog_entry.[ cls : button_class, init : button_init(`Cancel`, true, "", button_no_action, lambda (implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ properties_revert([ prop_select_string, prop_select_flags ]); widget_destroy_onclick(id); ],,,), hotkeys : treeset_from_list([ key_esc, key_f10 ]), ], ]; var winid := widget_new_window(dialog_class, dialog_init(select(sel, `Unselect files`, `Select files`), entries, 1, dialog_no_event, select_dialog_layout, "",,,), false); ] fn acmd_menu_invert_selection(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ widget_enqueue_event(st.panels[acmd_get_active_panel()], wevent.set_property.(event_set_property.[ prop : "mark-invert", val : property.n, ])); ] fn acmd_menu_save_setup(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var old_w := w; var cfgdir := path_config(st.ro.env, "acmd"); var cfgtext := property_save(saved_properties); path_write_atomic(cfgdir, "acmd.cfg", cfgtext); if is_exception w then [ var xw := w; recover_world(old_w); acmd_error(`Error saving config file`, locale_to_string(st.ro.loc, exception_string(xw))); ] ] fn acmd_main_menu_panel(implicit st : acmd_state, panel : int, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var entries := empty(menu_entry); var mp := acmd_mount_points(); if len(mp) > 0 then [ entries += [ menu_entry.[ label : `~Drive`, label_right : select(panel = 1, `F11`, `F12`), click : acmd_menu_drive(mp, panel,,,), close_onclick : true, ], menu_entry.[ label : ``, label_right : ``, ] ]; ] entries += [ menu_entry.[ label : `~Sort order`, label_right : ``, click : acmd_menu_sort_order(panel,,,), close_onclick : true, ], menu_entry.[ label : ``, label_right : ``, ], menu_entry.[ label : `~Rescan`, label_right : `Ctrl-R`, click : acmd_menu_rescan(panel,,,), close_onclick : true, ], ]; var winid := widget_new_window(menu_class, menu_init("", entries,,,), false); ] fn acmd_main_menu_file(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var entries := [ menu_entry.[ label : `~View`, label_right : `F3`, click : acmd_view(,,,), close_onclick : true, ], menu_entry.[ label : `~Edit`, label_right : `F4`, click : acmd_edit(,,,), close_onclick : true, ], menu_entry.[ label : `~Copy`, label_right : `F5`, click : acmd_copy(false, st,,,), close_onclick : true, ], menu_entry.[ label : `~Move`, label_right : `F6`, click : acmd_copy(true, st,,,), close_onclick : true, ], menu_entry.[ label : `C~reate directory`, label_right : `F7`, click : acmd_mkdir(st,,,), close_onclick : true, ], menu_entry.[ label : `~Delete`, label_right : `F8`, click : acmd_delete(st,,,), close_onclick : true, ], menu_entry.[ label : ``, label_right : ``, ], menu_entry.[ label : `Select ~group`, label_right : `+`, click : acmd_menu_select(true,,,), close_onclick : true, ], menu_entry.[ label : `U~nselect group`, label_right : `-`, click : acmd_menu_select(false,,,), close_onclick : true, ], menu_entry.[ label : `~Invert selection`, label_right : `*`, click : acmd_menu_invert_selection(,,,), close_onclick : true, ], menu_entry.[ label : ``, label_right : ``, ], menu_entry.[ label : `E~xit`, label_right : `F10`, click : acmd_quit(,,,), close_onclick : true, ], ]; var winid := widget_new_window(menu_class, menu_init("", entries,,,), false); ] fn acmd_main_menu_command(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var entries := [ menu_entry.[ label : `S~wap panels`, label_right : `Ctrl-U`, click : acmd_swap(,,,), close_onclick : true, ], ]; var winid := widget_new_window(menu_class, menu_init("", entries,,,), false); ] fn acmd_main_menu_options(implicit st : acmd_state, implicit w : world, implicit app : appstate, id : wid) : (world, appstate) [ var entries := [ menu_entry.[ label : `~Save setup`, label_right : ``, click : acmd_menu_save_setup(,,,), close_onclick : true, ], ]; var winid := widget_new_window(menu_class, menu_init("", entries,,,), false); ] fn acmd_main_menu(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : acmd_state, wev : wevent) : (world, appstate, widget_common, acmd_state) [ var entries := [ mainmenu_entry.[ label : `~Left`, click : acmd_main_menu_panel(0,,,), ], mainmenu_entry.[ label : `~File`, click : acmd_main_menu_file(,,,), ], mainmenu_entry.[ label : `~Command`, click : acmd_main_menu_command(,,,), ], mainmenu_entry.[ label : `~Options`, click : acmd_main_menu_options(,,,), ], mainmenu_entry.[ label : `~Right`, click : acmd_main_menu_panel(1,,,), ] ]; var winid := widget_new_window(mainmenu_class, mainmenu_init("", acmd_get_active_panel() * 4, entries,,,), false); if wev is mouse then [ widget_enqueue_event(winid, wev); ] ] fn acmd_process_event(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : acmd_state, wev : wevent) : (world, appstate, widget_common, acmd_state) [ if wev is resize then [ com.size_x := wev.resize.x; com.size_y := wev.resize.y - 1; widget_move(st.panels[0], 0, 0, com.size_x shr 1, com.size_y - 1, 0, 0); widget_move(st.panels[1], com.size_x shr 1, 0, com.size_x - (com.size_x shr 1), com.size_y - 1, 0, 0); ] if wev is resize or wev is property_changed then [ st.prompt := shell_expand_prompt(st.ro, st.ps1, property_get("cwd").b); var l := string_length(acmd_prompt_trunc(st, com.size_x)); widget_move(st.input, l, com.size_y - 1, com.size_x - l, 1, 0, 0); widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[ x1 : 0, x2 : com.size_x, y1 : com.size_y - 1, y2 : com.size_y, ])); return; ] if wev is keyboard then [ if st.last_key.key = key_esc, wev.keyboard.key >= '0', wev.keyboard.key <= '9', wev.keyboard.flags = 0 then [ var f := (wev.keyboard.key - '1' + 10) mod 10; wev.keyboard.key := key_f1 - f; ] if wev.keyboard.key >= '0', wev.keyboard.key <= '9', wev.keyboard.flags = key_flag_alt then [ var f := (wev.keyboard.key - '1' + 10) mod 10; wev.keyboard.key := key_f1 - f; ] st.last_key := wev.keyboard; if wev.keyboard.key = 'R', wev.keyboard.flags = key_flag_ctrl then [ acmd_menu_rescan(st, acmd_get_active_panel(), w, app, wid_none); return; ] if wev.keyboard.key = 'U', wev.keyboard.flags = key_flag_ctrl then [ acmd_swap(wid_none); return; ] if wev.keyboard.key = key_f3 then [ acmd_view(com.self); return; ] if wev.keyboard.key = key_f4 then [ acmd_edit(com.self); return; ] if wev.keyboard.key = key_f5 then [ acmd_copy(false, st, w, app, com.self); return; ] if wev.keyboard.key = key_f6 then [ acmd_copy(true, st, w, app, com.self); return; ] if wev.keyboard.key = key_f7 then [ acmd_mkdir(st, w, app, com.self); return; ] if wev.keyboard.key = key_f8 then [ acmd_delete(st, w, app, com.self); return; ] if wev.keyboard.key = key_f9 then [ acmd_main_menu(wev); return; ] if wev.keyboard.key = key_f10 then [ acmd_quit(com.self); return; ] if wev.keyboard.key = key_f11 or wev.keyboard.key = key_f12 or wev.keyboard.key = key_f1 and wev.keyboard.flags = key_flag_alt or wev.keyboard.key = key_f2 and wev.keyboard.flags = key_flag_alt then [ var mp := acmd_mount_points(); if len(mp) > 0 then [ var panel := select(wev.keyboard.key = key_f12 or wev.keyboard.key = key_f2, 0, 1); panel := widget_get_property(st.panels[panel], "property-index").i; acmd_menu_drive(st, mp, panel, w, app, com.self); ] return; ] if wev.keyboard.key = key_home or wev.keyboard.key = key_end then goto forward; if wev.keyboard.key = key_enter then [ var s := property_get("prompt").s; if (wev.keyboard.flags and key_flag_alt) <> 0 then [ var current_panel := st.panels[acmd_get_active_panel()]; var new_text := widget_get_property(current_panel, "selected-files").s; var cpos := property_get("prompt-cpos").i; s := s[ .. cpos] + new_text + s[cpos .. ]; property_set("prompt", property.s.(s)); property_set("prompt-cpos", property.i.(cpos + len(new_text))); return; ] if len(s) <> 0 then [ acmd_command(s); property_set("prompt", property.s.(``)); return; ] ] if wev.keyboard.key = '+' or wev.keyboard.key = '-' or wev.keyboard.key = '*' then [ var s := property_get("prompt").s; if len(s) = 0 then [ if wev.keyboard.key = '*' then [ acmd_menu_invert_selection(wid_none); ] else [ acmd_menu_select(wev.keyboard.key = '+', w, app, wid_none); ] return; ] ] ] if widget_accepts_key(st.input, wev) then [ widget_enqueue_event(st.input, wev); return; ] if wev is mouse then [ var mx, my := widget_relative_mouse_coords(com.self, wev.mouse); if my = 0, (wev.mouse.buttons and not wev.mouse.prev_buttons) <> 0 then [ acmd_main_menu(wev); return; ] if my = com.size_y - 1, mx >= string_length(st.prompt) then [ widget_enqueue_event(st.input, wev); return; ] ] if wev is change_focus then [ if widget_is_top(com.self) then acmd_set_fkeys(); return; ] if wev is set_property then [ if wev.set_property.prop = "rescan-panels" then [ acmd_menu_rescan(st, 0, w, app, wid_none); acmd_menu_rescan(st, 1, w, app, wid_none); return; ] if wev.set_property.prop = "mark-unselect" then [ widget_enqueue_event(st.panels[acmd_get_active_panel()], wevent.set_property.(event_set_property.[ prop : "mark-unselect", val : property.n, ])); return; ] if wev.set_property.prop = "swap-panels" then [ st.panels[0], st.panels[1] := st.panels[1], st.panels[0]; widget_move(st.panels[0], 0, 0, com.size_x shr 1, com.size_y - 1, 0, 0); widget_move(st.panels[1], com.size_x shr 1, 0, com.size_x - (com.size_x shr 1), com.size_y - 1, 0, 0); widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[ x1 : 0, x2 : com.size_x, y1 : 0, y2 : com.size_y, ])); return; ] return; ] forward: widget_forward(wev); ] const acmd_class~flat := widget_class.[ t : acmd_state, name : "acmd", is_selectable : true, redraw : acmd_redraw, get_cursor : acmd_get_cursor, process_event : acmd_process_event, ]; fn main(implicit w : world, d : dhandle, h : list(handle), args : list(bytes), env : treemap(bytes, bytes)) : world [ app_run(d, h, env, acmd_class, acmd_init(d, h, env,,,)); ]