246 lines
7.7 KiB
C++
246 lines
7.7 KiB
C++
/*
|
|
* 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();
|
|
}
|