diff options
| author | UMTS at Teleco <crt@teleco.ch> | 2025-12-13 02:51:15 +0100 |
|---|---|---|
| committer | UMTS at Teleco <crt@teleco.ch> | 2025-12-13 02:51:15 +0100 |
| commit | 8323fdd73272a2882781aba3c499ba0be3dff2a6 (patch) | |
| tree | ffbf86473933e69cfaeef30d5c6ea7e5b494856c /src/ui/printers.rs | |
Diffstat (limited to 'src/ui/printers.rs')
| -rw-r--r-- | src/ui/printers.rs | 943 |
1 files changed, 943 insertions, 0 deletions
diff --git a/src/ui/printers.rs b/src/ui/printers.rs new file mode 100644 index 0000000..bafc445 --- /dev/null +++ b/src/ui/printers.rs @@ -0,0 +1,943 @@ +use crate::api::ApiClient; +use crate::core::components::form_builder::FormBuilder; +use crate::core::components::interactions::ConfirmDialog; +use crate::core::{ColumnConfig, TableEventHandler, TableRenderer}; +use crate::core::{EditorField, FieldType}; +use crate::ui::ribbon::RibbonUI; +use eframe::egui; +use serde_json::Value; + +const SYSTEM_PRINTER_SETTINGS_TEMPLATE: &str = r#"{ + "paper_size": "Custom", + "orientation": "landscape", + "margins": { + "top": 0.0, + "right": 0.0, + "bottom": 0.0, + "left": 0.0 + }, + "color": false, + "quality": "high", + "copies": 1, + "duplex": false, + "center": "both", + "center_disabled": false, + "scale_mode": "fit", + "scale_factor": 1.0, + "custom_width_mm": 50.8, + "custom_height_mm": 76.2, + "printer_name": null, + "show_dialog_if_unfound": true +}"#; + +const PDF_PRINTER_SETTINGS_TEMPLATE: &str = SYSTEM_PRINTER_SETTINGS_TEMPLATE; + +const SYSTEM_PRINTER_JSON_HELP: &str = r#"# System printer JSON + +Use this payload when registering the `System` printer plugin. Leave fields out to fall back to BeepZone's legacy sizing. + +## Core fields +- `paper_size` *(string)* — Named stock such as `A4`, `Letter`, `A5`, or `Custom`. +- `orientation` *(string)* — Either `portrait` or `landscape`. Selecting `landscape` rotates the page 90°; any custom width/height you supply are interpreted in the stock's natural (portrait) orientation and the app flips them automatically while printing. +- `margins` *(object in millimetres)* — Trim space on each edge with `top`, `right`, `bottom`, `left` properties. +- `scale_mode` *(string)* — Scaling behavior: `fit` (proportional fit), `fit-x` (fit width), `fit-y` (fit height), `max-both`, `max-x`, `max-y`, or `manual`. +- `scale_factor` *(number ≥ 0)* — Manual multiplier applied according to scale_mode. +- `duplex`, `color`, `quality` *(optional)* — Mirrors the underlying OS print options. +- `copies` *(number)* — Number of copies to print. +- `custom_width_mm` / `custom_height_mm` *(numbers)* — Provide both to describe bespoke media using the printer's normal portrait orientation. + +## Layout control +- `center` *("none" | "horizontal" | "vertical" | "both" | null)* — Centers content when not disabled. +- `center_disabled` *(bool)* — When `true`, ignores the `center` setting while keeping the last chosen mode for later. + +## Direct print (optional) +- `printer_name` *(string | null)* — If set, the System plugin will attempt to print directly to this OS printer by name. +- `show_dialog_if_unfound` *(bool, default: true)* — When `true` (or omitted) and the named printer can't be resolved, a lightweight popup chooser appears. Set to `false` to skip the chooser and only open the PDF viewer. +- `compatibility_mode` *(bool, default: false)* — When `true`, sends NO CUPS job options at all - only the raw PDF. Use this for severely broken printer filters (e.g., Kyocera network printers with crashing filters). The printer will use its default settings. + +## Examples + +### Custom Label Printer (e.g., ZQ510) +```json +{ + "paper_size": "Custom", + "orientation": "landscape", + "margins": { + "top": 0.0, + "right": 0.0, + "bottom": 0.0, + "left": 0.0 + }, + "scale_mode": "fit", + "scale_factor": 1.0, + "color": false, + "quality": "high", + "copies": 1, + "duplex": false, + "custom_width_mm": 50.8, + "custom_height_mm": 76.2, + "center": "both", + "center_disabled": false, + "printer_name": "ZQ510", + "show_dialog_if_unfound": true +} +``` + +### Standard A4 Office Printer +```json +{ + "paper_size": "A4", + "orientation": "portrait", + "margins": { + "top": 12.7, + "right": 12.7, + "bottom": 12.7, + "left": 12.7 + }, + "scale_mode": "fit", + "scale_factor": 1.0, + "color": true, + "quality": "high", + "copies": 1, + "duplex": false, + "center": "both", + "center_disabled": false, + "printer_name": "HP LaserJet Pro", + "show_dialog_if_unfound": true +} +``` +"#; + +const PDF_PRINTER_JSON_HELP: &str = r#"# PDF export JSON + +The PDF plugin understands the same shape as the System printer. Use the optional flags only when you want the enhanced layout controls; otherwise omit them for the classic renderer settings. + +## Typical usage +- Provide `paper_size` / `orientation` or include `custom_width_mm` + `custom_height_mm` for bespoke sheets. Enter the measurements in the stock's natural portrait orientation; landscape output is handled automatically. +- Reuse the `margins` block from your system printers so labels line up identically. +- `scale_mode`, `scale_factor`, `center`, `center_disabled` behave exactly the same as the System plugin. +- The exported file path is still chosen through the PDF save dialog; these settings only influence page geometry. + +## Available scale modes +- `fit` — Proportionally fit the design within the printable area +- `fit-x` — Fit to page width only +- `fit-y` — Fit to page height only +- `max-both` — Maximum size that fits both dimensions +- `max-x` — Maximum width scaling +- `max-y` — Maximum height scaling +- `manual` — Use exact `scale_factor` value + +## Example + +```json +{ + "paper_size": "Letter", + "orientation": "portrait", + "margins": { "top": 5.0, "right": 5.0, "bottom": 5.0, "left": 5.0 }, + "scale_mode": "manual", + "scale_factor": 0.92, + "center": "horizontal", + "center_disabled": false +} +``` +"#; + +pub struct PrintersView { + printers: Vec<serde_json::Value>, + is_loading: bool, + last_error: Option<String>, + initial_load_done: bool, + + // Table renderer + table_renderer: TableRenderer, + + // Editor dialogs + edit_dialog: FormBuilder, + add_dialog: FormBuilder, + delete_dialog: ConfirmDialog, + + // Pending operations + pending_delete_id: Option<i64>, + pending_edit_id: Option<i64>, + + // Navigation + pub switch_to_print_history: bool, + + // Track last selected plugin to detect changes + last_add_dialog_plugin: Option<String>, +} + +impl PrintersView { + pub fn new() -> Self { + let edit_dialog = Self::create_edit_dialog(); + let add_dialog = Self::create_add_dialog(); + + // Define columns for printer_settings table + let columns = vec![ + ColumnConfig::new("ID", "id").with_width(60.0).hidden(), + ColumnConfig::new("Printer Name", "printer_name").with_width(150.0), + ColumnConfig::new("Description", "description").with_width(200.0), + ColumnConfig::new("Plugin", "printer_plugin").with_width(100.0), + ColumnConfig::new("Log Prints", "log").with_width(90.0), + ColumnConfig::new("Use for Reports", "can_be_used_for_reports").with_width(120.0), + ColumnConfig::new("Min Power Level", "min_powerlevel_to_use").with_width(110.0), + ColumnConfig::new("Settings JSON", "printer_settings") + .with_width(150.0) + .hidden(), + ]; + + Self { + printers: Vec::new(), + is_loading: false, + last_error: None, + initial_load_done: false, + table_renderer: TableRenderer::new() + .with_columns(columns) + .with_default_sort("printer_name", true) + .with_search_fields(vec![ + "printer_name".to_string(), + "description".to_string(), + "printer_plugin".to_string(), + ]), + edit_dialog, + add_dialog, + delete_dialog: ConfirmDialog::new( + "Delete Printer", + "Are you sure you want to delete this printer configuration?", + ), + pending_delete_id: None, + pending_edit_id: None, + switch_to_print_history: false, + last_add_dialog_plugin: None, + } + } + + fn plugin_help_text(plugin: &str) -> Option<&'static str> { + match plugin { + "System" => Some(SYSTEM_PRINTER_JSON_HELP), + "PDF" => Some(PDF_PRINTER_JSON_HELP), + _ => None, + } + } + + fn apply_plugin_help(editor: &mut FormBuilder, plugin: Option<&str>) { + if let Some(plugin) = plugin { + if let Some(help) = Self::plugin_help_text(plugin) { + editor.form_help_text = Some(help.to_string()); + return; + } + } + editor.form_help_text = None; + } + + fn create_edit_dialog() -> FormBuilder { + let plugin_options = vec![ + ("Ptouch".to_string(), "Brother P-Touch".to_string()), + ("Brother".to_string(), "Brother (Generic)".to_string()), + ("Zebra".to_string(), "Zebra".to_string()), + ("System".to_string(), "System Printer".to_string()), + ("PDF".to_string(), "PDF Export".to_string()), + ("Network".to_string(), "Network Printer".to_string()), + ("Custom".to_string(), "Custom".to_string()), + ]; + + FormBuilder::new( + "Edit Printer", + vec![ + EditorField { + name: "id".into(), + label: "ID".into(), + field_type: FieldType::Text, + required: false, + read_only: true, + }, + EditorField { + name: "printer_name".into(), + label: "Printer Name".into(), + field_type: FieldType::Text, + required: true, + read_only: false, + }, + EditorField { + name: "description".into(), + label: "Description".into(), + field_type: FieldType::MultilineText, + required: false, + read_only: false, + }, + EditorField { + name: "printer_plugin".into(), + label: "Printer Plugin".into(), + field_type: FieldType::Dropdown(plugin_options.clone()), + required: true, + read_only: false, + }, + EditorField { + name: "log".into(), + label: "Log Print Jobs".into(), + field_type: FieldType::Checkbox, + required: false, + read_only: false, + }, + EditorField { + name: "can_be_used_for_reports".into(), + label: "Can Print Reports".into(), + field_type: FieldType::Checkbox, + required: false, + read_only: false, + }, + EditorField { + name: "min_powerlevel_to_use".into(), + label: "Minimum Power Level".into(), + field_type: FieldType::Text, + required: true, + read_only: false, + }, + EditorField { + name: "printer_settings".into(), + label: "Printer Settings Required".into(), + field_type: FieldType::MultilineText, + required: false, + read_only: false, + }, + ], + ) + } + + fn create_add_dialog() -> FormBuilder { + let plugin_options = vec![ + ("Ptouch".to_string(), "Brother P-Touch".to_string()), + ("Brother".to_string(), "Brother (Generic)".to_string()), + ("Zebra".to_string(), "Zebra".to_string()), + ("System".to_string(), "System Printer".to_string()), + ("PDF".to_string(), "PDF Export".to_string()), + ("Network".to_string(), "Network Printer".to_string()), + ("Custom".to_string(), "Custom".to_string()), + ]; + + FormBuilder::new( + "Add Printer", + vec![ + EditorField { + name: "printer_name".into(), + label: "Printer Name".into(), + field_type: FieldType::Text, + required: true, + read_only: false, + }, + EditorField { + name: "description".into(), + label: "Description".into(), + field_type: FieldType::MultilineText, + required: false, + read_only: false, + }, + EditorField { + name: "printer_plugin".into(), + label: "Printer Plugin".into(), + field_type: FieldType::Dropdown(plugin_options), + required: true, + read_only: false, + }, + EditorField { + name: "log".into(), + label: "Log Print Jobs".into(), + field_type: FieldType::Checkbox, + required: false, + read_only: false, + }, + EditorField { + name: "can_be_used_for_reports".into(), + label: "Can Print Reports".into(), + field_type: FieldType::Checkbox, + required: false, + read_only: false, + }, + EditorField { + name: "min_powerlevel_to_use".into(), + label: "Minimum Power Level".into(), + field_type: FieldType::Text, + required: true, + read_only: false, + }, + EditorField { + name: "printer_settings".into(), + label: "Printer Settings (JSON)".into(), + field_type: FieldType::MultilineText, + required: true, + read_only: false, + }, + ], + ) + } + + fn ensure_loaded(&mut self, api_client: Option<&ApiClient>) { + if self.is_loading || self.initial_load_done { + return; + } + if let Some(client) = api_client { + self.load_printers(client); + } + } + + fn load_printers(&mut self, api_client: &ApiClient) { + use crate::core::tables::get_printers; + + self.is_loading = true; + self.last_error = None; + match get_printers(api_client) { + Ok(list) => { + self.printers = list; + self.is_loading = false; + self.initial_load_done = true; + } + Err(e) => { + self.last_error = Some(e.to_string()); + self.is_loading = false; + self.initial_load_done = true; + } + } + } + + pub fn render( + &mut self, + ui: &mut egui::Ui, + api_client: Option<&ApiClient>, + ribbon_ui: Option<&mut RibbonUI>, + session_manager: &std::sync::Arc<tokio::sync::Mutex<crate::session::SessionManager>>, + ) { + self.ensure_loaded(api_client); + + // Get search query from ribbon first (before mutable borrow) + let search_query = ribbon_ui + .as_ref() + .and_then(|r| r.search_texts.get("printers_search")) + .map(|s| s.clone()) + .unwrap_or_default(); + + // Apply search to table renderer + self.table_renderer.search_query = search_query; + + // Handle ribbon actions and default printer dropdown + if let Some(ribbon) = ribbon_ui { + if ribbon + .checkboxes + .get("printers_action_add") + .copied() + .unwrap_or(false) + { + // Provide default values - printer_settings will get plugin-specific template + let default_data = serde_json::json!({ + "printer_settings": "{}", + "log": true, + "can_be_used_for_reports": false, + "min_powerlevel_to_use": "0" + }); + self.add_dialog.open(&default_data); + } + if ribbon + .checkboxes + .get("printers_action_refresh") + .copied() + .unwrap_or(false) + { + if let Some(client) = api_client { + self.load_printers(client); + } + } + if ribbon + .checkboxes + .get("printers_view_print_history") + .copied() + .unwrap_or(false) + { + self.switch_to_print_history = true; + } + + // Handle default printer dropdown (will be rendered in Settings group) + // Store selected printer ID change flag + if ribbon + .checkboxes + .get("printers_default_changed") + .copied() + .unwrap_or(false) + { + if let Some(printer_id_str) = ribbon.search_texts.get("printers_default_id") { + if printer_id_str.is_empty() { + // Clear default printer + if let Ok(mut session) = session_manager.try_lock() { + if let Err(e) = session.update_default_printer(None) { + log::error!("Failed to clear default printer: {}", e); + } else { + log::info!("Default printer cleared"); + } + } + } else if let Ok(printer_id) = printer_id_str.parse::<i64>() { + // Set default printer + if let Ok(mut session) = session_manager.try_lock() { + if let Err(e) = session.update_default_printer(Some(printer_id)) { + log::error!("Failed to update default printer: {}", e); + } else { + log::info!("Default printer set to ID: {}", printer_id); + } + } + } + } + } + } + + // Error message + let mut clear_error = false; + if let Some(err) = &self.last_error { + ui.horizontal(|ui| { + ui.colored_label(egui::Color32::RED, format!("Error: {}", err)); + if ui.button("Close").clicked() { + clear_error = true; + } + }); + ui.separator(); + } + if clear_error { + self.last_error = None; + } + + // Loading indicator + if self.is_loading { + ui.spinner(); + ui.label("Loading printers..."); + return; + } + + // Render table with event handling + self.render_table_with_events(ui, api_client); + + // Handle dialogs + self.handle_dialogs(ui, api_client); + + // Process deferred actions from context menus + self.process_deferred_actions(ui, api_client); + } + + /// Called before rendering to inject printer dropdown data into ribbon + pub fn inject_dropdown_into_ribbon( + &self, + ribbon_ui: &mut RibbonUI, + session_manager: &std::sync::Arc<tokio::sync::Mutex<crate::session::SessionManager>>, + ) { + // Try to get current default printer ID without blocking (avoid Tokio panic) + let current_default = session_manager + .try_lock() + .ok() + .and_then(|s| s.get_default_printer_id()); + + // Store current default for ribbon rendering + if let Some(id) = current_default { + ribbon_ui + .search_texts + .insert("_printers_current_default".to_string(), id.to_string()); + } else { + ribbon_ui + .search_texts + .insert("_printers_current_default".to_string(), "".to_string()); + } + + // Store printer list as JSON string for ribbon to parse + let printers_json = serde_json::to_string(&self.printers).unwrap_or_default(); + ribbon_ui + .search_texts + .insert("_printers_list".to_string(), printers_json); + } + + fn render_table_with_events(&mut self, ui: &mut egui::Ui, api_client: Option<&ApiClient>) { + let printers_clone = self.printers.clone(); + let prepared_data = self.table_renderer.prepare_json_data(&printers_clone); + + let mut deferred_actions: Vec<DeferredAction> = Vec::new(); + let mut temp_handler = TempPrintersEventHandler { + api_client, + deferred_actions: &mut deferred_actions, + }; + + self.table_renderer + .render_json_table(ui, &prepared_data, Some(&mut temp_handler)); + + self.process_temp_deferred_actions(deferred_actions, api_client); + } + + fn process_temp_deferred_actions( + &mut self, + actions: Vec<DeferredAction>, + _api_client: Option<&ApiClient>, + ) { + for action in actions { + match action { + DeferredAction::DoubleClick(printer) => { + log::info!( + "Processing double-click edit for printer: {:?}", + printer.get("printer_name") + ); + self.edit_dialog.open(&printer); + if let Some(id) = printer.get("id").and_then(|v| v.as_i64()) { + self.pending_edit_id = Some(id); + } + } + DeferredAction::ContextEdit(printer) => { + log::info!( + "Processing context menu edit for printer: {:?}", + printer.get("printer_name") + ); + self.edit_dialog.open(&printer); + if let Some(id) = printer.get("id").and_then(|v| v.as_i64()) { + self.pending_edit_id = Some(id); + } + } + DeferredAction::ContextDelete(printer) => { + let name = printer + .get("printer_name") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + let id = printer.get("id").and_then(|v| v.as_i64()).unwrap_or(-1); + log::info!("Processing context menu delete for printer: {}", name); + self.pending_delete_id = Some(id); + self.delete_dialog.open(name.to_string(), id.to_string()); + } + DeferredAction::ContextClone(printer) => { + log::info!( + "Processing context menu clone for printer: {:?}", + printer.get("printer_name") + ); + let mut cloned = crate::core::components::prepare_cloned_value( + &printer, + &["id"], + Some("printer_name"), + Some(""), + ); + if let Some(obj) = cloned.as_object_mut() { + if let Some(ps) = obj.get("printer_settings") { + let as_str = if ps.is_string() { + ps.as_str().unwrap_or("{}").to_string() + } else { + serde_json::to_string_pretty(ps) + .unwrap_or_else(|_| "{}".to_string()) + }; + obj.insert( + "printer_settings".to_string(), + serde_json::Value::String(as_str), + ); + } + self.add_dialog.open_new(Some(obj)); + } + } + } + } + } + + fn handle_dialogs(&mut self, ui: &mut egui::Ui, api_client: Option<&ApiClient>) { + // BEFORE showing add dialog, check if printer_plugin changed and auto-populate printer_settings + if self.add_dialog.show { + let current_plugin = self + .add_dialog + .data + .get("printer_plugin") + .map(|s| s.clone()); + + // Detect if plugin changed to "System" + if current_plugin != self.last_add_dialog_plugin { + if let Some(ref plugin) = current_plugin { + let template = match plugin.as_str() { + "System" => Some(SYSTEM_PRINTER_SETTINGS_TEMPLATE), + "PDF" => Some(PDF_PRINTER_SETTINGS_TEMPLATE), + _ => None, + }; + + if let Some(template) = template { + let current_settings = self + .add_dialog + .data + .get("printer_settings") + .map(|s| s.as_str()) + .unwrap_or("{}"); + + if current_settings.trim().is_empty() || current_settings.trim() == "{}" { + self.add_dialog + .data + .insert("printer_settings".to_string(), template.to_string()); + } + } + } + self.last_add_dialog_plugin = current_plugin.clone(); + } + + Self::apply_plugin_help(&mut self.add_dialog, current_plugin.as_deref()); + } else { + // Reset tracking when dialog closes + self.last_add_dialog_plugin = None; + self.add_dialog.form_help_text = None; + } + + if self.edit_dialog.show { + let edit_plugin = self + .edit_dialog + .data + .get("printer_plugin") + .map(|s| s.clone()); + Self::apply_plugin_help(&mut self.edit_dialog, edit_plugin.as_deref()); + } else { + self.edit_dialog.form_help_text = None; + } + + // Delete confirmation dialog + if let Some(confirmed) = self.delete_dialog.show_dialog(ui.ctx()) { + if confirmed { + if let (Some(id), Some(client)) = (self.pending_delete_id, api_client) { + let where_clause = serde_json::json!({"id": id}); + match client.delete("printer_settings", where_clause) { + Ok(_) => { + log::info!("Printer {} deleted successfully", id); + self.load_printers(client); + } + Err(e) => { + self.last_error = Some(format!("Failed to delete printer: {}", e)); + log::error!("Failed to delete printer: {}", e); + } + } + } + self.pending_delete_id = None; + } + } + + // Edit dialog + if let Some(Some(updated)) = self.edit_dialog.show_editor(ui.ctx()) { + if let (Some(id), Some(client)) = (self.pending_edit_id, api_client) { + let where_clause = serde_json::json!({"id": id}); + // Ensure printer_settings field is valid JSON and send as JSON object + let mut to_update = updated; + // Remove generic editor metadata keys (avoid backend invalid column errors) + let mut meta_keys: Vec<String> = to_update + .keys() + .filter(|k| k.starts_with("__editor_")) + .cloned() + .collect(); + // Also remove __editor_item_id specifically + if to_update.contains_key("__editor_item_id") { + meta_keys.push("__editor_item_id".to_string()); + } + for k in meta_keys { + to_update.remove(&k); + } + if let Some(val) = to_update.get_mut("printer_settings") { + if let Some(s) = val.as_str() { + match serde_json::from_str::<serde_json::Value>(s) { + Ok(json_val) => { + // Send as actual JSON object, not base64 string + *val = json_val; + } + Err(e) => { + self.last_error = + Some(format!("Printer Settings JSON is invalid: {}", e)); + return; + } + } + } + } + match client.update( + "printer_settings", + serde_json::Value::Object(to_update.clone()), + where_clause, + ) { + Ok(resp) => { + if resp.success { + log::info!("Printer {} updated successfully", id); + self.load_printers(client); + } else { + self.last_error = Some(format!("Update failed: {:?}", resp.error)); + log::error!("Update failed: {:?}", resp.error); + } + } + Err(e) => { + self.last_error = Some(format!("Failed to update printer: {}", e)); + log::error!("Failed to update printer: {}", e); + } + } + self.pending_edit_id = None; + } + } + + // Add dialog + if let Some(Some(new_data)) = self.add_dialog.show_editor(ui.ctx()) { + if let Some(client) = api_client { + // Parse printer_settings JSON and send as JSON object + let mut payload = new_data; + // Strip any editor metadata that may have leaked in + let meta_strip: Vec<String> = payload + .keys() + .filter(|k| k.starts_with("__editor_")) + .cloned() + .collect(); + for k in meta_strip { + payload.remove(&k); + } + if let Some(val) = payload.get_mut("printer_settings") { + if let Some(s) = val.as_str() { + match serde_json::from_str::<serde_json::Value>(s) { + Ok(json_val) => { + // Send as actual JSON object, not base64 string + *val = json_val; + } + Err(e) => { + self.last_error = + Some(format!("Printer Settings JSON is invalid: {}", e)); + return; + } + } + } + } + match client.insert( + "printer_settings", + serde_json::Value::Object(payload.clone()), + ) { + Ok(resp) => { + if resp.success { + log::info!("Printer added successfully"); + self.load_printers(client); + } else { + self.last_error = Some(format!("Insert failed: {:?}", resp.error)); + log::error!("Insert failed: {:?}", resp.error); + } + } + Err(e) => { + self.last_error = Some(format!("Failed to add printer: {}", e)); + log::error!("Failed to add printer: {}", e); + } + } + } + } + } + + fn process_deferred_actions(&mut self, ui: &mut egui::Ui, _api_client: Option<&ApiClient>) { + // Handle double-click edit + if let Some(printer) = ui + .ctx() + .data_mut(|d| d.remove_temp::<Value>(egui::Id::new("printer_double_click_edit"))) + { + log::info!( + "Processing double-click edit for printer: {:?}", + printer.get("printer_name") + ); + self.edit_dialog.open(&printer); + if let Some(id) = printer.get("id").and_then(|v| v.as_i64()) { + self.pending_edit_id = Some(id); + } + } + + // Handle context menu actions + if let Some(printer) = ui + .ctx() + .data_mut(|d| d.remove_temp::<Value>(egui::Id::new("printer_context_menu_edit"))) + { + log::info!( + "Processing context menu edit for printer: {:?}", + printer.get("printer_name") + ); + self.edit_dialog.open(&printer); + if let Some(id) = printer.get("id").and_then(|v| v.as_i64()) { + self.pending_edit_id = Some(id); + } + } + + if let Some(printer) = ui + .ctx() + .data_mut(|d| d.remove_temp::<Value>(egui::Id::new("printer_context_menu_delete"))) + { + let name = printer + .get("printer_name") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + let id = printer.get("id").and_then(|v| v.as_i64()).unwrap_or(-1); + log::info!("Processing context menu delete for printer: {}", name); + self.pending_delete_id = Some(id); + self.delete_dialog.open(name.to_string(), id.to_string()); + } + } +} + +impl Default for PrintersView { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone)] +enum DeferredAction { + DoubleClick(Value), + ContextEdit(Value), + ContextDelete(Value), + ContextClone(Value), +} + +// Temporary event handler that collects actions for later processing +struct TempPrintersEventHandler<'a> { + #[allow(dead_code)] + api_client: Option<&'a ApiClient>, + deferred_actions: &'a mut Vec<DeferredAction>, +} + +impl<'a> TableEventHandler<Value> for TempPrintersEventHandler<'a> { + fn on_double_click(&mut self, item: &Value, _row_index: usize) { + log::info!( + "Double-click detected on printer: {:?}", + item.get("printer_name") + ); + self.deferred_actions + .push(DeferredAction::DoubleClick(item.clone())); + } + + fn on_context_menu(&mut self, ui: &mut egui::Ui, item: &Value, _row_index: usize) { + if ui + .button(format!("{} Edit Printer", egui_phosphor::regular::PENCIL)) + .clicked() + { + log::info!( + "Context menu edit clicked for printer: {:?}", + item.get("printer_name") + ); + self.deferred_actions + .push(DeferredAction::ContextEdit(item.clone())); + ui.close(); + } + + ui.separator(); + + if ui + .button(format!("{} Clone Printer", egui_phosphor::regular::COPY)) + .clicked() + { + log::info!( + "Context menu clone clicked for printer: {:?}", + item.get("printer_name") + ); + self.deferred_actions + .push(DeferredAction::ContextClone(item.clone())); + ui.close(); + } + + ui.separator(); + + if ui + .button(format!("{} Delete Printer", egui_phosphor::regular::TRASH)) + .clicked() + { + log::info!( + "Context menu delete clicked for printer: {:?}", + item.get("printer_name") + ); + self.deferred_actions + .push(DeferredAction::ContextDelete(item.clone())); + ui.close(); + } + } + + fn on_selection_changed(&mut self, selected_indices: &[usize]) { + log::debug!("Printer selection changed: {:?}", selected_indices); + } +} |
