mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 14:08:23 -06:00
fix(ui): menu Redo is enabled for the open text box's own Redo
Edit ▸ Redo and the toolbar button followed only the document's Redo. After Undo in a paragraph or added text box took the typing back, with no document step to redo, they stayed disabled and only the key could put the typing back. Redo is now also enabled when the box open on the page has a Redo of its own: the step redo_in_page_box takes. It doesn't ask for the box's focus, which the menu has taken by then.
This commit is contained in:
@@ -84,7 +84,9 @@ impl PdfCraftApp {
|
||||
if self.combine_showing() && matches!(spec.needs, commands::Needs::Undo | commands::Needs::Redo) {
|
||||
return self.combine_can_undo(spec.needs == commands::Needs::Undo);
|
||||
}
|
||||
commands::is_enabled(spec, &self.session, self.active_ids().map(|(_, id)| id))
|
||||
// Redo also takes back what Undo took in a box open on the page, with no document step.
|
||||
(commands::is_enabled(spec, &self.session, self.active_ids().map(|(_, id)| id))
|
||||
|| spec.needs == commands::Needs::Redo && self.open_box_can_redo())
|
||||
&& (spec.id != "edit.copy"
|
||||
|| self
|
||||
.active
|
||||
|
||||
@@ -333,8 +333,19 @@ impl PdfCraftApp {
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
egui::TextEdit::load_state(ctx, id)
|
||||
.is_some_and(|state| state.cursor.char_range().is_some_and(|range| state.undoer().has_redo(&(range, text.clone()))))
|
||||
has_typing_to_redo(ctx, id, text)
|
||||
}
|
||||
|
||||
/// Whether the box open on the page has a Redo of its own, the step
|
||||
/// [`Self::redo_in_page_box`] takes for Redo from the menu or toolbar: focus has moved to the
|
||||
/// menu by then, so unlike [`Self::page_box_can_redo`] this doesn't ask for it.
|
||||
pub(crate) fn open_box_can_redo(&self) -> bool {
|
||||
let Some(ctx) = &self.ctx else { return false };
|
||||
let Some(view) = self.active.and_then(|i| self.views.get(i)) else { return false };
|
||||
if let Some(ed) = &view.line_editor {
|
||||
return has_typing_to_redo(ctx, crate::edit_text_ui::input_id(), &ed.text);
|
||||
}
|
||||
view.content.draft.as_ref().is_some_and(|d| has_typing_to_redo(ctx, crate::content_ui::input_id(), &d.text))
|
||||
}
|
||||
|
||||
/// Typing in a box on the page (a paragraph being edited, added text) that the page doesn't
|
||||
@@ -1343,6 +1354,12 @@ fn bookmark_at<'a>(items: &'a [pdfcraft_render::OutlineItem], path: &[usize]) ->
|
||||
if rest.is_empty() { Some(item) } else { bookmark_at(&item.children, rest) }
|
||||
}
|
||||
|
||||
/// Whether the page text box `id`, showing `text`, has typing its own Redo would put back: the
|
||||
/// test egui makes when it handles the key.
|
||||
fn has_typing_to_redo(ctx: &egui::Context, id: egui::Id, text: &str) -> bool {
|
||||
egui::TextEdit::load_state(ctx, id)
|
||||
.is_some_and(|state| state.cursor.char_range().is_some_and(|range| state.undoer().has_redo(&(range, text.to_owned()))))
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{SaveTarget, is_mail_temp_copy, save_dialog_folder, write_atomically, write_atomically_syncing, write_atomically_with};
|
||||
|
||||
@@ -1038,6 +1038,31 @@ fn redo_from_the_menu_with_retyping_in_the_box_keeps_the_documents_redo() {
|
||||
assert_eq!(s.session.get(s.views[0].id).unwrap().can_redo(), Some("Edit content"), "the document's Redo still waits");
|
||||
}
|
||||
|
||||
/// Redo from the menu after Undo in an added text box, with nothing for the document to redo: the
|
||||
/// menu item follows the box's own Redo too, and puts the typing back.
|
||||
#[test]
|
||||
fn redo_from_the_menu_is_enabled_for_the_added_text_boxs_own_typing() {
|
||||
let mut h = harness();
|
||||
let rect = [40.0, 195.0, 240.0, 210.0];
|
||||
let text = pdfcraft_engine::AddedText { rect, text: "Reviewed".into(), ..Default::default() };
|
||||
assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::AddText { page: 0, text }));
|
||||
assert!(h.state_mut().execute("edit.text"));
|
||||
h.run_steps(3);
|
||||
let point = at(&h, 45.0, 195.0);
|
||||
click(&mut h, point);
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().and_then(|d| d.index), Some(0), "the editor opened on the text");
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::End);
|
||||
h.event(egui::Event::Text(" twice".into()));
|
||||
h.run_steps(2);
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::Z);
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().map(|d| d.text.as_str()), Some("Reviewed"), "Undo takes it back in the box");
|
||||
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().can_redo(), None, "the document has nothing to redo");
|
||||
assert!(h.state_mut().execute("edit.redo"), "Redo is enabled for the box's own step");
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().map(|d| d.text.as_str()), Some("Reviewed twice"), "Redo puts it back");
|
||||
}
|
||||
|
||||
/// Redo with an added text box open and unchanged reaches the document's waiting Redo, even when
|
||||
/// the box's text ends in a space: closing the unchanged box writes nothing, so it can't clear
|
||||
/// that Redo first.
|
||||
|
||||
@@ -6478,6 +6478,29 @@ fn redo_from_the_menu_with_typing_in_the_box_keeps_the_documents_redo() {
|
||||
assert_eq!(s.session.get(s.views[0].id).unwrap().can_redo(), Some("Edit text"), "the document's Redo still waits");
|
||||
}
|
||||
|
||||
/// Redo from the menu after Undo in the box took the typing back, with nothing for the document
|
||||
/// to redo: the menu item follows the box's own Redo too, and puts the typing back. It was
|
||||
/// disabled, as it followed only the document's Redo, and only the key could do it.
|
||||
#[test]
|
||||
fn redo_from_the_menu_is_enabled_for_the_boxs_own_typing() {
|
||||
let mut h = harness(1, |_| {});
|
||||
assert!(h.state_mut().execute("edit.edit_text"));
|
||||
h.run_steps(2);
|
||||
open_paragraph(&mut h);
|
||||
let original = h.state().views[0].line_editor.as_ref().unwrap().text.clone();
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::End);
|
||||
h.event(egui::Event::Text(" more".into()));
|
||||
h.run_steps(2);
|
||||
let typed = h.state().views[0].line_editor.as_ref().unwrap().text.clone();
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::Z);
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].line_editor.as_ref().unwrap().text, original, "the typing is taken back");
|
||||
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().can_redo(), None, "the document has nothing to redo");
|
||||
assert!(h.state_mut().execute("edit.redo"), "Redo is enabled for the box's own step");
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].line_editor.as_ref().expect("still open").text, typed, "Redo puts the typing back");
|
||||
}
|
||||
|
||||
/// Two copies of a document, the same paragraph open in each. The paragraph boxes share one text
|
||||
/// box, and so one Undo history: a box opened on the same text as another tab's didn't clear it.
|
||||
/// Typing in one tab, taken back with Undo, then came back with Redo (the key or the menu) in the
|
||||
|
||||
Reference in New Issue
Block a user