use std::collections::HashMap;
use muda::{
Menu, MenuItem, PredefinedMenuItem, Submenu,
MenuEvent, MenuId,
accelerator::{Accelerator, Code, Modifiers, CMD_OR_CTRL},
};
use crate::command::Command;
fn acc(mods: Modifiers, code: Code) -> Option<Accelerator> {
Some(Accelerator::new(Some(mods), code))
}
fn cmd(code: Code) -> Option<Accelerator> { acc(CMD_OR_CTRL, code) }
fn cmd_shift(code: Code) -> Option<Accelerator> { acc(CMD_OR_CTRL | Modifiers::SHIFT, code) }
fn no_acc() -> Option<Accelerator> { None }
pub struct MenuIds {
map: HashMap<MenuId, Command>,
}
impl MenuIds {
fn new() -> Self { Self { map: HashMap::new() } }
fn register(&mut self, item: &MenuItem, cmd: Command) {
self.map.insert(item.id().clone(), cmd);
}
pub fn command_for_event(&self, event: &MenuEvent) -> Option<Command> {
self.map.get(&event.id).cloned()
}
}
pub fn build_menu() -> (Menu, MenuIds) {
let menu = Menu::new();
let mut ids = MenuIds::new();
let app_menu = Submenu::with_items("Ticker", true, &[
&PredefinedMenuItem::about(None, None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::services(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::hide(None),
&PredefinedMenuItem::hide_others(None),
&PredefinedMenuItem::show_all(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::quit(None),
]).unwrap();
let open_i = MenuItem::new("Open…", true, cmd(Code::KeyO));
let save_i = MenuItem::new("Save", true, cmd(Code::KeyS));
let import_i = MenuItem::new("Import…", true, cmd(Code::KeyI));
let rename_p = MenuItem::new("Rename Project…", true, no_acc());
ids.register(&open_i, Command::OpenDialog);
ids.register(&save_i, Command::SaveDialog);
ids.register(&import_i, Command::ImportDialog);
ids.register(&rename_p, Command::RunCommand("rp".into()));
let file_menu = Submenu::with_items("File", true, &[
&open_i,
&PredefinedMenuItem::separator(),
&save_i,
&PredefinedMenuItem::separator(),
&import_i,
&PredefinedMenuItem::separator(),
&rename_p,
]).unwrap();
let undo_i = MenuItem::new("Undo", true, cmd(Code::KeyZ));
let redo_i = MenuItem::new("Redo", true, cmd_shift(Code::KeyZ));
let conv_i = MenuItem::new("Convert Column to Values", true, no_acc());
let set_prop = MenuItem::new("Set Property…", true, no_acc());
let del_prop = MenuItem::new("Delete Property…", true, no_acc());
ids.register(&undo_i, Command::Undo);
ids.register(&redo_i, Command::Redo);
ids.register(&conv_i, Command::ConvertToValues);
ids.register(&set_prop, Command::RunCommand("sp".into()));
ids.register(&del_prop, Command::RunCommand("dp".into()));
let edit_menu = Submenu::with_items("Edit", true, &[
&undo_i,
&redo_i,
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::cut(None),
&PredefinedMenuItem::copy(None),
&PredefinedMenuItem::paste(None),
&PredefinedMenuItem::select_all(None),
&PredefinedMenuItem::separator(),
&conv_i,
&PredefinedMenuItem::separator(),
&set_prop,
&del_prop,
]).unwrap();
let add_sheet = MenuItem::new("Add Sheet…", true, no_acc());
let ren_sheet = MenuItem::new("Rename Sheet…", true, no_acc());
let del_sheet = MenuItem::new("Delete Sheet", true, no_acc());
let filter_i = MenuItem::new("Create Filter…", true, no_acc());
let next_sheet = MenuItem::new("Next Sheet", true, acc(Modifiers::empty(), Code::BracketRight));
let prev_sheet = MenuItem::new("Previous Sheet", true, acc(Modifiers::empty(), Code::BracketLeft));
ids.register(&add_sheet, Command::RunCommand("as".into()));
ids.register(&ren_sheet, Command::RunCommand("rs".into()));
ids.register(&del_sheet, Command::DeleteSheet);
ids.register(&filter_i, Command::RunCommand("filter".into()));
ids.register(&next_sheet, Command::NextSheet);
ids.register(&prev_sheet, Command::PrevSheet);
let sheet_shortcuts: Vec<MenuItem> = (1usize..=9).map(|n| {
let code = match n {
1 => Code::Digit1, 2 => Code::Digit2, 3 => Code::Digit3,
4 => Code::Digit4, 5 => Code::Digit5, 6 => Code::Digit6,
7 => Code::Digit7, 8 => Code::Digit8, _ => Code::Digit9,
};
MenuItem::new(format!("Go to Sheet {}", n), true, cmd(code))
}).collect();
let sheet_shortcut_refs: Vec<&dyn muda::IsMenuItem> =
sheet_shortcuts.iter().map(|i| i as &dyn muda::IsMenuItem).collect();
for (i, item) in sheet_shortcuts.iter().enumerate() {
ids.register(item, Command::GoToSheet(i));
}
let sep1 = PredefinedMenuItem::separator();
let sep2 = PredefinedMenuItem::separator();
let sep3 = PredefinedMenuItem::separator();
let mut sheet_items: Vec<&dyn muda::IsMenuItem> = vec![
&add_sheet, &ren_sheet, &del_sheet,
&sep1,
&filter_i,
&sep2,
&next_sheet, &prev_sheet,
&sep3,
];
sheet_items.extend(sheet_shortcut_refs.iter().copied());
let sheet_menu = Submenu::with_items("Sheet", true, &sheet_items).unwrap();
let add_col = MenuItem::new("Add Column…", true, cmd_shift(Code::KeyA));
let ren_col = MenuItem::new("Rename Column…", true, no_acc());
let del_col = MenuItem::new("Delete Column", true, no_acc());
let ml = MenuItem::new("Move Left", true, no_acc());
let mr = MenuItem::new("Move Right", true, no_acc());
let clr_col = MenuItem::new("Clear Column", true, no_acc());
let hide_col = MenuItem::new("Hide Column", true, no_acc());
let show_col = MenuItem::new("Show Hidden Column…", true, no_acc());
let list_hid = MenuItem::new("List Hidden Columns", true, no_acc());
let goto_i = MenuItem::new("Go To…", true, cmd(Code::KeyG));
ids.register(&add_col, Command::RunCommand("ac".into()));
ids.register(&ren_col, Command::RunCommand("rc".into()));
ids.register(&del_col, Command::DeleteColumn);
ids.register(&ml, Command::MoveColumnLeft);
ids.register(&mr, Command::MoveColumnRight);
ids.register(&clr_col, Command::ClearColumn);
ids.register(&hide_col, Command::HideColumn);
ids.register(&show_col, Command::RunCommand("sh".into()));
ids.register(&list_hid, Command::ListHidden);
ids.register(&goto_i, Command::RunCommand("g".into()));
let col_menu = Submenu::with_items("Column", true, &[
&add_col, &ren_col, &del_col,
&PredefinedMenuItem::separator(),
&ml, &mr,
&PredefinedMenuItem::separator(),
&clr_col, &hide_col, &show_col, &list_hid,
&PredefinedMenuItem::separator(),
&goto_i,
]).unwrap();
let toggle_props = MenuItem::new("Toggle Property Panel", true,
acc(Modifiers::empty(), Code::KeyP));
let zoom_in = MenuItem::new("Zoom In", true, cmd(Code::Equal));
let zoom_out = MenuItem::new("Zoom Out", true, cmd(Code::Minus));
let zoom_rst = MenuItem::new("Reset Zoom", true, cmd(Code::Digit0));
ids.register(&toggle_props, Command::TogglePropertyPanel);
ids.register(&zoom_in, Command::RunCommand("zoom_in".into()));
ids.register(&zoom_out, Command::RunCommand("zoom_out".into()));
ids.register(&zoom_rst, Command::RunCommand("zoom_reset".into()));
let view_menu = Submenu::with_items("View", true, &[
&toggle_props,
&PredefinedMenuItem::separator(),
&zoom_in, &zoom_out, &zoom_rst,
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::fullscreen(None),
]).unwrap();
let window_menu = Submenu::with_items("Window", true, &[
&PredefinedMenuItem::minimize(None),
&PredefinedMenuItem::maximize(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::bring_all_to_front(None),
]).unwrap();
let help_i = MenuItem::new("Ticker Help", true, cmd_shift(Code::Slash));
ids.register(&help_i, Command::OpenHelp);
let help_menu = Submenu::with_items("Help", true, &[
&help_i,
]).unwrap();
menu.append_items(&[
&app_menu,
&file_menu,
&edit_menu,
&sheet_menu,
&col_menu,
&view_menu,
&window_menu,
&help_menu,
]).unwrap();
(menu, ids)
}