Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add listbox support #498

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading