1#[cfg(feature = "spreadsheet")]
7use crate::button::MaterialButton;
8#[cfg(feature = "spreadsheet")]
9use crate::theme::get_global_color;
10#[cfg(feature = "spreadsheet")]
11use std::collections::{HashMap, HashSet};
12#[cfg(feature = "spreadsheet")]
13use std::path::PathBuf;
14#[cfg(feature = "spreadsheet")]
15use std::sync::{Arc, Mutex};
16
17#[cfg(feature = "spreadsheet")]
18use duckdb::{params, Connection, Result as DuckResult};
19#[cfg(feature = "spreadsheet")]
20use egui::{
21 Color32, FontFamily, FontId, Id, Rect, Response, Sense, TextEdit, Ui, Vec2, Widget,
22};
23#[cfg(feature = "spreadsheet")]
24use egui_async::{Bind, StateWithData};
25
26#[cfg(feature = "spreadsheet")]
28pub use egui_extras::{Column, TableBuilder};
29
30#[cfg(feature = "spreadsheet")]
32#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
33pub struct ColumnDef {
34 pub name: String,
35 pub col_type: ColumnType,
36 pub width: f32,
37}
38
39#[cfg(feature = "spreadsheet")]
41#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
42pub enum ColumnType {
43 Text,
44 Integer,
45 Real,
46 Boolean,
47}
48
49#[cfg(feature = "spreadsheet")]
50impl ColumnType {
51 fn to_sql(&self) -> &'static str {
52 match self {
53 ColumnType::Text => "TEXT",
54 ColumnType::Integer => "INTEGER",
55 ColumnType::Real => "REAL",
56 ColumnType::Boolean => "BOOLEAN",
57 }
58 }
59}
60
61#[cfg(feature = "spreadsheet")]
63#[derive(Clone, Debug)]
64pub struct RowData {
65 pub id: usize,
66 pub values: Vec<String>,
67}
68
69#[cfg(feature = "spreadsheet")]
71#[derive(Clone, Debug, PartialEq)]
72pub enum FileFormat {
73 Csv,
74 Excel,
75 Parquet,
76 Arrow,
77}
78
79#[cfg(feature = "spreadsheet")]
80impl FileFormat {
81 pub fn from_path(path: &std::path::Path) -> Option<Self> {
82 path.extension()?.to_str().and_then(|ext| match ext.to_lowercase().as_str() {
83 "csv" => Some(FileFormat::Csv),
84 "xls" | "xlsx" => Some(FileFormat::Excel),
85 "parquet" => Some(FileFormat::Parquet),
86 "arrow" => Some(FileFormat::Arrow),
87 _ => None,
88 })
89 }
90}
91
92#[cfg(feature = "spreadsheet")]
94pub struct SpreadsheetDataModel {
95 conn: Connection,
96 table_name: String,
97 columns: Vec<ColumnDef>,
98 row_count: usize,
99}
100
101#[cfg(feature = "spreadsheet")]
102impl SpreadsheetDataModel {
103 pub fn new(columns: Vec<ColumnDef>) -> DuckResult<Self> {
105 let conn = Connection::open_in_memory()?;
106 let table_name = "spreadsheet_data".to_string();
107
108 let mut model = Self {
109 conn,
110 table_name: table_name.clone(),
111 columns: columns.clone(),
112 row_count: 0,
113 };
114
115 model.create_table()?;
116 Ok(model)
117 }
118
119 fn create_table(&mut self) -> DuckResult<()> {
120 let mut col_defs = vec!["id INTEGER PRIMARY KEY".to_string()];
121 for (idx, col) in self.columns.iter().enumerate() {
122 col_defs.push(format!("col{} {}", idx, col.col_type.to_sql()));
123 }
124
125 let create_sql = format!(
126 "CREATE TABLE IF NOT EXISTS {} ({})",
127 self.table_name,
128 col_defs.join(", ")
129 );
130
131 self.conn.execute_batch(&create_sql)?;
132 Ok(())
133 }
134
135 pub fn insert_rows(&mut self, rows: Vec<Vec<String>>) -> DuckResult<()> {
137 for row_values in rows {
138 self.insert_row(row_values)?;
139 }
140 Ok(())
141 }
142
143 pub fn insert_row(&mut self, values: Vec<String>) -> DuckResult<()> {
145 let placeholders: Vec<_> = (0..=self.columns.len()).map(|_| "?").collect();
146 let insert_sql = format!(
147 "INSERT INTO {} VALUES ({})",
148 self.table_name,
149 placeholders.join(", ")
150 );
151
152 let mut stmt = self.conn.prepare(&insert_sql)?;
153 let row_id = self.row_count;
154 self.row_count += 1;
155
156 let mut params_vec: Vec<Box<dyn duckdb::ToSql>> = vec![Box::new(row_id)];
158 for value in values {
159 params_vec.push(Box::new(value));
160 }
161
162 let params_refs: Vec<&dyn duckdb::ToSql> = params_vec.iter().map(|b| &**b as &dyn duckdb::ToSql).collect();
164 stmt.execute(params_refs.as_slice())?;
165
166 Ok(())
167 }
168
169 pub fn query_rows(&self) -> DuckResult<Vec<RowData>> {
171 let query_sql = format!("SELECT * FROM {} ORDER BY id", self.table_name);
172 let mut stmt = self.conn.prepare(&query_sql)?;
173
174 let col_count = self.columns.len();
175 let rows = stmt.query_map([], |row| {
176 let id: usize = row.get(0)?;
177 let mut values = Vec::new();
178 for i in 0..col_count {
179 let val: String = match row.get_ref(i + 1) {
181 Ok(val_ref) => {
182 use duckdb::types::ValueRef;
183 match val_ref {
184 ValueRef::Null => String::new(),
185 ValueRef::Boolean(b) => b.to_string(),
186 ValueRef::TinyInt(i) => i.to_string(),
187 ValueRef::SmallInt(i) => i.to_string(),
188 ValueRef::Int(i) => i.to_string(),
189 ValueRef::BigInt(i) => i.to_string(),
190 ValueRef::HugeInt(i) => i.to_string(),
191 ValueRef::UTinyInt(i) => i.to_string(),
192 ValueRef::USmallInt(i) => i.to_string(),
193 ValueRef::UInt(i) => i.to_string(),
194 ValueRef::UBigInt(i) => i.to_string(),
195 ValueRef::Float(f) => f.to_string(),
196 ValueRef::Double(f) => f.to_string(),
197 ValueRef::Text(s) => String::from_utf8_lossy(s).to_string(),
198 ValueRef::Blob(b) => format!("<blob {} bytes>", b.len()),
199 _ => row.get::<_, String>(i + 1).unwrap_or_default(),
200 }
201 }
202 Err(_) => String::new(),
203 };
204 values.push(val);
205 }
206 Ok(RowData { id, values })
207 })?;
208
209 let mut result = Vec::new();
210 for row in rows {
211 result.push(row?);
212 }
213 Ok(result)
214 }
215
216 pub fn update_cell(&mut self, row_id: usize, col_idx: usize, value: String) -> Result<(), String> {
218 if col_idx < self.columns.len() {
220 let col_type = &self.columns[col_idx].col_type;
221
222 match col_type {
224 ColumnType::Integer => {
225 if !value.is_empty() && value.parse::<i64>().is_err() {
226 return Err(format!("'{}' is not a valid integer", value));
227 }
228 }
229 ColumnType::Real => {
230 if !value.is_empty() && value.parse::<f64>().is_err() {
231 return Err(format!("'{}' is not a valid number", value));
232 }
233 }
234 ColumnType::Boolean => {
235 if !value.is_empty() && value.parse::<bool>().is_err() {
236 let lower = value.to_lowercase();
238 if lower != "true" && lower != "false" && lower != "1" && lower != "0" {
239 return Err(format!("'{}' is not a valid boolean (use true/false or 1/0)", value));
240 }
241 }
242 }
243 ColumnType::Text => {} }
245 }
246
247 let update_sql = format!(
248 "UPDATE {} SET col{} = ? WHERE id = ?",
249 self.table_name, col_idx
250 );
251 self.conn.execute(&update_sql, params![value, row_id])
252 .map_err(|e| e.to_string())?;
253 Ok(())
254 }
255
256 pub fn delete_row(&mut self, row_id: usize) -> DuckResult<()> {
258 let delete_sql = format!("DELETE FROM {} WHERE id = ?", self.table_name);
259 self.conn.execute(&delete_sql, params![row_id])?;
260 Ok(())
261 }
262
263 pub fn export_csv(&self, path: &std::path::Path) -> Result<(), String> {
265 use std::fs::File;
266 use std::io::Write;
267
268 let rows = self.query_rows().map_err(|e| e.to_string())?;
269 let mut file = File::create(path).map_err(|e| e.to_string())?;
270
271 let header: Vec<String> = self.columns.iter().map(|c| c.name.clone()).collect();
273 writeln!(file, "{}", header.join(",")).map_err(|e| e.to_string())?;
274
275 for row in rows {
277 writeln!(file, "{}", row.values.join(",")).map_err(|e| e.to_string())?;
278 }
279
280 Ok(())
281 }
282
283 pub fn import_csv(&mut self, path: &std::path::Path) -> Result<(), String> {
285 use std::fs::File;
286 use std::io::BufRead;
287
288 let file = File::open(path).map_err(|e| format!("Cannot open file: {}", e))?;
289 let reader = std::io::BufReader::new(file);
290 let all_lines: Vec<String> = reader.lines()
291 .collect::<Result<Vec<_>, _>>()
292 .map_err(|e| format!("Failed to read file: {}", e))?;
293
294 if all_lines.is_empty() {
295 return Err("CSV file is empty".to_string());
296 }
297
298 if all_lines.len() < 2 {
299 return Err("CSV file has only one line".to_string());
300 }
301
302 let first_line = &all_lines[0];
303 let second_line = &all_lines[1];
304 let last_line = all_lines.last().unwrap();
305
306 let delimiters = [',', ';', '\t'];
308 let mut best_delimiter = ',';
309 let mut best_score = 0;
310
311 for &delim in &delimiters {
312 let count1 = first_line.matches(delim).count();
313 let count2 = second_line.matches(delim).count();
314 let count_last = last_line.matches(delim).count();
315
316 if count1 > 0 && count1 == count2 && count2 == count_last {
318 let score = count1 * 100;
320 if score > best_score {
321 best_score = score;
322 best_delimiter = delim;
323 }
324 } else if count1 > 0 && count2 > 0 {
325 let min_count = count1.min(count2).min(count_last);
327 let max_count = count1.max(count2).max(count_last);
328 if max_count > 0 && min_count > 0 {
329 let score = (min_count * 50) / max_count;
330 if score > best_score {
331 best_score = score;
332 best_delimiter = delim;
333 }
334 }
335 }
336 }
337
338 let delimiter = best_delimiter;
339 let delimiter_name = match delimiter {
340 ',' => "comma",
341 ';' => "semicolon",
342 '\t' => "tab",
343 _ => "unknown",
344 };
345
346 let first_values: Vec<&str> = first_line.split(delimiter).collect();
347 let second_values: Vec<&str> = second_line.split(delimiter).collect();
348
349 let col_count = first_values.len();
350
351 let first_line_is_header = first_values.iter().zip(second_values.iter()).any(|(v1, v2)| {
353 let v1_is_num = v1.trim().parse::<f64>().is_ok();
354 let v2_is_num = v2.trim().parse::<f64>().is_ok();
355 v1_is_num != v2_is_num
356 });
357
358 let new_columns: Vec<ColumnDef> = if first_line_is_header {
360 first_values.iter().enumerate().map(|(i, name)| {
361 ColumnDef {
362 name: name.trim().to_string(),
363 col_type: ColumnType::Text,
364 width: 100.0,
365 }
366 }).collect()
367 } else {
368 (0..col_count).map(|i| {
369 ColumnDef {
370 name: format!("column{}", i + 1),
371 col_type: ColumnType::Text,
372 width: 100.0,
373 }
374 }).collect()
375 };
376
377 eprintln!("Detected {} columns with {} delimiter", col_count, delimiter_name);
378 eprintln!("First line is header: {}", first_line_is_header);
379
380 self.columns = new_columns;
382 self.conn.execute_batch(&format!("DROP TABLE IF EXISTS {}", self.table_name))
383 .map_err(|e| format!("Failed to drop table: {}", e))?;
384 self.row_count = 0;
385 self.create_table()
386 .map_err(|e| format!("Failed to create table: {}", e))?;
387
388 let start_idx = if first_line_is_header { 1 } else { 0 };
390 let data_lines: Vec<&String> = all_lines.iter()
391 .skip(start_idx)
392 .filter(|line| !line.trim().is_empty())
393 .collect();
394
395 for (idx, line) in data_lines.iter().enumerate() {
397 let values: Vec<String> = line.split(delimiter).map(|s| s.trim().to_string()).collect();
398
399 if values.len() != col_count {
401 return Err(format!(
402 "CSV row {} has {} columns, but expected {} columns",
403 idx + if first_line_is_header { 2 } else { 1 },
404 values.len(),
405 col_count
406 ));
407 }
408
409 self.insert_row(values)
410 .map_err(|e| format!("Failed to insert row {}: {}", idx + 1, e))?;
411 }
412
413 eprintln!("Successfully imported {} rows from CSV", data_lines.len());
414 Ok(())
415 }
416
417 pub fn export_parquet(&self, path: &std::path::Path) -> Result<(), String> {
419 let export_sql = format!(
420 "COPY {} TO '{}' (FORMAT PARQUET)",
421 self.table_name,
422 path.to_string_lossy()
423 );
424 self.conn
425 .execute_batch(&export_sql)
426 .map_err(|e| e.to_string())?;
427 Ok(())
428 }
429
430 pub fn import_parquet(&mut self, path: &std::path::Path) -> Result<(), String> {
432 self.conn.execute(&format!("DELETE FROM {}", self.table_name), [])
434 .map_err(|e| format!("Failed to clear table: {}", e))?;
435 self.row_count = 0;
436
437 let query_sql = format!("SELECT * FROM read_parquet('{}')", path.to_string_lossy());
439 let mut stmt = self.conn.prepare(&query_sql)
440 .map_err(|e| format!("Failed to read parquet file: {}", e))?;
441
442 let col_count = self.columns.len();
443 let rows = stmt.query_map([], |row| {
444 let mut values = Vec::new();
445 for i in 0..col_count {
446 let val: String = match row.get_ref(i) {
448 Ok(val_ref) => {
449 use duckdb::types::ValueRef;
450 match val_ref {
451 ValueRef::Null => String::new(),
452 ValueRef::Boolean(b) => b.to_string(),
453 ValueRef::TinyInt(i) => i.to_string(),
454 ValueRef::SmallInt(i) => i.to_string(),
455 ValueRef::Int(i) => i.to_string(),
456 ValueRef::BigInt(i) => i.to_string(),
457 ValueRef::HugeInt(i) => i.to_string(),
458 ValueRef::UTinyInt(i) => i.to_string(),
459 ValueRef::USmallInt(i) => i.to_string(),
460 ValueRef::UInt(i) => i.to_string(),
461 ValueRef::UBigInt(i) => i.to_string(),
462 ValueRef::Float(f) => f.to_string(),
463 ValueRef::Double(f) => f.to_string(),
464 ValueRef::Text(s) => String::from_utf8_lossy(s).to_string(),
465 _ => String::new(),
466 }
467 }
468 Err(_) => String::new(),
469 };
470 values.push(val);
471 }
472 Ok(values)
473 })
474 .map_err(|e| format!("Failed to query parquet data: {}", e))?;
475
476 let all_rows: Vec<Vec<String>> = rows
478 .map(|row_result| row_result.map_err(|e| format!("Failed to process row: {}", e)))
479 .collect::<Result<Vec<_>, String>>()?;
480
481 drop(stmt);
483
484 for values in all_rows {
486 self.insert_row(values).map_err(|e| format!("Failed to insert row: {}", e))?;
487 }
488
489 Ok(())
490 }
491}
492
493#[cfg(feature = "spreadsheet")]
495#[derive(Debug, Clone)]
496pub enum SpreadsheetAction {
497 CellEdited { row_id: usize, col_idx: usize, value: String },
498 RowAdded,
499 RowDeleted(usize),
500 DataLoaded(PathBuf),
501 DataSaved(PathBuf),
502}
503
504#[cfg(feature = "spreadsheet")]
506pub struct MaterialSpreadsheet {
507 id: Id,
508 pub data_model: Arc<Mutex<SpreadsheetDataModel>>,
509 cached_rows: Vec<RowData>,
510 editing_cell: Option<(usize, usize)>,
511 edit_buffer: String,
512 selected_rows: HashSet<usize>,
513 allow_editing: bool,
514 allow_selection: bool,
515 striped: bool,
516 row_height: f32,
517 load_bind: Bind<Vec<RowData>, String>,
518 save_bind: Bind<(), String>,
519 load_processed: bool, }
521
522#[cfg(feature = "spreadsheet")]
523impl MaterialSpreadsheet {
524 pub fn new(id: &str, columns: Vec<ColumnDef>) -> Result<Self, String> {
526 let data_model = SpreadsheetDataModel::new(columns).map_err(|e| e.to_string())?;
527
528 Ok(Self {
529 id: Id::new(id),
530 data_model: Arc::new(Mutex::new(data_model)),
531 cached_rows: Vec::new(),
532 editing_cell: None,
533 edit_buffer: String::new(),
534 selected_rows: HashSet::new(),
535 allow_editing: true,
536 allow_selection: true,
537 striped: true,
538 row_height: 36.0,
539 load_bind: Bind::new(false),
540 save_bind: Bind::new(false),
541 load_processed: false,
542 })
543 }
544
545 pub fn allow_editing(mut self, allow: bool) -> Self {
547 self.allow_editing = allow;
548 self
549 }
550
551 pub fn allow_selection(mut self, allow: bool) -> Self {
553 self.allow_selection = allow;
554 self
555 }
556
557 pub fn striped(mut self, striped: bool) -> Self {
559 self.striped = striped;
560 self
561 }
562
563 pub fn set_allow_editing(&mut self, allow: bool) {
565 self.allow_editing = allow;
566 }
567
568 pub fn set_allow_selection(&mut self, allow: bool) {
570 self.allow_selection = allow;
571 }
572
573 pub fn set_striped(&mut self, striped: bool) {
575 self.striped = striped;
576 }
577
578 pub fn add_row(&mut self) -> Result<(), String> {
580 let mut model = self.data_model.lock().unwrap();
581 let col_count = model.columns.len();
582 let empty_values = vec![String::new(); col_count];
583 model.insert_row(empty_values).map_err(|e| e.to_string())?;
584 self.cached_rows = model.query_rows().map_err(|e| e.to_string())?;
586 Ok(())
587 }
588
589 pub fn delete_row(&mut self, row_id: usize) -> Result<(), String> {
591 let mut model = self.data_model.lock().unwrap();
592 model.delete_row(row_id).map_err(|e| e.to_string())?;
593 Ok(())
594 }
595
596 pub fn refresh_data(&mut self) -> Result<(), String> {
598 let model = self.data_model.lock().unwrap();
599 self.cached_rows = model.query_rows().map_err(|e| e.to_string())?;
600 Ok(())
601 }
602
603 pub fn load_from_file(&mut self, path: PathBuf) {
605 self.load_processed = false; let model = Arc::clone(&self.data_model);
607 self.load_bind.refresh(async move {
608 let format = FileFormat::from_path(&path).ok_or_else(|| "Unknown file format".to_string())?;
609
610 let mut locked_model = model.lock().unwrap();
611 match format {
612 FileFormat::Csv => locked_model.import_csv(&path)?,
613 FileFormat::Parquet => locked_model.import_parquet(&path)?,
614 FileFormat::Excel => return Err("Excel import not yet implemented".to_string()),
615 FileFormat::Arrow => return Err("Arrow import not yet implemented".to_string()),
616 }
617
618 locked_model.query_rows().map_err(|e| e.to_string())
620 });
621 }
622
623 pub fn save_to_file(&mut self, path: PathBuf) {
625 let model = Arc::clone(&self.data_model);
626 self.save_bind.refresh(async move {
627 let format = FileFormat::from_path(&path).ok_or_else(|| "Unknown file format".to_string())?;
628
629 let locked_model = model.lock().unwrap();
630 match format {
631 FileFormat::Csv => locked_model.export_csv(&path)?,
632 FileFormat::Parquet => locked_model.export_parquet(&path)?,
633 FileFormat::Excel => return Err("Excel export not yet implemented".to_string()),
634 FileFormat::Arrow => return Err("Arrow export not yet implemented".to_string()),
635 }
636
637 Ok(())
638 });
639 }
640
641 pub fn columns(&self) -> Vec<ColumnDef> {
643 self.data_model.lock().unwrap().columns.clone()
644 }
645
646 pub fn rows(&self) -> Vec<RowData> {
648 self.cached_rows.clone()
649 }
650
651 pub fn show(&mut self, ui: &mut Ui) -> Response {
653 ui.ctx().plugin_or_default::<egui_async::EguiAsyncPlugin>();
655
656 match self.load_bind.state() {
658 StateWithData::Pending => {
659 ui.ctx().request_repaint();
660 return ui.label("Loading...").interact(Sense::hover());
661 }
662 StateWithData::Finished(rows) => {
663 if !self.load_processed {
665 eprintln!("DEBUG: Load finished, updating cached_rows with {} rows", rows.len());
666 self.cached_rows = rows.clone();
667 self.load_processed = true;
668 }
669 }
670 StateWithData::Failed(err) => {
671 return ui.label(format!("Load error: {}", err)).interact(Sense::hover());
672 }
673 StateWithData::Idle => {}
674 }
675
676 match self.save_bind.state() {
678 StateWithData::Pending => {
679 ui.ctx().request_repaint();
680 }
681 StateWithData::Failed(err) => {
682 ui.label(format!("Save error: {}", err));
683 }
684 _ => {}
685 }
686
687 let columns = self.data_model.lock().unwrap().columns.clone();
689
690 let surface = get_global_color("surface");
692 let on_surface = get_global_color("on-surface");
693 let primary = get_global_color("primary");
694 let surface_variant = get_global_color("surface-variant");
695
696 let text_height = ui.text_style_height(&egui::TextStyle::Body);
698 let available_height = ui.available_height();
699
700 let mut table = TableBuilder::new(ui)
701 .striped(self.striped)
702 .resizable(true)
703 .cell_layout(egui::Layout::left_to_right(egui::Align::Center))
704 .min_scrolled_height(0.0)
705 .max_scroll_height(available_height);
706
707 for col in columns.iter() {
709 table = table.column(Column::initial(col.width).at_least(50.0).resizable(true));
710 }
711
712 let display_rows = self.cached_rows.clone();
714 eprintln!("DEBUG: Rendering table with {} cached rows", display_rows.len());
715 if !display_rows.is_empty() && display_rows.len() > 4 {
716 eprintln!("DEBUG: Row 4 data: {:?}", display_rows[4].values);
717 }
718
719 let pending_update_id = self.id.with("pending_cell_update");
721
722 table
723 .header(30.0, |mut header| {
724 for col in columns.iter() {
725 header.col(|ui| {
726 let rect = ui.max_rect();
728 ui.painter().rect_filled(rect, egui::Rounding::ZERO, surface_variant);
729
730 ui.style_mut().visuals.override_text_color = Some(on_surface);
731 ui.strong(&col.name);
732 });
733 }
734 })
735 .body(|mut body| {
736 for row_data in &display_rows {
737 body.row(self.row_height, |mut row| {
738 for (col_idx, value) in row_data.values.iter().enumerate() {
739 row.col(|ui| {
740 let is_editing = self.editing_cell == Some((row_data.id, col_idx));
741
742 if is_editing {
743 eprintln!("DEBUG: Rendering TextEdit - row_id: {}, col_idx: {}, buffer: '{}'", row_data.id, col_idx, self.edit_buffer);
745 let edit_response = ui.add(
746 TextEdit::singleline(&mut self.edit_buffer)
747 .desired_width(f32::INFINITY)
748 );
749 eprintln!("DEBUG: TextEdit state - has_focus: {}, lost_focus: {}, gained_focus: {}",
750 edit_response.has_focus(), edit_response.lost_focus(), edit_response.gained_focus());
751
752 if edit_response.lost_focus() {
754 let escape_pressed = ui.input(|i| i.key_pressed(egui::Key::Escape));
755 eprintln!("DEBUG: TextEdit lost focus - escape_pressed: {}", escape_pressed);
756
757 if !escape_pressed {
758 eprintln!("DEBUG: Storing cell update - row_id: {}, col_idx: {}, value: '{}'", row_data.id, col_idx, self.edit_buffer);
759 ui.memory_mut(|mem| {
761 mem.data.insert_temp(pending_update_id, (row_data.id, col_idx, self.edit_buffer.clone()));
762 });
763 } else {
764 eprintln!("DEBUG: Edit cancelled with Escape");
765 }
766 self.editing_cell = None;
768 }
769
770 if edit_response.gained_focus() {
771 edit_response.request_focus();
772 eprintln!("DEBUG: Requested focus for TextEdit");
773 }
774 } else {
775 let label_response = ui.label(value);
777
778 if self.allow_editing && label_response.clicked() {
780 eprintln!("DEBUG: Starting edit mode - row_id: {}, col_idx: {}, current_value: {}", row_data.id, col_idx, value);
781 self.editing_cell = Some((row_data.id, col_idx));
782 self.edit_buffer = value.clone();
783 }
784 }
785 });
786 }
787 });
788 }
789 });
790
791 if let Some((row_id, col_idx, new_value)) = ui.memory(|mem| {
793 mem.data.get_temp::<(usize, usize, String)>(pending_update_id)
794 }) {
795 eprintln!("DEBUG: Retrieved cell update - row_id: {}, col_idx: {}, value: {}", row_id, col_idx, new_value);
796
797 ui.memory_mut(|mem| {
799 mem.data.remove::<(usize, usize, String)>(pending_update_id);
800 });
801
802 let mut model = self.data_model.lock().unwrap();
803 match model.update_cell(row_id, col_idx, new_value.clone()) {
804 Ok(_) => {
805 eprintln!("DEBUG: Cell updated in database successfully");
806 match model.query_rows() {
808 Ok(rows) => {
809 eprintln!("DEBUG: Refreshed {} rows from database", rows.len());
810 if rows.len() > 4 {
811 eprintln!("DEBUG: After query, row 4 data: {:?}", rows[4].values);
812 }
813 self.cached_rows = rows;
814 ui.ctx().request_repaint();
816 eprintln!("DEBUG: Requested repaint");
817 }
818 Err(e) => {
819 eprintln!("DEBUG: Failed to query rows: {}", e);
820 }
821 }
822 }
823 Err(e) => {
824 eprintln!("DEBUG: Failed to update cell: {}", e);
825
826 ui.memory_mut(|mem| {
828 mem.data.insert_temp(
829 self.id.with("cell_error"),
830 e.clone()
831 );
832 });
833 ui.ctx().request_repaint();
834 }
835 }
836 } else {
837 eprintln!("DEBUG: No pending cell update found");
838 }
839
840 if let Some(error_msg) = ui.memory(|mem| {
842 mem.data.get_temp::<String>(self.id.with("cell_error"))
843 }) {
844 ui.memory_mut(|mem| {
846 mem.data.remove::<String>(self.id.with("cell_error"));
847 });
848
849 ui.ctx().debug_painter().text(
851 ui.max_rect().center_top() + egui::vec2(0.0, 10.0),
852 egui::Align2::CENTER_TOP,
853 &error_msg,
854 egui::FontId::proportional(14.0),
855 get_global_color("error"),
856 );
857
858 ui.ctx().request_repaint_after(std::time::Duration::from_secs(3));
860 }
861
862 ui.interact(ui.max_rect(), self.id, Sense::hover())
863 }
864}
865
866#[cfg(feature = "spreadsheet")]
867impl Widget for MaterialSpreadsheet {
868 fn ui(mut self, ui: &mut Ui) -> Response {
869 self.show(ui)
870 }
871}
872
873#[cfg(feature = "spreadsheet")]
875pub fn column(name: impl Into<String>, col_type: ColumnType, width: f32) -> ColumnDef {
876 ColumnDef {
877 name: name.into(),
878 col_type,
879 width,
880 }
881}
882
883#[cfg(feature = "spreadsheet")]
885pub fn text_column(name: impl Into<String>, width: f32) -> ColumnDef {
886 column(name, ColumnType::Text, width)
887}
888
889#[cfg(feature = "spreadsheet")]
891pub fn number_column(name: impl Into<String>, width: f32) -> ColumnDef {
892 column(name, ColumnType::Real, width)
893}
894
895#[cfg(feature = "spreadsheet")]
897pub fn integer_column(name: impl Into<String>, width: f32) -> ColumnDef {
898 column(name, ColumnType::Integer, width)
899}