use serde::{Deserialize, Serialize};
use std::fmt;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Modifier {
None,
Ctrl,
Alt,
Shift,
CtrlAlt,
CtrlShift,
AltShift,
CtrlAltShift,
}
impl fmt::Display for Modifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Modifier::None => "",
Modifier::Ctrl => "Ctrl",
Modifier::Alt => "Alt",
Modifier::Shift => "Shift",
Modifier::CtrlAlt => "Ctrl+Alt",
Modifier::CtrlShift => "Ctrl+Shift",
Modifier::AltShift => "Alt+Shift",
Modifier::CtrlAltShift => "Ctrl+Alt+Shift",
};
write!(f, "{}", s)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Hotkey {
pub modifier: Modifier,
pub key: char,
}
impl fmt::Display for Hotkey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.modifier == Modifier::None {
write!(f, "{}", self.key)
} else {
write!(f, "{}+{}", self.modifier, self.key)
}
}
}
impl FromStr for Hotkey {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.is_empty() {
return Err("empty hotkey".to_string());
}
let parts: Vec<&str> = s.split('+').map(|p| p.trim()).collect();
if parts.is_empty() {
return Err("invalid hotkey".to_string());
}
let last = parts.last().unwrap();
let ch = last
.chars()
.next()
.ok_or_else(|| "no key char".to_string())?;
let mods = &parts[..parts.len().saturating_sub(1)];
let modifier = match mods.len() {
0 => Modifier::None,
1 => match mods[0].to_lowercase().as_str() {
"ctrl" | "control" => Modifier::Ctrl,
"alt" => Modifier::Alt,
"shift" => Modifier::Shift,
other => return Err(format!("unknown modifier '{}'", other)),
},
2 => {
let a = mods[0].to_lowercase();
let b = mods[1].to_lowercase();
if (a == "ctrl" && b == "alt") || (a == "alt" && b == "ctrl") {
Modifier::CtrlAlt
} else if (a == "ctrl" && b == "shift") || (a == "shift" && b == "ctrl") {
Modifier::CtrlShift
} else if (a == "alt" && b == "shift") || (a == "shift" && b == "alt") {
Modifier::AltShift
} else {
return Err(format!("unknown modifier combo '{:?}'", mods));
}
}
3 => {
let mut lowers: Vec<String> = mods.iter().map(|m| m.to_lowercase()).collect();
lowers.sort();
if lowers == ["alt".to_string(), "ctrl".to_string(), "shift".to_string()] {
Modifier::CtrlAltShift
} else {
return Err(format!("unknown modifier combo '{:?}'", mods));
}
}
_ => return Err(format!("too many modifiers: {:?}", mods)),
};
Ok(Hotkey { modifier, key: ch })
}
}
impl Hotkey {
pub fn new(modifier: Modifier, key: char) -> Self {
Self { modifier, key }
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Hotkeys {
pub fft: Option<Hotkey>,
pub math: Option<Hotkey>,
pub fit_view: Option<Hotkey>,
pub fit_view_cont: Option<Hotkey>,
pub traces: Option<Hotkey>,
pub thresholds: Option<Hotkey>,
pub pause: Option<Hotkey>,
pub save_png: Option<Hotkey>,
pub export_data: Option<Hotkey>,
pub reset_markers: Option<Hotkey>,
}
impl Default for Hotkeys {
fn default() -> Self {
Self {
fft: Some(Hotkey::new(Modifier::Ctrl, 'F')),
math: Some(Hotkey::new(Modifier::None, 'M')),
fit_view: Some(Hotkey::new(Modifier::None, 'F')),
fit_view_cont: Some(Hotkey::new(Modifier::None, 'C')),
traces: Some(Hotkey::new(Modifier::None, 'T')),
thresholds: Some(Hotkey::new(Modifier::Ctrl, 'T')),
pause: Some(Hotkey::new(Modifier::None, 'P')),
save_png: Some(Hotkey::new(Modifier::None, 'S')),
export_data: Some(Hotkey::new(Modifier::None, 'E')),
reset_markers: Some(Hotkey::new(Modifier::None, 'R')),
}
}
}
impl Hotkeys {
pub fn reset_defaults(&mut self) {
*self = Hotkeys::default();
}
pub fn save_to_default_path(&self) -> Result<(), String> {
let home = std::env::var("HOME").map_err(|e| format!("HOME env var not set: {}", e))?;
let dir = PathBuf::from(home).join(".liveplot");
if let Err(e) = fs::create_dir_all(&dir) {
return Err(format!("Failed to create dir {:?}: {}", dir, e));
}
let path = dir.join("hotkeys.yaml");
let s = serde_yaml::to_string(self).map_err(|e| format!("Serialization error: {}", e))?;
let mut f = fs::File::create(&path)
.map_err(|e| format!("Failed to create file {:?}: {}", path, e))?;
f.write_all(s.as_bytes())
.map_err(|e| format!("Failed to write file {:?}: {}", path, e))?;
Ok(())
}
pub fn load_from_default_path() -> Result<Hotkeys, String> {
let home = std::env::var("HOME").map_err(|e| format!("HOME env var not set: {}", e))?;
let path = PathBuf::from(home).join(".liveplot").join("hotkeys.yaml");
if !path.exists() {
return Err(format!("Hotkeys file {:?} does not exist", path));
}
let s =
fs::read_to_string(&path).map_err(|e| format!("Failed to read {:?}: {}", path, e))?;
let hk: Hotkeys =
serde_yaml::from_str(&s).map_err(|e| format!("Deserialization error: {}", e))?;
Ok(hk)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum HotkeyName {
Fft,
Math,
FitView,
FitViewCont,
Pause,
Traces,
Thresholds,
SavePng,
ExportData,
ResetMarkers,
}