use super::*; impl OpenCADStudio { pub(super) fn dispatch_fileops(&mut self, cmd: &str, i: usize) -> Option> { match cmd { "NEW" => return Some(Task::done(Message::TabNew)), "OPEN" => return Some(Task::done(Message::OpenFile)), "SAVE" | "QSAVE" => return Some(Task::done(Message::SaveFile)), // SAVEALL — write every open drawing that already has a file path. // Tabs without a path (never saved) are skipped with a note. "SAVEALL" => { if self.read_only { self.command_line .push_error("Read-only session (--read-only): saving is disabled."); return Some(Task::none()); } #[cfg(not(target_arch = "wasm32"))] { let mut saved = 0usize; let mut skipped = 0usize; for t in 0..self.tabs.len() { if self.tabs[t].is_start { continue; } if let Some(path) = self.tabs[t].current_path.clone() { if self .active_save_jobs .contains_key(&self.tabs[t].id) { self.command_line.push_info(&format!( "SAVEALL: {} already has a save running", path.display() )); skipped += 1; continue; } match self.save_tab_synchronously_protected( t, path.clone(), false, ) { Ok(()) => { saved += 1; } Err(e) => self.command_line.push_error(&format!( "SAVEALL: {} failed: {e}", path.display() )), } } else { skipped += 1; } } self.command_line.push_output(&format!( "SAVEALL: saved {saved} drawing(s){}.", if skipped > 0 { format!("; {skipped} need SAVEAS (no file path yet)") } else { String::new() } )); } #[cfg(target_arch = "wasm32")] { self.command_line .push_info("SAVEALL: save each tab individually in the web build."); } return Some(Task::none()); } "SAVEAS" => return Some(Task::done(Message::SaveAs)), // UNDO — step back n operations at once; bare UNDO / U is one step. cmd if cmd.starts_with("UNDO ") => { let arg = cmd["UNDO ".len()..].trim(); match arg.parse::() { Ok(0) => return Some(Task::none()), Ok(n) => return Some(Task::done(Message::UndoMany(n))), Err(_) => { self.command_line .push_error("Usage: UNDO [number of steps]"); return Some(Task::none()); } } } "UNDO" => return Some(Task::done(Message::Undo)), "REDO" => return Some(Task::done(Message::Redo)), // OOPS — restore the objects removed by the most recent ERASE, // without undoing any work done since. "OOPS" => { if self.oops_cache.is_empty() { self.command_line.push_info("OOPS: nothing to restore."); } else { let restored = std::mem::take(&mut self.oops_cache); let pending = self.begin_undo(i, "OOPS", restored.len(), true); let restored = self.tabs[i].scene.restore_erased_entities(restored); let n = restored.len(); self.tabs[i].dirty = true; self.refresh_properties(); if let Some(pending) = pending { self.commit_undo_delta(i, pending); } self.command_line .push_output(&format!("OOPS: restored {n} object(s).")); } } "CLEAR" | "CLR" => return Some(Task::done(Message::ClearScene)), "WIREFRAME" => return Some(Task::done(Message::SetWireframe(true))), // Visual-style commands. OCS renders either a wireframe or a shaded // view; the named styles map onto the closest of the two and the // chosen style is reported so the mapping is explicit. (`SOLID` is // intentionally NOT a visual-style verb — it is the 2D filled-polygon // draw command; the shaded ribbon button drives `SetWireframe`.) "VS" | "VSCURRENT" | "SHADEMODE" => { use crate::command::KeywordCommand; let c = KeywordCommand::new( "VSCURRENT", "VSCURRENT visual style [Shaded / Wireframe / Hidden / Realistic / Conceptual / X-ray]:", vec![ ("Shaded", "SHADED", None), ("Wireframe", "WIREFRAME", None), ("Hidden", "HIDDEN", None), ("Realistic", "REALISTIC", None), ("Conceptual", "CONCEPTUAL", None), ("X-Ray", "XRAY", None), ], ); self.command_line.push_info(&c.prompt()); self.tabs[i].active_cmd = Some(Box::new(c)); } // The named visual-style shortcuts still switch directly, and the // `