use teksilo_i18n::tr_widget;
use teksilo_core::event::Key;
use teksilo_core::intent::Intent;
use teksilo_core::shortcut::KeyStroke;
use teksilo_core::widget::Widget;
use crate::keystroke_format::format_keystroke;
use crate::menu_item::MenuItem;
use crate::menu_list::MenuList;
use super::clipboard as rt_clipboard;
use super::policy::{ClipboardPolicy, EditCommandKind, PolicyBundle};
use super::state::SharedState;
pub const INTENT_CUT: &str = "teksilo.rich_text.cut";
pub const INTENT_COPY: &str = "teksilo.rich_text.copy";
pub const INTENT_PASTE: &str = "teksilo.rich_text.paste";
pub const INTENT_PASTE_UNFORMATTED: &str = "teksilo.rich_text.paste_unformatted";
pub const INTENT_SELECT_ALL: &str = "teksilo.rich_text.select_all";
pub(super) fn default_factory(state: SharedState) -> RichTextContextMenuFactory {
Box::new(move |pos, _ctx| {
super::mouse::reposition_caret_for_context_menu(&state, pos);
let state_for_build = state.clone();
Some(Box::new(build_menu(state_for_build)) as Box<dyn Widget>)
})
}
fn build_menu(state: SharedState) -> MenuList {
let mut list = MenuList::new();
let policy: PolicyBundle = state.borrow().policy;
let has_selection = state.borrow().cursor.has_selection();
let doc_non_empty = !state
.borrow()
.document
.to_plain_text()
.unwrap_or_default()
.is_empty();
if policy.clipboard_policy.allows_cut() && policy.command_filter.accepts(EditCommandKind::Cut) {
let state_for_cut = state.clone();
list = list.item(
MenuItem::new(tr_widget!(menu_cut()))
.shortcut_label(format_keystroke(KeyStroke::command(Key::X)))
.enabled(has_selection)
.on_activate_fn(move |evt_ctx| {
let mut st = state_for_cut.borrow_mut();
rt_clipboard::cut(&mut st, evt_ctx);
drop(st);
super::sync_cursor_signals(&state_for_cut);
evt_ctx.request_frame();
evt_ctx.send_intent(Intent::new(INTENT_CUT));
}),
);
}
{
let state_for_copy = state.clone();
list = list.item(
MenuItem::new(tr_widget!(menu_copy()))
.shortcut_label(format_keystroke(KeyStroke::command(Key::C)))
.enabled(has_selection)
.on_activate_fn(move |evt_ctx| {
let mut st = state_for_copy.borrow_mut();
rt_clipboard::copy(&mut st, evt_ctx);
drop(st);
evt_ctx.send_intent(Intent::new(INTENT_COPY));
}),
);
}
if policy.clipboard_policy.allows_paste() {
let state_for_paste = state.clone();
list = list.item(
MenuItem::new(tr_widget!(menu_paste()))
.shortcut_label(format_keystroke(KeyStroke::command(Key::V)))
.on_activate_fn(move |evt_ctx| {
let mut st = state_for_paste.borrow_mut();
rt_clipboard::paste(&mut st, evt_ctx);
drop(st);
super::sync_cursor_signals(&state_for_paste);
evt_ctx.request_frame();
evt_ctx.send_intent(Intent::new(INTENT_PASTE));
}),
);
}
if policy.clipboard_policy.allows_paste_unformatted() {
let state_for_pu = state.clone();
list = list.item(
MenuItem::new(tr_widget!(menu_paste_unformatted()))
.shortcut_label(format_keystroke(KeyStroke::command_shift(Key::V)))
.on_activate_fn(move |evt_ctx| {
let mut st = state_for_pu.borrow_mut();
rt_clipboard::paste_unformatted(&mut st, evt_ctx);
drop(st);
super::sync_cursor_signals(&state_for_pu);
evt_ctx.request_frame();
evt_ctx.send_intent(Intent::new(INTENT_PASTE_UNFORMATTED));
}),
);
}
if policy
.command_filter
.accepts(EditCommandKind::ToggleBlockquote)
{
let state_for_bq = state.clone();
let cross_frame_selection = state.borrow().cursor.selection_spans_multiple_frames();
let in_quote = state.borrow().cursor.is_in_blockquote();
let label = if in_quote {
tr_widget!(menu_remove_blockquote())
} else {
tr_widget!(menu_toggle_blockquote())
};
list = list.separator();
list = list.item(
MenuItem::new(label)
.enabled(!cross_frame_selection)
.on_activate_fn(move |evt_ctx| {
{
let st = state_for_bq.borrow();
let _ = st.cursor.toggle_blockquote();
}
super::sync_cursor_signals(&state_for_bq);
evt_ctx.request_frame();
}),
);
}
if matches!(policy.clipboard_policy, ClipboardPolicy::Full) {
list = list.separator();
}
{
let state_for_sa = state.clone();
list = list.item(
MenuItem::new(tr_widget!(menu_select_all()))
.shortcut_label(format_keystroke(KeyStroke::command(Key::A)))
.enabled(doc_non_empty)
.on_activate_fn(move |evt_ctx| {
{
let mut st = state_for_sa.borrow_mut();
st.cursor
.select(teksilo_text::text_document::SelectionType::Document);
st.select_all_level = 0;
st.select_all_anchor_cell = None;
}
super::sync_cursor_signals(&state_for_sa);
evt_ctx.request_frame();
evt_ctx.send_intent(Intent::new(INTENT_SELECT_ALL));
}),
);
}
list
}
pub(super) fn resolve_factory(
user_factory: Option<RichTextContextMenuFactory>,
default_enabled: bool,
state: SharedState,
) -> Option<RichTextContextMenuFactory> {
if let Some(user) = user_factory {
return Some(user);
}
if default_enabled {
return Some(default_factory(state));
}
None
}
pub(super) type RichTextContextMenuFactory = Box<
dyn Fn(
teksilo_canvas::Point,
&mut teksilo_core::widget::EventContext,
) -> Option<Box<dyn Widget>>,
>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intent_names_use_reserved_teksilo_prefix() {
assert_eq!(INTENT_CUT, "teksilo.rich_text.cut");
assert_eq!(INTENT_COPY, "teksilo.rich_text.copy");
assert_eq!(INTENT_PASTE, "teksilo.rich_text.paste");
assert_eq!(
INTENT_PASTE_UNFORMATTED,
"teksilo.rich_text.paste_unformatted"
);
assert_eq!(INTENT_SELECT_ALL, "teksilo.rich_text.select_all");
}
}