use std::collections::HashMap;
use crate::frontend::TableFrontend;
use crate::table_view::state::SelectedRange;
use crate::TableView;
use egui::{Event, Id, Key, Modal, Ui};
use itertools::Itertools;
use log::warn;
use rvariant::Variant;
use tabular_core::backend::{TableBackend, VisualColIdx, VisualRowIdx};
use tabular_core::{ColumnUid, RowUid};
impl TableView {
pub(crate) fn handle_key_input<T: TableBackend>(&mut self, data: &mut T, ui: &mut Ui) {
if ui.input(|i| i.modifiers.ctrl && i.key_pressed(Key::C)) {
if let Some(selected) = self.state.selected_range {
let mut text = String::new();
for mono_row_idx in selected.row_start()..=selected.row_end() {
let Some(row_uid) = data.row_uid(VisualRowIdx(mono_row_idx)) else {
continue;
};
for mono_col_idx in selected.col_start()..=selected.col_end() {
let Some(col_uid) = self.state.columns_ordered.get(mono_col_idx) else {
continue;
};
if let Some(v) = data.get((row_uid, *col_uid).into()) {
match v {
Variant::Str(s) => text += s.as_str(),
o => text += o.to_string().as_str(),
}
}
if mono_col_idx != selected.col_end() {
text += "\t";
}
}
if mono_row_idx != selected.row_end() {
text += "\n";
}
}
if !text.is_empty() {
ui.ctx().copy_text(text);
}
}
}
if ui.input(|i| i.modifiers.command && i.key_pressed(Key::A)) {
self.state.selected_range = Some(SelectedRange::rect(
self.state.columns_ordered.len(),
data.row_count(),
));
}
if ui.input(|i| i.key_pressed(Key::Escape)) {
self.state.selected_range = None;
}
if !data.persistent_flags().is_read_only && ui.input(|i| i.key_pressed(Key::N)) {
data.create_row([]);
}
self.handle_selection_moves(data.row_count(), data, ui);
}
pub(crate) fn handle_key_input_when_editing<T: TableBackend + TableFrontend>(
&mut self,
data: &mut T,
ui: &mut Ui,
) {
if ui.input(|i| i.key_pressed(Key::Tab)) {
if let Some(already_selected) = &mut self.state.selected_range {
if let Some(coord) = already_selected.editing() {
data.commit_cell_edit(coord);
}
already_selected.move_right(false, self.state.columns_ordered.len());
let row_uid = data.row_uid(VisualRowIdx(already_selected.row_start()));
let col_uid = data.col_uid(VisualColIdx(already_selected.col_start()));
if let (Some(row_uid), Some(col_uid)) = (row_uid, col_uid) {
already_selected.set_editing(Some((row_uid, col_uid).into()));
}
}
}
if ui.input(|i| i.key_pressed(Key::Escape)) {
if let Some(already_selected) = &mut self.state.selected_range {
already_selected.set_editing(None);
data.cancel_edit();
}
}
}
pub(crate) fn handle_paste(
&mut self,
paste_from_empty: bool,
data: &mut impl TableBackend,
ui: &mut Ui,
) {
let paste = ui.input(|i| {
i.events
.iter()
.find(|e| matches!(e, Event::Paste(_)))
.cloned()
});
let Some(Event::Paste(text)) = paste else {
return;
};
let mut rows = vec![];
for row in text.split('\n') {
let mut cols = vec![];
for col in row.split('\t') {
cols.push(col.trim().to_string());
}
rows.push(cols);
}
if rows.is_empty() {
return;
}
self.state.pasting_block_width = rows[0].len();
let is_equal_lengths =
rows.iter()
.map(|c| c.len())
.tuple_windows()
.fold(0i32, |acc, (l1, l2)| {
self.state.pasting_block_width = l1.max(l2);
acc + l1 as i32 - l2 as i32
})
== 0;
self.state.pasting_block_with_holes = !is_equal_lengths;
if paste_from_empty {
self.state.selected_range = Some(SelectedRange::rect(
self.state.pasting_block_width,
rows.len(),
));
for _ in 0..self.state.pasting_block_width {
data.create_column();
}
self.state.columns_ordered = data.used_columns().collect();
self.state.columns_ordered.sort();
for _ in 0..rows.len() {
data.create_row([]);
}
data.one_shot_flags_internal_mut().reloaded = true;
}
if let Some(selected_range) = &self.state.selected_range {
let selection_is_exact = rows.len() == selected_range.height()
&& self.state.pasting_block_width == selected_range.width()
&& is_equal_lengths;
self.state.about_to_paste_rows = rows;
if selection_is_exact {
self.paste_block(data);
} else {
self.state.create_rows_on_paste = false;
self.state.fill_with_same_on_paste = false;
self.state.create_cols_on_paste = false;
}
} else {
warn!("Refusing to paste without selection"); }
}
pub(crate) fn handle_paste_continue(
&mut self,
data: &mut impl TableBackend,
id: Id,
ui: &mut Ui,
) {
if self.state.about_to_paste_rows.is_empty() {
return;
}
let rows = self.state.about_to_paste_rows.len();
let cols = self.state.pasting_block_width;
let Some(selected_range) = self.state.selected_range else {
return;
};
let mut should_close = false;
let mut should_paste = false;
let _modal = Modal::new(id.with("egui_tabular_paste_modal")).show(ui.ctx(), |ui| {
ui.set_width(250.);
ui.heading("Paste");
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.add_space(8.0);
let with_holes = if self.state.pasting_block_with_holes {
" (with holes)"
} else {
""
};
ui.label(format!(
"You are about to paste {}x{} block{} into {}x{} selection",
rows,
cols,
with_holes,
selected_range.height(),
selected_range.width(),
));
if selected_range.height() < rows {
ui.checkbox(&mut self.state.create_rows_on_paste, "Create more rows");
}
if selected_range.height() > rows || selected_range.width() > cols {
ui.checkbox(
&mut self.state.fill_with_same_on_paste,
"Fill with repeated values",
);
}
if selected_range.width() < cols {
ui.checkbox(&mut self.state.create_adhoc_cols_on_paste, "Create columns");
}
});
});
ui.separator();
egui::Sides::new().show(
ui,
|ui| {
if ui.button("Close").clicked() {
should_close = true;
}
},
|ui| {
if ui.button("Paste clip").clicked() {
should_paste = true;
}
},
);
});
if should_paste {
self.paste_block(data);
}
if should_close || ui.input(|i| i.key_pressed(Key::Escape)) {
self.state.about_to_paste_rows.clear();
}
}
pub(crate) fn paste_block(&mut self, data: &mut impl TableBackend) {
let Some(selected_range) = &self.state.selected_range else {
return;
};
let mut row_ids: Vec<Option<RowUid>> = (0..selected_range.height())
.map(|mono_row_idx| {
data.row_uid(VisualRowIdx(mono_row_idx + selected_range.row_start()))
})
.collect();
if self.state.create_rows_on_paste
&& self.state.about_to_paste_rows.len() > selected_range.height()
{
for _ in 0..self.state.about_to_paste_rows.len() - selected_range.height() {
row_ids.push(data.create_row(HashMap::new()));
}
}
let mut col_ids: Vec<Option<ColumnUid>> = (0..selected_range.width())
.map(|mono_col_idx| self.state.columns_ordered.get(mono_col_idx).map(|col| *col))
.collect();
if self.state.create_adhoc_cols_on_paste {
for _ in 0..self.state.about_to_paste_rows[0].len() - selected_range.width() {
col_ids.push(data.create_column());
}
}
if self.state.fill_with_same_on_paste {
for (row_id, row) in row_ids
.into_iter()
.zip(self.state.about_to_paste_rows.iter().cycle())
{
let Some(row_uid) = row_id else { continue };
for (col_id_ty, cell) in col_ids.iter().zip(row.iter().cycle()) {
let Some(col_uid) = col_id_ty else {
continue;
};
let coord = (row_uid, *col_uid).into();
data.set(coord, Variant::Str(cell.clone()));
}
}
} else {
for (row_id, row) in row_ids
.into_iter()
.zip(self.state.about_to_paste_rows.iter())
{
let Some(row_id) = row_id else { continue };
for (col_id_ty, cell) in col_ids.iter().zip(row.iter()) {
let Some(col_uid) = col_id_ty else {
continue;
};
let coord = (row_id, *col_uid).into();
data.set(coord, Variant::Str(cell.clone()));
}
}
}
self.state.about_to_paste_rows.clear();
}
fn handle_selection_moves(
&mut self,
row_count: usize,
data: &mut impl TableBackend,
ui: &mut Ui,
) {
let (left, right, up, down, enter, shift) = ui.input(|i| {
(
i.key_pressed(Key::ArrowLeft),
i.key_pressed(Key::ArrowRight),
i.key_pressed(Key::ArrowUp),
i.key_pressed(Key::ArrowDown),
i.key_pressed(Key::E),
i.modifiers.shift,
)
});
if left || right || up || down {
if let Some(already_selected) = &mut self.state.selected_range {
if let Some(coord) = already_selected.editing() {
data.commit_cell_edit(coord);
}
if left {
already_selected.move_left(shift);
}
if right {
already_selected.move_right(shift, self.state.columns_ordered.len());
}
if up {
already_selected.move_up(shift);
}
if down {
already_selected.move_down(shift, row_count);
}
} else {
if data.row_count() > 0 && data.used_columns().next().is_some() {
self.state.selected_range = Some(SelectedRange::single_cell(0, 0));
}
}
}
let enter = enter && !data.persistent_flags().is_read_only;
if enter {
if let Some(selected) = &mut self.state.selected_range {
if selected.is_single_cell() {
let row_uid = data.row_uid(VisualRowIdx(selected.row_start()));
let col_uid = data.col_uid(VisualColIdx(selected.col_start()));
if let (Some(row_uid), Some(col_uid)) = (row_uid, col_uid) {
selected.set_editing(Some((row_uid, col_uid).into()));
}
}
}
}
}
}