/* * edit.cpp * Editing, selection, clipboard, formatting, undo/redo, scroll * Copyright (c) 2026 Daniel Hammer */ #include "spreadsheet.h" // ============================================================================ // Undo/redo // ============================================================================ void undo_push() { for (int i = g_undo_pos; i < g_undo_count; i++) { if (g_undo[i]) { montauk::mfree(g_undo[i]); g_undo[i] = nullptr; } } if (g_undo[UNDO_MAX]) { montauk::mfree(g_undo[UNDO_MAX]); g_undo[UNDO_MAX] = nullptr; } g_undo_count = g_undo_pos; if (g_undo_count >= UNDO_MAX) { if (g_undo[0]) montauk::mfree(g_undo[0]); for (int i = 0; i < UNDO_MAX - 1; i++) g_undo[i] = g_undo[i + 1]; g_undo[UNDO_MAX - 1] = nullptr; g_undo_count = UNDO_MAX - 1; } UndoEntry* e = (UndoEntry*)montauk::malloc(sizeof(UndoEntry)); if (!e) return; for (int r = 0; r < MAX_ROWS; r++) for (int c = 0; c < MAX_COLS; c++) { str_cpy(e->cells[r][c].input, g_cells[r][c].input, CELL_TEXT_MAX); e->cells[r][c].align = g_cells[r][c].align; e->cells[r][c].fmt = g_cells[r][c].fmt; e->cells[r][c].bold = g_cells[r][c].bold; } g_undo[g_undo_count] = e; g_undo_count++; g_undo_pos = g_undo_count; } static void undo_restore(UndoEntry* e) { for (int r = 0; r < MAX_ROWS; r++) for (int c = 0; c < MAX_COLS; c++) { str_cpy(g_cells[r][c].input, e->cells[r][c].input, CELL_TEXT_MAX); g_cells[r][c].align = e->cells[r][c].align; g_cells[r][c].fmt = e->cells[r][c].fmt; g_cells[r][c].bold = e->cells[r][c].bold; } eval_all_cells(); } void undo_do() { if (g_undo_pos <= 0) return; if (g_undo_pos == g_undo_count && !g_undo[g_undo_count]) { UndoEntry* e = (UndoEntry*)montauk::malloc(sizeof(UndoEntry)); if (e) { for (int r = 0; r < MAX_ROWS; r++) for (int c = 0; c < MAX_COLS; c++) { str_cpy(e->cells[r][c].input, g_cells[r][c].input, CELL_TEXT_MAX); e->cells[r][c].align = g_cells[r][c].align; e->cells[r][c].fmt = g_cells[r][c].fmt; e->cells[r][c].bold = g_cells[r][c].bold; } g_undo[g_undo_count] = e; } } g_undo_pos--; if (g_undo[g_undo_pos]) undo_restore(g_undo[g_undo_pos]); } void redo_do() { if (g_undo_pos >= g_undo_count) return; g_undo_pos++; UndoEntry* e = g_undo[g_undo_pos]; if (e) undo_restore(e); } // ============================================================================ // Editing // ============================================================================ void start_editing() { if (g_editing) return; g_editing = true; Cell* c = &g_cells[g_sel_row][g_sel_col]; str_cpy(g_edit_buf, c->input, CELL_TEXT_MAX); g_edit_len = str_len(g_edit_buf); g_edit_cursor = g_edit_len; } void commit_edit() { if (!g_editing) return; g_editing = false; undo_push(); Cell* c = &g_cells[g_sel_row][g_sel_col]; str_cpy(c->input, g_edit_buf, CELL_TEXT_MAX); g_modified = true; eval_all_cells(); } void cancel_edit() { g_editing = false; } // ============================================================================ // Selection helpers // ============================================================================ void sel_range(int* c0, int* r0, int* c1, int* r1) { if (!g_has_selection) { *c0 = *c1 = g_sel_col; *r0 = *r1 = g_sel_row; return; } *c0 = g_sel_col < g_anchor_col ? g_sel_col : g_anchor_col; *c1 = g_sel_col > g_anchor_col ? g_sel_col : g_anchor_col; *r0 = g_sel_row < g_anchor_row ? g_sel_row : g_anchor_row; *r1 = g_sel_row > g_anchor_row ? g_sel_row : g_anchor_row; } void clear_selection() { g_has_selection = false; g_anchor_col = g_sel_col; g_anchor_row = g_sel_row; } void copy_selection() { int c0, r0, c1, r1; sel_range(&c0, &r0, &c1, &r1); g_clip_count = 0; g_clip_cols = c1 - c0 + 1; g_clip_rows = r1 - r0 + 1; for (int r = r0; r <= r1 && g_clip_count < CLIP_MAX_CELLS; r++) { for (int c = c0; c <= c1 && g_clip_count < CLIP_MAX_CELLS; c++) { ClipCell* cc = &g_clipboard[g_clip_count]; str_cpy(cc->text, g_cells[r][c].input, CELL_TEXT_MAX); cc->rel_col = c - c0; cc->rel_row = r - r0; cc->align = g_cells[r][c].align; cc->fmt = g_cells[r][c].fmt; cc->bold = g_cells[r][c].bold; g_clip_count++; } } } void cut_selection() { copy_selection(); undo_push(); int c0, r0, c1, r1; sel_range(&c0, &r0, &c1, &r1); for (int r = r0; r <= r1; r++) for (int c = c0; c <= c1; c++) g_cells[r][c].input[0] = '\0'; eval_all_cells(); g_modified = true; } void paste_at_cursor() { if (g_clip_count == 0) return; undo_push(); for (int i = 0; i < g_clip_count; i++) { int tc = g_sel_col + g_clipboard[i].rel_col; int tr = g_sel_row + g_clipboard[i].rel_row; if (tc >= 0 && tc < MAX_COLS && tr >= 0 && tr < MAX_ROWS) { str_cpy(g_cells[tr][tc].input, g_clipboard[i].text, CELL_TEXT_MAX); g_cells[tr][tc].align = g_clipboard[i].align; g_cells[tr][tc].fmt = g_clipboard[i].fmt; g_cells[tr][tc].bold = g_clipboard[i].bold; } } eval_all_cells(); g_modified = true; } // ============================================================================ // Formatting helpers (apply to selection) // ============================================================================ void apply_bold_toggle() { undo_push(); int c0, r0, c1, r1; sel_range(&c0, &r0, &c1, &r1); bool new_bold = !g_cells[r0][c0].bold; for (int r = r0; r <= r1; r++) for (int c = c0; c <= c1; c++) g_cells[r][c].bold = new_bold; g_modified = true; } void apply_align(CellAlign a) { undo_push(); int c0, r0, c1, r1; sel_range(&c0, &r0, &c1, &r1); for (int r = r0; r <= r1; r++) for (int c = c0; c <= c1; c++) g_cells[r][c].align = a; g_modified = true; } void apply_format(NumFormat f) { undo_push(); int c0, r0, c1, r1; sel_range(&c0, &r0, &c1, &r1); for (int r = r0; r <= r1; r++) for (int c = c0; c <= c1; c++) g_cells[r][c].fmt = f; eval_all_cells(); g_modified = true; } // ============================================================================ // Scroll clamping // ============================================================================ void clamp_scroll() { int pbh = g_pathbar_open ? PATHBAR_H : 0; int max_x = content_width() - (g_win_w - ROW_HEADER_W); int max_y = content_height() - (g_win_h - TOOLBAR_H - pbh - FORMULA_BAR_H - STATUS_BAR_H); if (max_x < 0) max_x = 0; if (max_y < 0) max_y = 0; if (g_scroll_x < 0) g_scroll_x = 0; if (g_scroll_x > max_x) g_scroll_x = max_x; if (g_scroll_y < 0) g_scroll_y = 0; if (g_scroll_y > max_y) g_scroll_y = max_y; } void ensure_sel_visible() { int pbh = g_pathbar_open ? PATHBAR_H : 0; int area_w = g_win_w - ROW_HEADER_W; int area_h = g_win_h - TOOLBAR_H - pbh - FORMULA_BAR_H - COL_HEADER_H - STATUS_BAR_H; int cx = col_x(g_sel_col) - ROW_HEADER_W; int cy = g_sel_row * ROW_H; if (cx < g_scroll_x) g_scroll_x = cx; if (cx + g_col_widths[g_sel_col] > g_scroll_x + area_w) g_scroll_x = cx + g_col_widths[g_sel_col] - area_w; if (cy < g_scroll_y) g_scroll_y = cy; if (cy + ROW_H > g_scroll_y + area_h) g_scroll_y = cy + ROW_H - area_h; clamp_scroll(); }