Select: Link Block Series ignores selected type that Select All cannot reach

Type selected by id on a hidden, locked or template layer, or outside
the isolated group, no longer counts as selected type: the command stays
off and does not select that frame's thread. The check reads the
object's ancestry, so the Select menu does not walk the whole document.
This commit is contained in:
dotCooCoo
2026-11-02 15:21:51 +07:00
committed by bflatastic
parent 012a50815f
commit 405cc83cae
4 changed files with 49 additions and 8 deletions
+11 -6
View File
@@ -11,7 +11,7 @@ use vectorcraft_geom::Vec2;
use super::edit::selected_roots;
use super::pathops::Beside;
use super::select::{selectable_ids, walk_selectable};
use super::select::{is_selectable, selectable_ids, walk_selectable};
use super::{
CommandSpec, EngineError, Point, Result, Session, bad, bool_or, checked_ids_param, cmd, f64_or, f64_req, has_doc, has_selection, id_param, ok,
str_param,
@@ -32,7 +32,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Link Block Series",
["Select", "Same"],
None,
"{} select every frame of the threads of the selected type objects (and of the visible, unlocked type inside selected groups); type in no thread selects itself; frames that are hidden or locked, inside a hidden or locked object or layer, on a template layer, or outside the isolated group or layer are left out → {count}",
"{} select every frame of the threads of the selected type objects (and of the type inside selected groups); type in no thread selects itself; type and frames that are hidden or locked, inside a hidden or locked object or layer, on a template layer, or outside the isolated group or layer neither count as selected nor are selected → {count}",
has_text_selection,
same_link_block_series
),
@@ -322,8 +322,9 @@ fn same_text(s: &mut Session, cmd: &str, eq: fn(&CharStyle, &CharStyle) -> bool)
finish_same(s, cmd, ids)
}
/// The type objects among the selected objects and inside them, in selection order, leaving out
/// every hidden or locked object with what it holds.
/// The type objects among the selected objects and inside them, in selection order, keeping only
/// those [`walk_selectable`] visits: none that is hidden, locked, on a hidden, locked or template
/// layer, or outside the isolated group.
fn selected_text(s: &Session) -> Vec<NodeId> {
fn inside(n: &Node, out: &mut Vec<NodeId>) {
if !n.visible || n.locked {
@@ -338,8 +339,12 @@ fn selected_text(s: &Session) -> Vec<NodeId> {
}
let Some(st) = s.active() else { return vec![] };
let mut out = vec![];
for n in st.selection.objects.iter().filter_map(|id| st.doc.node(*id)) {
inside(n, &mut out);
for id in &st.selection.objects {
if is_selectable(&st.doc, st.isolation, *id)
&& let Some(n) = st.doc.node(*id)
{
inside(n, &mut out);
}
}
out
}
+19
View File
@@ -249,6 +249,25 @@ pub(crate) fn selectable_ids(d: &Document, iso: Option<NodeId>) -> BTreeSet<Node
ids
}
/// Whether [`walk_selectable`] visits `id`, read from the objects that hold it instead of a walk of
/// the document.
pub(crate) fn is_selectable(d: &Document, iso: Option<NodeId>, id: NodeId) -> bool {
let Some(a) = d.ancestry(id) else { return false };
if !a.iter().all(|i| d.node(*i).is_some_and(|n| n.visible && !n.locked && !n.is_template())) {
return false;
}
match iso.and_then(|i| d.node(i)) {
// Inside the isolated container, or the single object isolated.
Some(c) => a.iter().position(|i| *i == c.id).is_some_and(|p| p + 1 < a.len() || !c.is_container()),
None => {
let locks = d.locked_artboards();
// The object Select All takes: the outermost one that is not a layer.
let top = a.iter().filter_map(|i| d.node(*i)).find(|n| !n.is_layer());
top.is_some_and(|t| locks.is_empty() || !locks.holds(t))
}
}
}
fn all(s: &mut Session, _: &Value) -> Result<Value> {
// While the Type tool edits text, Select All takes all of that text and leaves the art
// selection as it is (Illustrator: text cursor in a text object).
+16
View File
@@ -1993,6 +1993,22 @@ fn select_same_link_block_series_selects_the_frames_of_a_thread() {
assert!(s.execute("select.same.linkBlockSeries", &json!({})).is_err(), "{props}");
assert_eq!(selected(&s), vec![g], "{props}");
}
s.execute("layer.setProps", &json!({"id": d.0, "locked": false})).unwrap();
// A frame on a hidden layer, selected by id, is not type to start from.
let l1 = s.doc().unwrap().doc.layers[0].id;
s.execute("layer.setProps", &json!({"id": l1.0, "visible": false})).unwrap();
sel(&mut s, &[a]);
assert!(!find_command("select.same.linkBlockSeries").unwrap().info(&s).enabled);
assert!(s.execute("select.same.linkBlockSeries", &json!({})).is_err());
s.execute("layer.setProps", &json!({"id": l1.0, "visible": true})).unwrap();
// In an isolated group, only the frames inside it count and are selected.
s.execute("object.isolate", &json!({"id": g.0})).unwrap();
sel(&mut s, &[d]);
assert_eq!(s.execute("select.same.linkBlockSeries", &json!({})).unwrap()["count"], 1);
assert_eq!(selected(&s), vec![d]);
sel(&mut s, &[a]);
assert!(!find_command("select.same.linkBlockSeries").unwrap().info(&s).enabled);
assert!(s.execute("select.same.linkBlockSeries", &json!({})).is_err());
}
/// #1737: Select > Same reads type's fill and stroke from its characters (the first run), so type
+3 -2
View File
@@ -4886,8 +4886,9 @@ doesn't exist, a `port` other than `in` or `out`, and a side where no frame of a
`select.same.linkBlockSeries` (Select › Same › Link Block Series) selects every frame of the threads that the selected
type objects, and the type inside selected groups, are in, in thread order, and returns `{count}`. Type in no thread
selects itself, and other selected objects are left out. With frames of several threads selected, it selects the frames
of all of them. Hidden or locked type inside a selected group does not count as selected, and hidden or locked frames of
a thread are left out. It is off while no type is selected, and records no undo step.
of all of them. Type that is hidden or locked, inside a hidden or locked group or layer, on a template layer, or outside
the isolated group does not count as selected, and such frames of a thread are left out. It is off while no other type
is selected, and records no undo step.
In every thread, a paragraph that continues from one frame into the next has its first line, first-line indent and
bullet or number in the frame where it starts. In the next frame its first line starts where its wrapped lines start,