use std::fmt;
use std::sync::Arc;
use crate::data::CellValue;
use crate::pivot::aggregation::AggregationFn;
use crate::pivot::config::PivotConfig;
use crate::pivot::state::PivotState;
pub const PIVOT_ACTION_SHOW_SOURCE_ROWS: &str = "pivot.show-source-rows";
pub const PIVOT_ACTION_COPY_VALUE: &str = "pivot.copy-value";
pub const PIVOT_ACTION_COPY_CSV: &str = "pivot.copy-csv";
#[derive(Clone, Debug, PartialEq)]
pub struct PivotPathComponent {
pub field_index: usize,
pub field_name: String,
pub label: String,
pub group_value: CellValue,
pub is_blank: bool,
}
#[derive(Clone, Debug)]
pub struct PivotCellContext {
pub row_path: Vec<PivotPathComponent>,
pub col_path: Vec<PivotPathComponent>,
pub value: CellValue,
pub formatted_value: String,
pub is_row_grand_total: bool,
pub is_col_grand_total: bool,
pub is_row_subtotal: bool,
pub is_col_subtotal: bool,
}
#[derive(Clone, Debug)]
pub enum PivotMenuTarget {
Cell(PivotCellContext),
RowHeader {
path: Vec<PivotPathComponent>,
is_grand_total: bool,
},
ColHeader {
path: Vec<PivotPathComponent>,
is_grand_total: bool,
},
Corner,
}
impl PivotMenuTarget {
#[must_use]
pub fn cell(&self) -> Option<&PivotCellContext> {
match self {
Self::Cell(c) => Some(c),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct PivotContextMenuRequest {
pub target: PivotMenuTarget,
pub aggregation: AggregationFn,
pub value_field_index: Option<usize>,
pub value_caption: String,
pub config: PivotConfig,
pub source_row_indices: Vec<usize>,
}
impl PivotContextMenuRequest {
#[must_use]
pub fn clicked_cell(&self) -> Option<&PivotCellContext> {
self.target.cell()
}
#[must_use]
pub fn source_row_count(&self) -> usize {
self.source_row_indices.len()
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum PivotMenuItem {
Action {
id: String,
label: String,
},
Separator,
}
impl PivotMenuItem {
#[must_use]
pub fn action(id: impl Into<String>, label: impl Into<String>) -> Self {
Self::Action {
id: id.into(),
label: label.into(),
}
}
#[must_use]
pub fn separator() -> Self {
Self::Separator
}
#[must_use]
pub fn show_source_rows() -> Self {
Self::action(PIVOT_ACTION_SHOW_SOURCE_ROWS, "Show source rows in Grid")
}
#[must_use]
pub fn copy_value() -> Self {
Self::action(PIVOT_ACTION_COPY_VALUE, "Copy value")
}
#[must_use]
pub fn copy_csv() -> Self {
Self::action(PIVOT_ACTION_COPY_CSV, "Copy pivot as CSV")
}
#[must_use]
pub fn standard_items(target: &PivotMenuTarget) -> Vec<Self> {
let mut items = Vec::new();
match target {
PivotMenuTarget::Cell(_) => {
items.push(Self::show_source_rows());
items.push(Self::copy_value());
items.push(Self::separator());
}
PivotMenuTarget::RowHeader { .. } | PivotMenuTarget::ColHeader { .. } => {
items.push(Self::show_source_rows());
items.push(Self::separator());
}
PivotMenuTarget::Corner => {}
}
items.push(Self::copy_csv());
items
}
}
pub trait PivotContextMenuProvider: 'static {
fn menu_items(&self, request: &PivotContextMenuRequest) -> Vec<PivotMenuItem>;
#[allow(unused_variables)]
fn on_action(
&self,
action_id: &str,
request: &PivotContextMenuRequest,
state: &mut PivotState,
cx: &mut gpui::App,
) {
}
}
#[derive(Clone)]
pub(crate) struct PivotContextMenuProviderHandle(Arc<dyn PivotContextMenuProvider>);
impl PivotContextMenuProviderHandle {
pub(crate) fn new(provider: impl PivotContextMenuProvider + 'static) -> Self {
Self(Arc::new(provider))
}
}
impl fmt::Debug for PivotContextMenuProviderHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PivotContextMenuProviderHandle")
.finish_non_exhaustive()
}
}
impl std::ops::Deref for PivotContextMenuProviderHandle {
type Target = dyn PivotContextMenuProvider;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn standard_items_for_cell_include_drill_copy_and_csv() {
let cell = PivotCellContext {
row_path: vec![],
col_path: vec![],
value: CellValue::Integer(1),
formatted_value: "1".into(),
is_row_grand_total: false,
is_col_grand_total: false,
is_row_subtotal: false,
is_col_subtotal: false,
};
let items = PivotMenuItem::standard_items(&PivotMenuTarget::Cell(cell));
assert_eq!(items.len(), 4);
assert_eq!(items[0], PivotMenuItem::show_source_rows());
assert_eq!(items[1], PivotMenuItem::copy_value());
assert_eq!(items[2], PivotMenuItem::Separator);
assert_eq!(items[3], PivotMenuItem::copy_csv());
}
#[test]
fn standard_items_for_corner_only_offer_csv() {
let items = PivotMenuItem::standard_items(&PivotMenuTarget::Corner);
assert_eq!(items, vec![PivotMenuItem::copy_csv()]);
}
#[test]
fn standard_items_for_headers_offer_drill() {
let items = PivotMenuItem::standard_items(&PivotMenuTarget::RowHeader {
path: vec![],
is_grand_total: false,
});
assert_eq!(items[0], PivotMenuItem::show_source_rows());
}
}