1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Selecting rows, one or a range, and copying them.
use std::ops::Range;
use densa_grid::{CellText, GridModel, Palette};
use densa_ui::{ActionKind, EventCx};
use super::DataGrid;
impl<M: GridModel> DataGrid<M> {
/// The selected rows: one, the range from where a Shift selection started to the
/// [selected](Self::selected) row, or all of them; empty for none.
pub fn selected_rows(&self) -> Range<usize> {
let rows = self.model.rows();
if self.all {
return 0..rows;
}
let Some(focus) = self.selected() else {
return 0..0;
};
let anchor = self.anchor.map_or(focus, |a| a.row);
// Until the next layout or paint finds its records again, a row can be past the end.
let end = (anchor.max(focus) + 1).min(rows);
anchor.min(focus).min(end)..end
}
/// Selects the rows from `anchor` to `row` (either way round), `row` being the
/// [selected](Self::selected) one, and scrolls it into view. Rows past the last are
/// taken as the last.
pub fn select_rows(&mut self, cx: &mut EventCx<'_>, anchor: usize, row: usize) {
let Some(last) = self.model.rows().checked_sub(1) else {
return;
};
self.all = false;
self.anchor = None;
self.select(cx, Some(row.min(last)));
self.anchor = Some(self.selection_of(anchor.min(last)));
}
/// Selects every row, and those added later, until the selection changes. The
/// [selected](Self::selected) row stays as it was, and nothing scrolls.
pub fn select_all(&mut self, cx: &mut EventCx<'_>) {
self.all = true;
cx.request_paint();
}
/// Puts the selected rows on the clipboard, as [`selected_text`](Self::selected_text)
/// writes them; `false` if no row is selected or the clipboard would not take them.
///
/// The text of every selected row is built at once, on the calling thread: a million
/// rows take a noticeable time and as much memory as their text.
pub fn copy(&mut self, cx: &mut EventCx<'_>) -> bool {
self.catch_up();
if self.selected_rows().is_empty() {
return false;
}
let text = self.selected_text(&super::palette(cx.theme()));
cx.clipboard().set_text(&text).is_ok()
}
/// The selected rows' cells as the grid shows them (with `palette`'s formatting, where
/// the model's text depends on it), a tab between cells and a line break between rows:
/// the shape spreadsheets paste into cells. Tabs and line breaks within a cell become
/// spaces.
pub fn selected_text(&mut self, palette: &Palette) -> String {
self.catch_up();
let columns = self.model.columns().len();
let mut out = String::new();
let mut cell = CellText::new();
for (index, row) in self.selected_rows().enumerate() {
if index > 0 {
out.push('\n');
}
for column in 0..columns {
if column > 0 {
out.push('\t');
}
cell.clear();
self.model.cell(row, column, &mut cell, palette);
out.extend(cell.as_str().chars().map(|c| match c {
'\t' | '\n' | '\r' => ' ',
c => c,
}));
}
}
out
}
/// Takes in what the model changed since the last frame, as painting does.
fn catch_up(&mut self) {
self.model.prepare();
self.follow_model();
self.clamp();
}
/// Selects `row` alone (reporting it if it was not selected) and shows it.
pub(super) fn select_row(&mut self, cx: &mut EventCx<'_>, row: usize) {
self.anchor = None;
self.all = false;
self.move_selection(cx, row);
}
/// Selects from where the selection started (the selected row, if it was one row) to
/// `row`: Shift with a click or a key.
pub(super) fn extend_to(&mut self, cx: &mut EventCx<'_>, row: usize) {
self.all = false;
if self.anchor.is_none() {
self.anchor = Some(self.selected.unwrap_or_else(|| self.selection_of(row)));
}
self.move_selection(cx, row);
}
/// Makes `row` the selected row, keeping the anchor, and shows it.
fn move_selection(&mut self, cx: &mut EventCx<'_>, row: usize) {
if self.selected() != Some(row) {
self.selected = Some(self.selection_of(row));
cx.emit(ActionKind::Selected(row));
}
self.reveal_row(row);
cx.request_paint();
}
}