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.