oxidize_pdf/actions/
launch_action.rs

1//! Launch actions for opening applications and documents
2
3use crate::objects::{Dictionary, Object};
4
5/// Launch parameters for Windows
6#[derive(Debug, Clone)]
7pub struct WindowsLaunchParams {
8    /// File name
9    pub file_name: String,
10    /// Default directory
11    pub default_directory: Option<String>,
12    /// Operation (open, print, etc.)
13    pub operation: Option<String>,
14    /// Parameters
15    pub parameters: Option<String>,
16}
17
18/// Launch parameters
19#[derive(Debug, Clone)]
20pub enum LaunchParameters {
21    /// Simple parameters string
22    Simple(String),
23    /// Windows-specific parameters
24    Windows(WindowsLaunchParams),
25}
26
27/// Launch action - launch an application
28#[derive(Debug, Clone)]
29pub struct LaunchAction {
30    /// Application or document to launch
31    pub file: String,
32    /// Launch parameters
33    pub parameters: Option<LaunchParameters>,
34    /// Whether to open in new window
35    pub new_window: Option<bool>,
36}
37
38impl LaunchAction {
39    /// Create new launch action
40    pub fn new(file: impl Into<String>) -> Self {
41        Self {
42            file: file.into(),
43            parameters: None,
44            new_window: None,
45        }
46    }
47
48    /// Launch application
49    pub fn application(app: impl Into<String>) -> Self {
50        Self::new(app)
51    }
52
53    /// Launch document
54    pub fn document(path: impl Into<String>) -> Self {
55        Self::new(path)
56    }
57
58    /// Set simple parameters
59    pub fn with_params(mut self, params: impl Into<String>) -> Self {
60        self.parameters = Some(LaunchParameters::Simple(params.into()));
61        self
62    }
63
64    /// Set Windows-specific parameters
65    pub fn with_windows_params(mut self, params: WindowsLaunchParams) -> Self {
66        self.parameters = Some(LaunchParameters::Windows(params));
67        self
68    }
69
70    /// Set whether to open in new window
71    pub fn in_new_window(mut self, new_window: bool) -> Self {
72        self.new_window = Some(new_window);
73        self
74    }
75
76    /// Convert to dictionary
77    pub fn to_dict(&self) -> Dictionary {
78        let mut dict = Dictionary::new();
79        dict.set("Type", Object::Name("Action".to_string()));
80        dict.set("S", Object::Name("Launch".to_string()));
81
82        // File specification
83        dict.set("F", Object::String(self.file.clone()));
84
85        // Parameters
86        if let Some(params) = &self.parameters {
87            match params {
88                LaunchParameters::Simple(p) => {
89                    dict.set("P", Object::String(p.clone()));
90                }
91                LaunchParameters::Windows(wp) => {
92                    let mut win_dict = Dictionary::new();
93                    win_dict.set("F", Object::String(wp.file_name.clone()));
94
95                    if let Some(dir) = &wp.default_directory {
96                        win_dict.set("D", Object::String(dir.clone()));
97                    }
98                    if let Some(op) = &wp.operation {
99                        win_dict.set("O", Object::String(op.clone()));
100                    }
101                    if let Some(params) = &wp.parameters {
102                        win_dict.set("P", Object::String(params.clone()));
103                    }
104
105                    dict.set("Win", Object::Dictionary(win_dict));
106                }
107            }
108        }
109
110        // New window flag
111        if let Some(nw) = self.new_window {
112            dict.set("NewWindow", Object::Boolean(nw));
113        }
114
115        dict
116    }
117}
118
119impl WindowsLaunchParams {
120    /// Create new Windows launch parameters
121    pub fn new(file_name: impl Into<String>) -> Self {
122        Self {
123            file_name: file_name.into(),
124            default_directory: None,
125            operation: None,
126            parameters: None,
127        }
128    }
129
130    /// Set default directory
131    pub fn with_directory(mut self, dir: impl Into<String>) -> Self {
132        self.default_directory = Some(dir.into());
133        self
134    }
135
136    /// Set operation (open, print, etc.)
137    pub fn with_operation(mut self, op: impl Into<String>) -> Self {
138        self.operation = Some(op.into());
139        self
140    }
141
142    /// Set parameters
143    pub fn with_parameters(mut self, params: impl Into<String>) -> Self {
144        self.parameters = Some(params.into());
145        self
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_launch_action_simple() {
155        let action = LaunchAction::application("notepad.exe");
156        let dict = action.to_dict();
157
158        assert_eq!(dict.get("S"), Some(&Object::Name("Launch".to_string())));
159        assert_eq!(
160            dict.get("F"),
161            Some(&Object::String("notepad.exe".to_string()))
162        );
163    }
164
165    #[test]
166    fn test_launch_action_with_params() {
167        let action = LaunchAction::document("document.txt")
168            .with_params("/p")
169            .in_new_window(true);
170
171        let dict = action.to_dict();
172
173        assert_eq!(
174            dict.get("F"),
175            Some(&Object::String("document.txt".to_string()))
176        );
177        assert_eq!(dict.get("P"), Some(&Object::String("/p".to_string())));
178        assert_eq!(dict.get("NewWindow"), Some(&Object::Boolean(true)));
179    }
180
181    #[test]
182    fn test_windows_launch_params() {
183        let win_params = WindowsLaunchParams::new("cmd.exe")
184            .with_directory("C:\\Windows\\System32")
185            .with_operation("open")
186            .with_parameters("/c dir");
187
188        let action = LaunchAction::new("cmd.exe").with_windows_params(win_params);
189
190        let dict = action.to_dict();
191
192        assert!(dict.get("Win").is_some());
193        if let Some(Object::Dictionary(win_dict)) = dict.get("Win") {
194            assert_eq!(
195                win_dict.get("F"),
196                Some(&Object::String("cmd.exe".to_string()))
197            );
198            assert_eq!(
199                win_dict.get("D"),
200                Some(&Object::String("C:\\Windows\\System32".to_string()))
201            );
202            assert_eq!(win_dict.get("O"), Some(&Object::String("open".to_string())));
203            assert_eq!(
204                win_dict.get("P"),
205                Some(&Object::String("/c dir".to_string()))
206            );
207        }
208    }
209
210    #[test]
211    fn test_launch_action_document() {
212        let action = LaunchAction::document("report.pdf");
213        assert_eq!(action.file, "report.pdf");
214        assert!(action.parameters.is_none());
215        assert!(action.new_window.is_none());
216    }
217
218    #[test]
219    fn test_launch_action_application() {
220        let action = LaunchAction::application("firefox");
221        assert_eq!(action.file, "firefox");
222        assert!(action.parameters.is_none());
223        assert!(action.new_window.is_none());
224    }
225
226    #[test]
227    fn test_launch_parameters_simple() {
228        let params = LaunchParameters::Simple("--help".to_string());
229        match params {
230            LaunchParameters::Simple(p) => assert_eq!(p, "--help"),
231            _ => panic!("Expected Simple parameters"),
232        }
233    }
234
235    #[test]
236    fn test_launch_parameters_windows() {
237        let win_params = WindowsLaunchParams::new("explorer.exe");
238        let params = LaunchParameters::Windows(win_params);
239        match params {
240            LaunchParameters::Windows(wp) => assert_eq!(wp.file_name, "explorer.exe"),
241            _ => panic!("Expected Windows parameters"),
242        }
243    }
244
245    #[test]
246    fn test_windows_params_minimal() {
247        let win_params = WindowsLaunchParams::new("calc.exe");
248        assert_eq!(win_params.file_name, "calc.exe");
249        assert!(win_params.default_directory.is_none());
250        assert!(win_params.operation.is_none());
251        assert!(win_params.parameters.is_none());
252    }
253
254    #[test]
255    fn test_windows_params_with_directory_only() {
256        let win_params = WindowsLaunchParams::new("app.exe").with_directory("C:\\Program Files");
257        assert_eq!(win_params.file_name, "app.exe");
258        assert_eq!(
259            win_params.default_directory,
260            Some("C:\\Program Files".to_string())
261        );
262        assert!(win_params.operation.is_none());
263        assert!(win_params.parameters.is_none());
264    }
265
266    #[test]
267    fn test_windows_params_with_operation_only() {
268        let win_params = WindowsLaunchParams::new("document.pdf").with_operation("print");
269        assert_eq!(win_params.file_name, "document.pdf");
270        assert!(win_params.default_directory.is_none());
271        assert_eq!(win_params.operation, Some("print".to_string()));
272        assert!(win_params.parameters.is_none());
273    }
274
275    #[test]
276    fn test_launch_action_without_new_window() {
277        let action = LaunchAction::new("app.exe");
278        let dict = action.to_dict();
279
280        // NewWindow should not be present when not set
281        assert!(dict.get("NewWindow").is_none());
282    }
283
284    #[test]
285    fn test_launch_action_with_false_new_window() {
286        let action = LaunchAction::new("app.exe").in_new_window(false);
287        let dict = action.to_dict();
288
289        assert_eq!(dict.get("NewWindow"), Some(&Object::Boolean(false)));
290    }
291
292    #[test]
293    fn test_launch_action_dict_type_fields() {
294        let action = LaunchAction::new("test.exe");
295        let dict = action.to_dict();
296
297        // Verify required Type and S fields
298        assert_eq!(dict.get("Type"), Some(&Object::Name("Action".to_string())));
299        assert_eq!(dict.get("S"), Some(&Object::Name("Launch".to_string())));
300    }
301
302    #[test]
303    fn test_windows_params_partial_dict() {
304        // Test Windows params with only some fields set
305        let win_params = WindowsLaunchParams::new("app.exe").with_parameters("/silent");
306
307        let action = LaunchAction::new("app.exe").with_windows_params(win_params);
308        let dict = action.to_dict();
309
310        if let Some(Object::Dictionary(win_dict)) = dict.get("Win") {
311            assert_eq!(
312                win_dict.get("F"),
313                Some(&Object::String("app.exe".to_string()))
314            );
315            assert!(win_dict.get("D").is_none()); // Directory not set
316            assert!(win_dict.get("O").is_none()); // Operation not set
317            assert_eq!(
318                win_dict.get("P"),
319                Some(&Object::String("/silent".to_string()))
320            );
321        } else {
322            panic!("Expected Win dictionary");
323        }
324    }
325}