use crate::error::{err_number, VBError, VBResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WindowStyle {
Hide,
NormalFocus,
MinimizedFocus,
MaximizedFocus,
NormalNoFocus,
MinimizedNoFocus,
}
impl WindowStyle {
pub const ALL: [WindowStyle; 6] = [
WindowStyle::Hide,
WindowStyle::NormalFocus,
WindowStyle::MinimizedFocus,
WindowStyle::MaximizedFocus,
WindowStyle::NormalNoFocus,
WindowStyle::MinimizedNoFocus,
];
pub fn id(self) -> i64 {
match self {
WindowStyle::Hide => 0,
WindowStyle::NormalFocus => 1,
WindowStyle::MinimizedFocus => 2,
WindowStyle::MaximizedFocus => 3,
WindowStyle::NormalNoFocus => 4,
WindowStyle::MinimizedNoFocus => 6,
}
}
pub fn name(self) -> &'static str {
match self {
WindowStyle::Hide => "vbHide",
WindowStyle::NormalFocus => "vbNormalFocus",
WindowStyle::MinimizedFocus => "vbMinimizedFocus",
WindowStyle::MaximizedFocus => "vbMaximizedFocus",
WindowStyle::NormalNoFocus => "vbNormalNoFocus",
WindowStyle::MinimizedNoFocus => "vbMinimizedNoFocus",
}
}
pub fn from_id(id: i64) -> Option<Self> {
Self::ALL.into_iter().find(|style| style.id() == id)
}
pub fn from_name(name: &str) -> Option<Self> {
let name = name.trim();
Self::ALL.into_iter().find(|s| {
let full = s.name();
full.eq_ignore_ascii_case(name)
|| full.trim_start_matches("vb").eq_ignore_ascii_case(name)
})
}
}
impl Default for WindowStyle {
fn default() -> Self {
WindowStyle::MinimizedFocus
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShellRequest {
pub pathname: String,
pub window_style: WindowStyle,
}
impl ShellRequest {
pub fn new(pathname: impl Into<String>) -> Self {
Self {
pathname: pathname.into(),
window_style: WindowStyle::default(),
}
}
pub fn parse(pathname: impl Into<String>, window_style: i64) -> VBResult<Self> {
let Some(style) = WindowStyle::from_id(window_style) else {
return Err(invalid_window_style(window_style));
};
Ok(Self {
pathname: pathname.into(),
window_style: style,
})
}
pub fn with_window_style(mut self, window_style: WindowStyle) -> Self {
self.window_style = window_style;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShellRecord {
pub pathname: String,
pub window_style: WindowStyle,
}
impl ShellRecord {
pub fn of(request: &ShellRequest) -> Self {
Self {
pathname: request.pathname.clone(),
window_style: request.window_style,
}
}
}
fn invalid_window_style(raw: i64) -> VBError {
VBError::with_description(
err_number::INVALID_PROCEDURE_CALL,
format!(
"Invalid procedure call or argument: {} is not a valid window style \
(expected one of {})",
raw,
WindowStyle::ALL
.iter()
.map(|s| s.name())
.collect::<Vec<_>>()
.join(", "),
),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_match_vb6_constants() {
assert_eq!(WindowStyle::Hide.id(), 0);
assert_eq!(WindowStyle::NormalFocus.id(), 1);
assert_eq!(WindowStyle::MinimizedFocus.id(), 2);
assert_eq!(WindowStyle::MaximizedFocus.id(), 3);
assert_eq!(WindowStyle::NormalNoFocus.id(), 4);
assert_eq!(WindowStyle::MinimizedNoFocus.id(), 6);
}
#[test]
fn from_id_round_trips_every_style() {
for style in WindowStyle::ALL {
assert_eq!(WindowStyle::from_id(style.id()), Some(style));
}
}
#[test]
fn five_is_not_a_defined_style() {
assert_eq!(WindowStyle::from_id(5), None);
assert_eq!(WindowStyle::from_id(-1), None);
assert_eq!(WindowStyle::from_id(7), None);
}
#[test]
fn from_name_matches_constant_names_case_insensitively() {
assert_eq!(
WindowStyle::from_name("vbNormalFocus"),
Some(WindowStyle::NormalFocus)
);
assert_eq!(WindowStyle::from_name("VBHIDE"), Some(WindowStyle::Hide));
assert_eq!(
WindowStyle::from_name("minimizednofocus"),
Some(WindowStyle::MinimizedNoFocus)
);
assert_eq!(WindowStyle::from_name("nonsense"), None);
}
#[test]
fn new_defaults_to_minimized_with_focus() {
let request = ShellRequest::new("notepad.exe");
assert_eq!(request.window_style, WindowStyle::MinimizedFocus);
}
#[test]
fn parse_accepts_documented_styles() {
let request = ShellRequest::parse("calc.exe", 3).unwrap();
assert_eq!(request.pathname, "calc.exe");
assert_eq!(request.window_style, WindowStyle::MaximizedFocus);
}
#[test]
fn parse_rejects_undefined_styles_with_error_5() {
let err = ShellRequest::parse("calc.exe", 5).unwrap_err();
assert_eq!(err.number, err_number::INVALID_PROCEDURE_CALL);
assert!(
err.description.contains("vbMinimizedNoFocus"),
"{}",
err.description
);
}
#[test]
fn with_window_style_overrides_the_default() {
let request = ShellRequest::new("backup.bat").with_window_style(WindowStyle::Hide);
assert_eq!(request.window_style, WindowStyle::Hide);
}
#[test]
fn record_captures_the_request() {
let request = ShellRequest::new(r#""C:\Program Files\App.exe" /flag"#)
.with_window_style(WindowStyle::NormalNoFocus);
let record = ShellRecord::of(&request);
assert_eq!(record.pathname, r#""C:\Program Files\App.exe" /flag"#);
assert_eq!(record.window_style, WindowStyle::NormalNoFocus);
}
}