| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/a11y/lib/screen_reader/util/util.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "fuchsia/accessibility/semantics/cpp/fidl.h" |
| |
| namespace a11y { |
| |
| namespace { |
| |
| bool NodeHasSubsetOfActions(const fuchsia::accessibility::semantics::Node* node_1, |
| const fuchsia::accessibility::semantics::Node* node_2) { |
| // If node_1 doesn't have any actions, then by definition, its actions must be |
| // a subset of node_2's actions. |
| if (!node_1->has_actions() || node_1->actions().empty()) { |
| return true; |
| } |
| |
| // If node_1 does have actions, and node_2 doesn't, then node_1's actions are |
| // not a subset of node_2's. |
| if (!node_2->has_actions() || node_2->actions().empty()) { |
| return false; |
| } |
| |
| const auto& node_1_actions = node_1->actions(); |
| const auto& node_2_actions = node_2->actions(); |
| |
| // If node_1 contains more actions than node_2, then its actions cannot be a |
| // subset of node_2's. |
| if (node_1_actions.size() > node_2_actions.size()) { |
| return false; |
| } |
| |
| std::set<fuchsia::accessibility::semantics::Action> node_2_actions_set(node_2_actions.begin(), |
| node_2_actions.end()); |
| |
| for (const auto node_1_action : node_1_actions) { |
| // If node_1 has an action that node_2 does not, then node_1's actions are |
| // not a subset of node_2's. |
| if (node_2_actions_set.find(node_1_action) == node_2_actions_set.end()) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| bool IsContainer(const fuchsia::accessibility::semantics::Node* node) { |
| return node->has_role() && (node->role() == fuchsia::accessibility::semantics::Role::TABLE || |
| node->role() == fuchsia::accessibility::semantics::Role::LIST); |
| } |
| |
| bool NodeIsDescribable(const fuchsia::accessibility::semantics::Node* node) { |
| if (!node) { |
| return false; |
| } |
| if (node->has_states() && node->states().has_hidden() && node->states().hidden()) { |
| return false; |
| } |
| |
| bool contains_text = node->has_attributes() && node->attributes().has_label() && |
| !node->attributes().label().empty(); |
| bool is_actionable = |
| node->has_role() && (node->role() == fuchsia::accessibility::semantics::Role::BUTTON || |
| node->role() == fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH || |
| node->role() == fuchsia::accessibility::semantics::Role::RADIO_BUTTON || |
| node->role() == fuchsia::accessibility::semantics::Role::LINK || |
| node->role() == fuchsia::accessibility::semantics::Role::CHECK_BOX || |
| node->role() == fuchsia::accessibility::semantics::Role::SLIDER || |
| node->role() == fuchsia::accessibility::semantics::Role::IMAGE || |
| node->role() == fuchsia::accessibility::semantics::Role::CELL || |
| node->role() == fuchsia::accessibility::semantics::Role::COLUMN_HEADER || |
| node->role() == fuchsia::accessibility::semantics::Role::ROW_HEADER); |
| |
| return (contains_text || is_actionable) && !IsContainer(node); |
| } |
| |
| std::vector<const fuchsia::accessibility::semantics::Node*> GetContainerNodes( |
| zx_koid_t koid, uint32_t node_id, SemanticsSource* semantics_source) { |
| std::vector<const fuchsia::accessibility::semantics::Node*> result; |
| |
| const fuchsia::accessibility::semantics::Node* node = |
| semantics_source->GetParentNode(koid, node_id); |
| while (node) { |
| if (IsContainer(node)) { |
| result.push_back(node); |
| } |
| node = semantics_source->GetParentNode(koid, node->node_id()); |
| } |
| |
| // We reverse the result to get 'deepest-last' order. |
| std::reverse(result.begin(), result.end()); |
| return result; |
| } |
| |
| std::string FormatFloat(float input) { |
| auto output = std::to_string(input); |
| FX_DCHECK(!output.empty()); |
| |
| // If the output string does not contain a decimal point, we don't need to |
| // trim trailing zeros. |
| auto location_of_decimal = output.find('.'); |
| if (location_of_decimal == std::string::npos) { |
| return output; |
| } |
| |
| auto location_of_last_non_zero_character = output.find_last_not_of('0'); |
| // If the last non-zero character is a decimal point, the value is an integer, |
| // so we should remove the decimal point and trailing zeros. |
| if (location_of_last_non_zero_character == location_of_decimal) { |
| return output.erase(location_of_decimal, std::string::npos); |
| } |
| |
| // If the last digit is non-zero, the string has no trailing zeros, so return |
| // the string as is. |
| if (location_of_last_non_zero_character == output.size() - 1) { |
| return output; |
| } |
| |
| // In the last remaining case, the string represents a decimal with trailing |
| // zeros. |
| return output.erase(location_of_last_non_zero_character + 1, std::string::npos); |
| } |
| |
| bool SameInformationAsParent(const fuchsia::accessibility::semantics::Node* node, |
| const fuchsia::accessibility::semantics::Node* parent_node) { |
| FX_DCHECK(node) << "Node should not be null"; |
| |
| if (!parent_node) { |
| return false; |
| } |
| |
| if (!node->has_attributes() || !node->attributes().has_label() || |
| node->attributes().label().empty()) { |
| return false; |
| } |
| if (!parent_node->has_attributes() || !parent_node->attributes().has_label() || |
| parent_node->attributes().label().empty()) { |
| return false; |
| } |
| |
| if (node->attributes().label() != parent_node->attributes().label()) { |
| return false; |
| } |
| |
| if (!NodeIsDescribable(parent_node)) { |
| return false; |
| } |
| |
| if (parent_node->has_child_ids() && parent_node->child_ids().size() != 1) { |
| return false; |
| } |
| |
| if (!NodeHasSubsetOfActions(node, parent_node)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool NodeIsSlider(const fuchsia::accessibility::semantics::Node* node) { |
| FX_DCHECK(node); |
| |
| bool has_role_slider = |
| node->has_role() && node->role() == fuchsia::accessibility::semantics::Role::SLIDER; |
| bool has_range_value = node->has_states() && node->states().has_range_value(); |
| |
| return has_role_slider || has_range_value; |
| } |
| |
| std::string GetSliderValue(const fuchsia::accessibility::semantics::Node& node) { |
| if (!node.has_states()) { |
| return std::string(); |
| } |
| |
| if (node.states().has_range_value()) { |
| return std::to_string(static_cast<int>((node.states().range_value()))); |
| } |
| |
| if (node.states().has_value() && !node.states().value().empty()) { |
| return node.states().value(); |
| } |
| |
| return std::string(); |
| } |
| |
| } // namespace a11y |