From 7b30ee345cbdf835b38d7a26c1c52a6a5506d084 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Sun, 10 Mar 2024 20:01:24 -0400 Subject: [PATCH 1/2] Accesibility Change One: Show Acessible Info This change does two things 1. If a widget is accessible with a title, use that title for the melatonin sidebar rather than clasname and preferentially over juce name. (If it has both an accessible title and juce name and they differ the juce name is shown additionally) 2. collect and display basic accessible info in the inspector panel. these are title, type, value, and handler class. --- melatonin/component_model.h | 65 +++++++++++++++++++++++++++ melatonin/components/properties.h | 16 +++++++ melatonin/helpers/component_helpers.h | 13 ++++++ 3 files changed, 94 insertions(+) diff --git a/melatonin/component_model.h b/melatonin/component_model.h index 5a8462b..e71c0ef 100644 --- a/melatonin/component_model.h +++ b/melatonin/component_model.h @@ -22,6 +22,12 @@ namespace melatonin juce::Value lookAndFeelValue, typeValue, fontValue, alphaValue; juce::Value pickedColor; juce::Value timing1, timing2, timing3, timingMax, hasChildren; + + struct AccessiblityDetail + { + juce::Value title, value, role, handlerType; + } accessiblityDetail; + double timingWithChildren1, timingWithChildren2, timingWithChildren3, timingWithChildrenMax; ComponentModel() = default; @@ -145,6 +151,65 @@ namespace melatonin interceptsMouseValue.addListener (this); childrenInterceptsMouseValue.addListener (this); + if (selectedComponent->isAccessible() && selectedComponent->getAccessibilityHandler()) + { + auto* accH = selectedComponent->getAccessibilityHandler(); + accessiblityDetail.handlerType = type (*accH); + if (accH->getValueInterface()) + { + accessiblityDetail.value = accH->getValueInterface()->getCurrentValueAsString(); + } + else + { + accessiblityDetail.value = "no value interface"; + } + accessiblityDetail.title = accH->getTitle(); + auto role = accH->getRole(); + switch (role) + { + // Amazingly juce doesn' thave a display name fn for these +#define DN(x) \ + case juce::AccessibilityRole::x: \ + accessiblityDetail.role = #x; \ + break; + DN (button) + DN (toggleButton) + DN (radioButton) + DN (comboBox) + DN (image) + DN (slider) + DN (label) + DN (staticText) + DN (editableText) + DN (menuItem) + DN (menuBar) + DN (popupMenu) + DN (table) + DN (tableHeader) + DN (column) + DN (row) + DN (cell) + DN (hyperlink) + DN (list) + DN (listItem) + DN (tree) + DN (treeItem) + DN (progressBar) + DN (group) + DN (dialogWindow) + DN (window) + DN (scrollBar) + DN (tooltip) + DN (splashScreen) + DN (ignored) + DN (unspecified) +#undef DN + default: + accessiblityDetail.role = juce::String ("Unknown ") + juce::String ((int) role); + break; + } + } + { bool interceptsMouse = false; bool childrenInterceptsMouse = false; diff --git a/melatonin/components/properties.h b/melatonin/components/properties.h index 7f71ab6..63a9272 100644 --- a/melatonin/components/properties.h +++ b/melatonin/components/properties.h @@ -68,6 +68,22 @@ namespace melatonin } panel.addProperties (props, padding); + if (model.accessibilityHandledValue.getValue()) + { + auto& ad = model.accessiblityDetail; + auto aprops = juce::Array { + new juce::TextPropertyComponent (ad.title, "Title", 200, false, false), + new juce::TextPropertyComponent (ad.value, "Value", 200, false, false), + new juce::TextPropertyComponent (ad.role, "Role", 200, false, false), + new juce::TextPropertyComponent (ad.handlerType, "Handler", 200, false, false), + }; + for (auto* p : aprops) + { + p->setLookAndFeel (&getLookAndFeel()); + } + panel.addSection ("Accessibility", aprops); + } + resized(); } diff --git a/melatonin/helpers/component_helpers.h b/melatonin/helpers/component_helpers.h index f4c1838..ad22121 100644 --- a/melatonin/helpers/component_helpers.h +++ b/melatonin/helpers/component_helpers.h @@ -49,6 +49,19 @@ namespace melatonin return juce::String ("Editor: ") + editor->getAudioProcessor()->getName(); } #endif + else if (c && c->isAccessible() && c->getAccessibilityHandler() && !c->getAccessibilityHandler()->getTitle().isEmpty()) + { + // If a widget has an accessible name, prefer that to the internal + // name since it is user facing in a screen reader + auto acctitle = c->getAccessibilityHandler()->getTitle(); + auto nm = c->getName(); + if (nm != acctitle && !nm.isEmpty()) + { + // but if i also have an internal name, dont' suppress it + acctitle += "(name=" + nm + ")"; + } + return acctitle; + } else if (c && !c->getName().isEmpty()) { return c->getName(); From 1ddf31658120a17438be15ce5eac10c0c1765675 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Mon, 11 Mar 2024 12:58:14 -0400 Subject: [PATCH 2/2] Updates post reviews - Remove clumsy (name=) from the title - Add name to the properties tearsheet - Add an accesibility panel section to the main component - Move the accesible information display into that panel --- melatonin/component_model.h | 3 ++ melatonin/components/accesibility.h | 55 +++++++++++++++++++++++++++ melatonin/components/properties.h | 17 +-------- melatonin/helpers/component_helpers.h | 8 ---- melatonin/inspector_component.h | 9 +++++ 5 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 melatonin/components/accesibility.h diff --git a/melatonin/component_model.h b/melatonin/component_model.h index e71c0ef..e808684 100644 --- a/melatonin/component_model.h +++ b/melatonin/component_model.h @@ -16,6 +16,7 @@ namespace melatonin virtual void componentModelChanged (ComponentModel& model) = 0; }; + juce::Value nameValue; juce::Value widthValue, heightValue, xValue, yValue; juce::Value enabledValue, opaqueValue, hasCachedImageValue, accessibilityHandledValue; juce::Value visibleValue, wantsFocusValue, interceptsMouseValue, childrenInterceptsMouseValue; @@ -127,6 +128,7 @@ namespace melatonin return; } + nameValue = selectedComponent->getName(); lookAndFeelValue = lnfString (selectedComponent); visibleValue = selectedComponent->isVisible(); enabledValue = selectedComponent->isEnabled(); @@ -138,6 +140,7 @@ namespace melatonin typeValue = type (*selectedComponent); accessibilityHandledValue = selectedComponent->isAccessible(); + nameValue.addListener(this); widthValue.addListener (this); heightValue.addListener (this); xValue.addListener (this); diff --git a/melatonin/components/accesibility.h b/melatonin/components/accesibility.h new file mode 100644 index 0000000..13f5daf --- /dev/null +++ b/melatonin/components/accesibility.h @@ -0,0 +1,55 @@ + +#pragma once +#include "melatonin_inspector/melatonin/component_model.h" + +namespace melatonin +{ + class Accessibility : public juce::Component, private ComponentModel::Listener + { + public: + Accessibility(ComponentModel & m) : model(m) { + addAndMakeVisible (&panel); + model.addListener (*this); + } + + void updateProperties() + { + panel.clear(); + + if (!model.getSelectedComponent()) + return; + + auto& ad = model.accessiblityDetail; + auto aprops = juce::Array { + new juce::TextPropertyComponent (ad.title, "Title", 200, false, false), + new juce::TextPropertyComponent (ad.value, "Value", 200, false, false), + new juce::TextPropertyComponent (ad.role, "Role", 200, false, false), + new juce::TextPropertyComponent (ad.handlerType, "Handler", 200, false, false), + }; + for (auto* p : aprops) + { + p->setLookAndFeel (&getLookAndFeel()); + } + panel.addProperties(aprops, 0); + resized(); + } + + protected: + ComponentModel& model; + void componentModelChanged (ComponentModel& model) override { + updateProperties(); + } + + + void resized() override + { + TRACE_COMPONENT(); + // let the property panel know what total height we need to be + panel.setBounds (getLocalBounds().withTrimmedTop (padding)); + } + + int padding {3}; + + juce::PropertyPanel panel { "Accessibility" }; + }; +} diff --git a/melatonin/components/properties.h b/melatonin/components/properties.h index 63a9272..9addc2b 100644 --- a/melatonin/components/properties.h +++ b/melatonin/components/properties.h @@ -68,22 +68,6 @@ namespace melatonin } panel.addProperties (props, padding); - if (model.accessibilityHandledValue.getValue()) - { - auto& ad = model.accessiblityDetail; - auto aprops = juce::Array { - new juce::TextPropertyComponent (ad.title, "Title", 200, false, false), - new juce::TextPropertyComponent (ad.value, "Value", 200, false, false), - new juce::TextPropertyComponent (ad.role, "Role", 200, false, false), - new juce::TextPropertyComponent (ad.handlerType, "Handler", 200, false, false), - }; - for (auto* p : aprops) - { - p->setLookAndFeel (&getLookAndFeel()); - } - panel.addSection ("Accessibility", aprops); - } - resized(); } @@ -98,6 +82,7 @@ namespace melatonin // Always have class up top juce::Array props = { new juce::TextPropertyComponent (model.typeValue, "Class", 200, false, false), + new juce::TextPropertyComponent (model.nameValue, "Name", 200, false, false), }; // Then prioritize model properties diff --git a/melatonin/helpers/component_helpers.h b/melatonin/helpers/component_helpers.h index ad22121..ebecd1e 100644 --- a/melatonin/helpers/component_helpers.h +++ b/melatonin/helpers/component_helpers.h @@ -51,15 +51,7 @@ namespace melatonin #endif else if (c && c->isAccessible() && c->getAccessibilityHandler() && !c->getAccessibilityHandler()->getTitle().isEmpty()) { - // If a widget has an accessible name, prefer that to the internal - // name since it is user facing in a screen reader auto acctitle = c->getAccessibilityHandler()->getTitle(); - auto nm = c->getName(); - if (nm != acctitle && !nm.isEmpty()) - { - // but if i also have an internal name, dont' suppress it - acctitle += "(name=" + nm + ")"; - } return acctitle; } else if (c && !c->getName().isEmpty()) diff --git a/melatonin/inspector_component.h b/melatonin/inspector_component.h index 509cb8d..f41f160 100644 --- a/melatonin/inspector_component.h +++ b/melatonin/inspector_component.h @@ -7,6 +7,7 @@ #include "melatonin_inspector/melatonin/components/component_tree_view_item.h" #include "melatonin_inspector/melatonin/components/preview.h" #include "melatonin_inspector/melatonin/components/properties.h" +#include "melatonin_inspector/melatonin/components/accesibility.h" #include "melatonin_inspector/melatonin/lookandfeel.h" /* @@ -37,12 +38,14 @@ namespace melatonin addChildComponent (colorPicker); addChildComponent (preview); addChildComponent (properties); + addChildComponent (accessibility); // z-order on panels is higher so they are clickable addAndMakeVisible (boxModelPanel); addAndMakeVisible (colorPickerPanel); addAndMakeVisible (previewPanel); addAndMakeVisible (propertiesPanel); + addAndMakeVisible (accessibilityPanel); addAndMakeVisible (searchBox); addAndMakeVisible (searchIcon); @@ -251,6 +254,9 @@ namespace melatonin colorPicker.setBounds (colorPickerBounds.withTrimmedLeft (32)); colorPickerPanel.setBounds (colorPickerBounds.removeFromTop (32).removeFromLeft (200)); + accessibilityPanel.setBounds (mainCol.removeFromTop (32)); + accessibility.setBounds (mainCol.removeFromTop (accessibility.isVisible() ? 110 : 0).withTrimmedLeft(32)); + propertiesPanel.setBounds (mainCol.removeFromTop (33)); // extra pixel for divider properties.setBounds (mainCol.withTrimmedLeft (32)); @@ -391,6 +397,9 @@ namespace melatonin Properties properties { model }; CollapsablePanel propertiesPanel { "PROPERTIES", &properties, true }; + Accessibility accessibility { model }; + CollapsablePanel accessibilityPanel { "ACCESSIBILITY", &accessibility, false }; + // TODO: move to its own component juce::TreeView tree; juce::Label emptySelectionPrompt { "SelectionPrompt", "Select any component to see components tree" };