Skip to content

Commit

Permalink
feat: Add list box support to the consumer and atspi-common crates
Browse files Browse the repository at this point in the history
  • Loading branch information
DataTriny committed Dec 21, 2024
1 parent 46ed99b commit 058b714
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 11 deletions.
88 changes: 88 additions & 0 deletions consumer/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,33 @@ impl<'a> Node<'a> {
self.supports_action(Action::Click)
}

pub fn is_selectable(&self) -> bool {
// It's selectable if it has the attribute, whether it's true or false.
self.is_selected().is_some() && !self.is_disabled()
}

pub fn is_multiselectable(&self) -> bool {
self.data().is_multiselectable()
}

pub fn size_of_set_from_container(
&self,
filter: &impl Fn(&Node) -> FilterResult,
) -> Option<usize> {
self.selection_container(filter)
.and_then(|c| c.size_of_set())
}

pub fn size_of_set(&self) -> Option<usize> {
// TODO: compute this if it is not provided (#9).
self.data().size_of_set()
}

pub fn position_in_set(&self) -> Option<usize> {
// TODO: compute this if it is not provided (#9).
self.data().position_in_set()
}

pub fn supports_toggle(&self) -> bool {
self.toggled().is_some()
}
Expand Down Expand Up @@ -623,6 +650,44 @@ impl<'a> Node<'a> {
self.data().is_selected()
}

pub fn is_item_like(&self) -> bool {
matches!(
self.role(),
Role::Article
| Role::Comment
| Role::ListItem
| Role::MenuItem
| Role::MenuItemRadio
| Role::Tab
| Role::MenuItemCheckBox
| Role::TreeItem
| Role::ListBoxOption
| Role::MenuListOption
| Role::RadioButton
| Role::DescriptionListTerm
| Role::Term
)
}

pub fn is_container_with_selectable_children(&self) -> bool {
matches!(
self.role(),
Role::ComboBox
| Role::EditableComboBox
| Role::Grid
| Role::ListBox
| Role::ListGrid
| Role::Menu
| Role::MenuBar
| Role::MenuListPopup
| Role::RadioGroup
| Role::TabList
| Role::Toolbar
| Role::Tree
| Role::TreeGrid
)
}

pub fn raw_text_selection(&self) -> Option<&TextSelection> {
self.data().text_selection()
}
Expand Down Expand Up @@ -690,6 +755,29 @@ impl<'a> Node<'a> {
}
None
}

pub fn selection_container(&self, filter: &impl Fn(&Node) -> FilterResult) -> Option<Node<'a>> {
self.filtered_parent(&|parent| {
if parent.is_container_with_selectable_children() {
filter(parent)
} else {
FilterResult::ExcludeNode
}
})
}

pub fn items(
&self,
filter: impl Fn(&Node) -> FilterResult + 'a,
) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
self.filtered_children(move |child| {
if child.is_item_like() {
filter(child)
} else {
FilterResult::ExcludeNode
}
})
}
}

struct SpacePrefixingWriter<W: fmt::Write> {
Expand Down
55 changes: 49 additions & 6 deletions platforms/atspi-common/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct AdapterChangeHandler<'a> {
added_nodes: HashSet<NodeId>,
removed_nodes: HashSet<NodeId>,
checked_text_change: HashSet<NodeId>,
selection_changed: HashSet<NodeId>,
}

impl<'a> AdapterChangeHandler<'a> {
Expand All @@ -41,6 +42,7 @@ impl<'a> AdapterChangeHandler<'a> {
added_nodes: HashSet::new(),
removed_nodes: HashSet::new(),
checked_text_change: HashSet::new(),
selection_changed: HashSet::new(),
}
}

Expand All @@ -53,8 +55,8 @@ impl<'a> AdapterChangeHandler<'a> {

let role = node.role();
let is_root = node.is_root();
let node = NodeWrapper(node);
let interfaces = node.interfaces();
let wrapper = NodeWrapper(node);
let interfaces = wrapper.interfaces();
self.adapter.register_interfaces(node.id(), interfaces);
if is_root && role == Role::Window {
let adapter_index = self
Expand All @@ -66,13 +68,16 @@ impl<'a> AdapterChangeHandler<'a> {
self.adapter.window_created(adapter_index, node.id());
}

let live = node.live();
let live = wrapper.live();
if live != Live::None {
if let Some(name) = node.name() {
if let Some(name) = wrapper.name() {
self.adapter
.emit_object_event(node.id(), ObjectEvent::Announcement(name, live));
}
}
if let Some(true) = node.is_selected() {
self.enqueue_selection_changed_if_needed(node);
}
}

fn add_subtree(&mut self, node: &Node) {
Expand All @@ -91,14 +96,17 @@ impl<'a> AdapterChangeHandler<'a> {

let role = node.role();
let is_root = node.is_root();
let node = NodeWrapper(node);
let wrapper = NodeWrapper(node);
if is_root && role == Role::Window {
self.adapter.window_destroyed(node.id());
}
self.adapter
.emit_object_event(node.id(), ObjectEvent::StateChanged(State::Defunct, true));
self.adapter
.unregister_interfaces(node.id(), node.interfaces());
.unregister_interfaces(node.id(), wrapper.interfaces());
if let Some(true) = node.is_selected() {
self.enqueue_selection_changed_if_needed(node);
}
}

fn remove_subtree(&mut self, node: &Node) {
Expand Down Expand Up @@ -235,6 +243,36 @@ impl<'a> AdapterChangeHandler<'a> {
}
}
}

fn enqueue_selection_changed_if_needed_parent(&mut self, node: Node) {
if !node.is_container_with_selectable_children() {
return;
}
let id = node.id();
if self.selection_changed.contains(&id) {
return;
}
self.selection_changed.insert(id);
}

fn enqueue_selection_changed_if_needed(&mut self, node: &Node) {
if !node.is_item_like() {
return;
}
if let Some(node) = node.selection_container(&filter) {
self.enqueue_selection_changed_if_needed_parent(node);
}
}

fn emit_selection_changed(&mut self) {
for id in self.selection_changed.iter() {
if self.removed_nodes.contains(id) {
continue;
}
self.adapter
.emit_object_event(*id, ObjectEvent::SelectionChanged);
}
}
}

impl TreeChangeHandler for AdapterChangeHandler<'_> {
Expand Down Expand Up @@ -275,6 +313,9 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> {
let bounds = *self.adapter.context.read_root_window_bounds();
new_wrapper.notify_changes(&bounds, self.adapter, &old_wrapper);
self.emit_text_selection_change(Some(old_node), new_node);
if new_node.is_selected() != old_node.is_selected() {
self.enqueue_selection_changed_if_needed(new_node);
}
}
}

Expand Down Expand Up @@ -466,6 +507,8 @@ impl Adapter {
let mut handler = AdapterChangeHandler::new(self);
let mut tree = self.context.tree.write().unwrap();
tree.update_and_process_changes(update, &mut handler);
drop(tree);
handler.emit_selection_changed();
}

pub fn update_window_focus_state(&mut self, is_focused: bool) {
Expand Down
1 change: 1 addition & 0 deletions platforms/atspi-common/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub enum ObjectEvent {
ChildAdded(usize, NodeId),
ChildRemoved(NodeId),
PropertyChanged(Property),
SelectionChanged,
StateChanged(State, bool),
TextInserted {
start_index: i32,
Expand Down
Loading

0 comments on commit 058b714

Please sign in to comment.