use super::*;
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CopyTarget {
Cell {
row: usize,
column: usize,
},
Row(usize),
SelectedRows,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadState<'a> {
Loading,
Error(&'a str),
Empty,
Ready,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExternalFilterError;
impl std::fmt::Display for ExternalFilterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("use set_search and filter in the source for an external grid")
}
}
impl std::error::Error for ExternalFilterError {}
impl<T> Grid<T> {
pub fn search_query(&self) -> &str {
&self.search_query
}
pub fn search_draft(&self) -> Option<&str> {
self.search_draft.as_deref()
}
pub fn is_searching(&self) -> bool {
self.search_draft.is_some()
}
pub fn begin_search(&mut self) -> Action {
self.search_draft = Some(self.search_query.clone());
self.hover = None;
self.clear_cell_details();
self.drag = None;
Action::SearchEdited
}
pub fn set_search(&mut self, query: impl Into<String>) -> Option<Action> {
let query: String = query
.into()
.chars()
.filter(|c| !crate::text::is_unsafe(*c))
.collect();
if query == self.search_query {
return None;
}
self.search_query = query;
self.row_offset = 0;
self.hover = None;
self.clear_cell_details();
self.drag = None;
self.range_base = None;
self.selection_anchor = None;
if self.is_external() {
let p = self.pagination.as_mut()?;
p.page = 0;
p.total = None;
p.reset_cursors();
self.page_action()
} else {
self.model.set_search(self.search_query.clone());
if let Some(p) = self.pagination.as_mut() {
p.page = 0;
}
Some(Action::FilterChanged)
}
}
pub fn set_filter(
&mut self,
filter: impl Fn(&T) -> bool + 'static,
) -> Result<Action, ExternalFilterError> {
if self.is_external() {
return Err(ExternalFilterError);
}
self.model.set_filter(filter);
self.filter_changed();
Ok(Action::FilterChanged)
}
pub fn clear_filter(&mut self) -> Result<Action, ExternalFilterError> {
if self.is_external() {
return Err(ExternalFilterError);
}
self.model.clear_filter();
self.filter_changed();
Ok(Action::FilterChanged)
}
fn filter_changed(&mut self) {
self.row_offset = 0;
self.hover = None;
self.clear_cell_details();
self.drag = None;
self.selection_anchor = None;
self.range_base = None;
if let Some(p) = self.pagination.as_mut() {
p.page = 0;
}
}
pub fn select_page_rows(&mut self) -> Option<Action> {
let mut indices: BTreeSet<_> = self.model.selected_indices().collect();
indices.extend(self.row_range().filter_map(|p| self.model.index_at(p)));
self.range_base = None;
self.model
.set_selected_indices(indices)
.then_some(Action::RowsSelected)
}
pub fn clear_row_selection(&mut self) -> Option<Action> {
self.range_base = None;
self.model
.clear_row_selection()
.then_some(Action::RowsSelected)
}
pub fn toggle_row_selection(&mut self, index: usize) -> Option<Action> {
self.range_base = None;
self.model
.toggle_row_selection(index)
.then_some(Action::RowsSelected)
}
pub(super) fn extend_selection(
&mut self,
position: usize,
column: Option<usize>,
) -> Option<Action> {
let range = self.row_range();
if !range.contains(&position) {
return None;
}
let anchor_index = self.selection_anchor.or(self.model.selected_index());
let anchor = anchor_index
.and_then(|index| self.model.order.iter().position(|&i| i == index))
.filter(|p| range.contains(p))
.unwrap_or(position);
let anchor_index = self.model.index_at(anchor);
let base = self
.range_base
.take()
.unwrap_or_else(|| self.model.selected_indices().collect());
if let Some(column) = column {
self.cursor_column = Some(column);
}
self.select_position(position);
let mut indices = base.clone();
indices.extend(
(anchor.min(position)..=anchor.max(position)).filter_map(|p| self.model.index_at(p)),
);
self.model.set_selected_indices(indices);
self.selection_anchor = anchor_index;
self.range_base = Some(base);
Some(Action::RowsSelected)
}
pub fn copy_text(&self, target: CopyTarget) -> Option<String> {
let columns = self.visible_columns();
let append_row = |index, output: &mut String| {
let row = self.model.rows().get(index)?;
for (position, &column) in columns.iter().enumerate() {
if position > 0 {
output.push('\t');
}
let text = (self.model.columns[column].format)(row);
output.extend(text.chars().filter(|c| !crate::text::is_unsafe(*c)));
}
Some(())
};
match target {
CopyTarget::Cell { row, column } => {
if !self.is_column_visible(column) {
return None;
}
let mut text =
(self.model.columns.get(column)?.format)(self.model.rows().get(row)?);
text.retain(|c| !crate::text::is_unsafe(c));
Some(text)
}
CopyTarget::Row(row) => {
if columns.is_empty() {
return None;
}
let mut output = String::new();
append_row(row, &mut output)?;
Some(output)
}
CopyTarget::SelectedRows => {
if columns.is_empty() {
return None;
}
let mut output = String::new();
if self.model.selected_indices().next().is_some() {
let mut copied = false;
for &index in &self.model.order {
if !self.model.is_row_selected(index) {
continue;
}
if copied {
output.push('\n');
}
append_row(index, &mut output)?;
copied = true;
}
copied.then_some(output)
} else {
append_row(self.cursor()?.0, &mut output)?;
Some(output)
}
}
}
}
pub(super) fn can_copy(&self, target: CopyTarget) -> bool {
match target {
CopyTarget::Cell { row, column } => {
self.is_column_visible(column) && row < self.model.rows().len()
}
CopyTarget::Row(row) => {
!self.visible_columns().is_empty() && row < self.model.rows().len()
}
CopyTarget::SelectedRows => {
!self.visible_columns().is_empty()
&& if self.model.selected_indices().next().is_some() {
self.model
.order
.iter()
.any(|&index| self.model.is_row_selected(index))
} else {
self.cursor().is_some()
}
}
}
}
pub fn load_state(&self) -> LoadState<'_> {
if let Some(error) = &self.page_error {
LoadState::Error(error)
} else if self.page_state().is_some_and(|p| p.loading) {
LoadState::Loading
} else if self.model.visible_len() == 0 {
LoadState::Empty
} else {
LoadState::Ready
}
}
pub fn set_page_error(
&mut self,
request: PageRequest,
message: impl Into<String>,
) -> Result<(), PageError> {
let current = self.page_request().ok_or(PageError::NotExternal)?;
if current != request || !self.page_state().is_some_and(|p| p.loading) {
return Err(PageError::StaleResponse);
}
self.accept_page_error(message);
Ok(())
}
pub fn set_cursor_page_error(
&mut self,
request: CursorPageRequest,
message: impl Into<String>,
) -> Result<(), PageError> {
self.validate_cursor_request(&request)?;
self.accept_page_error(message);
Ok(())
}
fn accept_page_error(&mut self, message: impl Into<String>) {
let message: String = message
.into()
.chars()
.filter(|c| !crate::text::is_unsafe(*c))
.collect();
self.page_error = Some(if message.is_empty() {
"Unable to load records".into()
} else {
message
});
let p = self.pagination.as_mut().expect("external pagination");
p.loading = false;
p.has_next = false;
self.hover = None;
self.clear_cell_details();
}
}