T: move table rows and columns
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a4e76ed620
commit
dbf1610aa6
|
|
@ -83,6 +83,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
|||
- View ▸ Separations Preview (`view.separations`): one process plate as ink density, or areas over an ink limit in red (a GCR preview of the rendered view; spot plates and the flattener preview aren't there).
|
||||
- Books (`book.new/open/add/remove/list/styleSource/paginate/syncStyles/exportPdf`, Book panel): `.dcbook` files, continuous page numbering, style sync from a style source, one merged PDF.
|
||||
- Track Changes (`changes.track/list/acceptAll/rejectAll`, Type ▸ Track Changes): typing is marked added (highlighted on screen), deletions are kept but hidden from layout and exports until accepted or rejected. Per-change accept/reject and authors aren't there yet.
|
||||
- Move table rows and columns (`table.moveRow`, `table.moveColumn`, Table panel buttons; dragging on the canvas isn't there yet).
|
||||
- Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`).
|
||||
- Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export.
|
||||
- Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override.
|
||||
|
|
|
|||
|
|
@ -691,6 +691,44 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
/// Move row `from` to position `to` (indices before the move). Refused when merged cells span
|
||||
/// rows (the move would cut them).
|
||||
pub fn move_row(&mut self, from: usize, to: usize) -> Result<(), String> {
|
||||
let (nr, nc) = (self.nrows(), self.ncols());
|
||||
if from >= nr || to >= nr {
|
||||
return Err("no such row".into());
|
||||
}
|
||||
if self.regions().iter().any(|r| r.2 > 1) {
|
||||
return Err("unmerge cells that span rows first".into());
|
||||
}
|
||||
let row = self.rows.remove(from);
|
||||
self.rows.insert(to, row);
|
||||
let cells: Vec<Cell> = self.cells.drain(from * nc..(from + 1) * nc).collect();
|
||||
let at = to * nc;
|
||||
for (k, c) in cells.into_iter().enumerate() {
|
||||
self.cells.insert(at + k, c);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move column `from` to position `to`. Refused when merged cells span columns.
|
||||
pub fn move_col(&mut self, from: usize, to: usize) -> Result<(), String> {
|
||||
let (nr, nc) = (self.nrows(), self.ncols());
|
||||
if from >= nc || to >= nc {
|
||||
return Err("no such column".into());
|
||||
}
|
||||
if self.regions().iter().any(|r| r.3 > 1) {
|
||||
return Err("unmerge cells that span columns first".into());
|
||||
}
|
||||
let col = self.columns.remove(from);
|
||||
self.columns.insert(to, col);
|
||||
for r in 0..nr {
|
||||
let c = self.cells.remove(r * nc + from);
|
||||
self.cells.insert(r * nc + to, c);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark the first `header` rows as header rows and the last `footer` rows as footer rows.
|
||||
pub fn set_header_footer(&mut self, header: usize, footer: usize) {
|
||||
let n = self.nrows();
|
||||
|
|
|
|||
|
|
@ -112,6 +112,18 @@ fn table_specs() -> Vec<CommandSpec> {
|
|||
cmd!("table.deleteColumn", "Delete Column", ["Table", "Delete"], None, "{} — the columns of the target cells", in_table, delete_cols),
|
||||
cmd!("table.delete", "Delete Table", ["Table", "Delete"], None, "{}", in_table, delete_table),
|
||||
cmd!("table.merge", "Merge Cells", ["Table"], None, "{} — merge the target cell range", in_table, merge),
|
||||
cmd!(
|
||||
"table.moveRow",
|
||||
"Move Row",
|
||||
[],
|
||||
None,
|
||||
"{from?, to} (0-based; from defaults to the target row) — like dragging a row",
|
||||
in_table,
|
||||
|s, p| move_rc(s, p, true)
|
||||
),
|
||||
cmd!("table.moveColumn", "Move Column", [], None, "{from?, to} (0-based; from defaults to the target column)", in_table, |s, p| move_rc(
|
||||
s, p, false
|
||||
)),
|
||||
cmd!(
|
||||
"table.sortRows",
|
||||
"Sort",
|
||||
|
|
@ -1090,6 +1102,17 @@ mod style_tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn move_rc(s: &mut Session, p: &Value, rows: bool) -> Result<Value> {
|
||||
const ID: &str = "table.move";
|
||||
let g = target(s, p, ID)?;
|
||||
let from = p.get("from").and_then(Value::as_u64).map_or(if rows { g.range.r0 } else { g.range.c0 }, |v| v as usize);
|
||||
let to = p.get("to").and_then(Value::as_u64).ok_or_else(|| bad(ID, "`to` required"))? as usize;
|
||||
edit_table(s, &g, ID, |t| {
|
||||
if rows { t.move_row(from, to) } else { t.move_col(from, to) }.map_err(|e| bad(ID, e))?;
|
||||
Ok(json!({"from": from, "to": to}))
|
||||
})
|
||||
}
|
||||
|
||||
fn sort_rows(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let g = target(s, p, "table.sortRows")?;
|
||||
let col = p.get("column").and_then(Value::as_u64).map_or(g.range.c0, |c| c as usize);
|
||||
|
|
@ -1158,4 +1181,25 @@ mod sort_tests {
|
|||
let r = s.execute("table.splitHorizontally", &json!({"rows": [0, 0], "cols": [1, 1]})).unwrap();
|
||||
assert_eq!((r["rows"].as_u64(), r["cols"].as_u64()), (Some(5), Some(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_rows_and_columns() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let r = s.execute("frame.create", &json!({"rect": [72, 72, 500, 400], "content": "text", "text": ""})).unwrap();
|
||||
s.execute("text.select", &json!({"story": r["story"], "anchor": 0, "focus": 0})).unwrap();
|
||||
s.execute("table.insert", &json!({"rows": 3, "cols": 2})).unwrap();
|
||||
for (r, c, t) in [(0, 0, "a"), (1, 0, "b"), (2, 0, "c"), (0, 1, "x")] {
|
||||
s.execute("table.setCell", &json!({"rows": [r, r], "cols": [c, c], "text": t})).unwrap();
|
||||
}
|
||||
let cell = |s: &Session, r: usize, c: usize| {
|
||||
let d = &s.doc().unwrap().doc;
|
||||
let t = d.stories.values().flat_map(|st| st.tables.values()).next().unwrap();
|
||||
t.cell(r, c).unwrap().text.text.clone()
|
||||
};
|
||||
s.execute("table.moveRow", &json!({"from": 0, "to": 2})).unwrap();
|
||||
assert_eq!([cell(&s, 0, 0), cell(&s, 1, 0), cell(&s, 2, 0)], ["b", "c", "a"]);
|
||||
s.execute("table.moveColumn", &json!({"from": 1, "to": 0})).unwrap();
|
||||
assert_eq!((cell(&s, 2, 0), cell(&s, 2, 1)), ("x".to_string(), "a".to_string()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,23 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||
let run = |app: &mut DesignApp, id: &str, p: Value| {
|
||||
let _ = app.run(id, p);
|
||||
};
|
||||
// Move the target row / column (InDesign drags them).
|
||||
ui.horizontal(|ui| {
|
||||
caption(ui, "Move");
|
||||
let (nr, nc) = (rows.len(), cols.len());
|
||||
for (label, id, from, to, ok) in [
|
||||
("Row ↑", "table.moveRow", r0, r0.saturating_sub(1), r0 > 0),
|
||||
("Row ↓", "table.moveRow", r0, r0 + 1, r0 + 1 < nr),
|
||||
("Col ←", "table.moveColumn", c0, c0.saturating_sub(1), c0 > 0),
|
||||
("Col →", "table.moveColumn", c0, c0 + 1, c0 + 1 < nc),
|
||||
] {
|
||||
if ui.add_enabled(ok, egui::Button::new(label).small()).clicked()
|
||||
&& let Err(e) = app.run(id, json!({"from": from, "to": to}))
|
||||
{
|
||||
app.status(format!("Table: {e}"));
|
||||
}
|
||||
}
|
||||
});
|
||||
// Table and cell styles: pick to apply, + to save the current look as a new style.
|
||||
if let Some(st) = app.session.active() {
|
||||
let styles = &st.doc.styles;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 92 | 4 | 1 | 97% |
|
||||
| P2 | 76 | 34 | 21 | 21 | 59% |
|
||||
| P2 | 76 | 34 | 22 | 20 | 59% |
|
||||
|
||||
| Area | Parity |
|
||||
|---|---|
|
||||
|
|
@ -26,7 +26,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
| Type & text frames | 94% |
|
||||
| Typography | 98% |
|
||||
| Styles | 97% |
|
||||
| Tables | 92% |
|
||||
| Tables | 94% |
|
||||
| Long documents | 96% |
|
||||
| Interactivity & digital | 75% |
|
||||
| Output & production | 80% |
|
||||
|
|
@ -259,7 +259,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
|||
| Tables | Tables spanning frames | P1 | D |
|
||||
| Tables | Sort table rows | P2 | D |
|
||||
| Tables | Excel import | P2 | D |
|
||||
| Tables | Drag to move rows/columns | P2 | M |
|
||||
| Tables | Drag to move rows/columns | P2 | P |
|
||||
| Long documents | Table of contents | P1 | D |
|
||||
| Long documents | Index | P2 | D |
|
||||
| Long documents | Books | P2 | D |
|
||||
|
|
|
|||
Loading…
Reference in New Issue