use std::collections::{BTreeMap, BTreeSet};
use pdfrum_common::PageIndex;
use pdfrum_doc::Subtype;
use crate::edit::Place;
use crate::event::Modifiers;
use crate::field::FieldState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FieldId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AnnotId {
pub page: PageIndex,
pub index: u32,
}
impl AnnotId {
#[must_use]
pub fn new(page: impl Into<PageIndex>, index: u32) -> AnnotId {
AnnotId {
page: page.into(),
index,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FocusTarget {
Widget(FieldId, AnnotId),
Annot(AnnotId),
}
impl FocusTarget {
#[must_use]
pub fn annot(self) -> AnnotId {
match self {
FocusTarget::Widget(_, annot) | FocusTarget::Annot(annot) => annot,
}
}
#[must_use]
pub fn field(self) -> Option<FieldId> {
match self {
FocusTarget::Widget(field, _) => Some(field),
FocusTarget::Annot(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DragAnchor {
pub field: FieldId,
pub start: Place,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SessionConfig {
pub accelerator: Modifiers,
pub redo_on_ctrl_y: bool,
pub focusable: Vec<Subtype>,
pub max_undo_items: u32,
pub max_calculate_depth: u32,
}
impl SessionConfig {
#[must_use]
pub fn apple() -> SessionConfig {
SessionConfig {
accelerator: Modifiers::META,
redo_on_ctrl_y: false,
..SessionConfig::default()
}
}
}
impl Default for SessionConfig {
fn default() -> SessionConfig {
SessionConfig {
accelerator: Modifiers::CONTROL,
redo_on_ctrl_y: true,
focusable: vec![Subtype::Widget],
max_undo_items: crate::edit::UndoStack::DEFAULT_MAX,
max_calculate_depth: pdfrum_common::Limits::default().max_calculate_depth,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FormSession {
pub focus: Option<FocusTarget>,
pub fields: BTreeMap<FieldId, FieldState>,
pub hover: Option<AnnotId>,
pub drag: Option<DragAnchor>,
pub dirty: BTreeSet<FieldId>,
pub formatted: BTreeMap<FieldId, String>,
pub config: SessionConfig,
}
impl FormSession {
#[must_use]
pub fn new() -> FormSession {
FormSession::default()
}
#[must_use]
pub fn with_config(config: SessionConfig) -> FormSession {
FormSession {
config,
..FormSession::default()
}
}
#[must_use]
pub fn focused_field(&self) -> Option<FieldId> {
self.focus.and_then(FocusTarget::field)
}
#[must_use]
pub fn focused_state(&self) -> Option<&FieldState> {
self.fields.get(&self.focused_field()?)
}
pub fn focused_state_mut(&mut self) -> Option<&mut FieldState> {
let field = self.focused_field()?;
self.fields.get_mut(&field)
}
#[must_use]
pub fn is_accelerator(&self, modifiers: Modifiers) -> bool {
crate::field::text::is_shortcut(modifiers, self.config.accelerator)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_session_has_no_focus_and_no_state() {
let session = FormSession::new();
assert!(session.focus.is_none());
assert!(session.focused_field().is_none());
assert!(session.fields.is_empty());
assert!(session.dirty.is_empty());
}
#[test]
fn a_focus_target_names_its_annotation_either_way() {
let annot = AnnotId::new(0, 3);
let widget = FocusTarget::Widget(FieldId(1), annot);
let plain = FocusTarget::Annot(annot);
assert_eq!(widget.annot(), annot);
assert_eq!(plain.annot(), annot);
assert_eq!(widget.field(), Some(FieldId(1)));
assert_eq!(plain.field(), None);
}
#[test]
fn the_two_platform_configurations_differ_in_exactly_two_switches() {
let general = SessionConfig::default();
let apple = SessionConfig::apple();
assert_eq!(general.accelerator, Modifiers::CONTROL);
assert!(general.redo_on_ctrl_y);
assert_eq!(apple.accelerator, Modifiers::META);
assert!(!apple.redo_on_ctrl_y);
assert_eq!(general.focusable, apple.focusable);
assert_eq!(general.max_undo_items, apple.max_undo_items);
}
#[test]
fn the_accelerator_test_rejects_the_other_platforms_modifier() {
let session = FormSession::new();
assert!(session.is_accelerator(Modifiers::CONTROL));
assert!(session.is_accelerator(Modifiers::CONTROL | Modifiers::SHIFT));
assert!(!session.is_accelerator(Modifiers::META));
assert!(!session.is_accelerator(Modifiers::NONE));
assert!(!session.is_accelerator(Modifiers::CONTROL | Modifiers::ALT));
assert!(session.is_accelerator(Modifiers::CONTROL | Modifiers::META));
let apple = FormSession::with_config(SessionConfig::apple());
assert!(apple.is_accelerator(Modifiers::META));
assert!(!apple.is_accelerator(Modifiers::CONTROL));
}
#[test]
fn only_widgets_are_focusable_by_default() {
assert_eq!(SessionConfig::default().focusable, vec![Subtype::Widget]);
}
}