A small tool to troubleshoot focus issues

While working on accessibility, I find myself often wondering which component has keyboard focus and how it moves between components when I press tab/shift-tab. Here is a small class that I’m using to understand that and improve the keyboard navigation on my interface:

struct FocusDebugger : public Component, public Timer, public DeletedAtShutdown {
  WeakReference<Component> currently_focused_component;
  Rectangle<int> outline_r;

  FocusDebugger() {
    startTimer(50);
    setInterceptsMouseClicks(false, false);
    setOpaque(false);
    setAlwaysOnTop(true);
  }

  void updateMe() {
    if (currently_focused_component) {
      auto r = currently_focused_component->getScreenBounds().expanded(2);
      outline_r = r.withZeroOrigin();
      if (r.getWidth() < 300) r = r.withWidth(300);
      r = r.withHeight(r.getHeight() + 35);
      setBounds(r);
      if (!isVisible()) {
        if (!isOnDesktop()) {
          addToDesktop(ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresMouseClicks |
                       ComponentPeer::windowIgnoresKeyPresses);
        }
        setVisible(true);
      }
      repaint();
    } else {
      setVisible(false);
    }
  }

  void paint(Graphics &g) override {
    Colour outline_colour(0xffff0000);
    g.setColour(outline_colour);
    g.drawRect(outline_r);
    g.setFont(Font(11.f));
    String txt = "null";
    Component *c = currently_focused_component;
    if (c) {
      std::string classname = cxxDemangle(typeid(*c).name());
      classname = classname.substr(0, 30);
      txt = "";
      txt << "class=" + std2juce(classname) << "; name=\"" << c->getName() << "\"; ID=\"" << c->getComponentID() << "\"";
      /*if (c->getProperties().size()) {
        txt << "\nprops={";
        String sprops;
        for (auto &p : c->getProperties()) {
          sprops << (sprops.isNotEmpty() ? ", " : "") << p.name;
          String sval;
          if (p.value.isBool()) sval = ((bool)p.value ? "True":"False");
          else if (p.value.isInt()) sval = String((int)p.value);
          else if (p.value.isInt64()) sval = String((int64_t)p.value);
          else if (p.value.isDouble()) sval = String((double)p.value);
          else if (p.value.isString()) sval = "'" + p.value.operator String() + "'";
          if (sval.isNotEmpty()) sprops << "=" << sval;
        }
        txt += sprops + "}";
        }*/
      txt << "\nfocusOrder=" << c->getExplicitFocusOrder();
    }
    g.setColour(Colours::black.withAlpha(0.6f));
    g.fillRect(0, outline_r.getBottom(), getWidth(), getHeight() - outline_r.getBottom());
    g.setColour(Colours::white);
    g.drawFittedText(txt, getLocalBounds(), Justification::bottomLeft, 4);
  }

  void updateCurrentFocusedComponent() {
    auto *c = Component::getCurrentlyFocusedComponent();
    if (c != currently_focused_component) { currently_focused_component = c; }
  }

  void timerCallback() override {
    updateCurrentFocusedComponent();
    updateMe();
  }

  static void enableFocusDebugger(bool b) {
    static FocusDebugger *instance = nullptr;
    if (instance == nullptr && b) {
      instance = new FocusDebugger();
    } else if (instance && !b)
      delete instance;
  }
};

It just draws a red rectangle around the currently focused component, and displays a few informations about it below (class name, name, component ID)

Note: it does not work on Linux, as the transparent window used to draw a red rectangle around the focused component seems to intercept all mouse events despite having the ‘windowIgnoresMouseClicks’ property.

5 Likes

hi @jpo, I wanted to give this a try, but there are two “undeclared identifier” errors, for cxxDemangle and std2juce. Could you possibly include those functions here as well?

I have put an updated version here:

https://bitbucket.org/jpommier/juce-stuff/src/master/focus_debugger.h

This one should work fine.

2 Likes

Thank you, that’s working for me now. Very handy for visually tracking component focus.