Keyboard focus for PropertyPanel


#1

I have a settings page in my application that is somewhat similar to the Config tab in the Introjucer in structure, i.e. it has a TreeView on the left and an PropertyPanel on the right. I'd like to be able to move from a TreeViewItem into the first PropertyComponent of the PropertyPanel using a keyboard shortcut (e.g. F2), but I'm a bit stuck working out how to make it work.

I've managed to get focus across to the parent Component that contains the PropertyPanel, but that's as far as I've been able to get. Any tips?


#2

I'm not clear exactly what you're stuck on? Getting the key-press or setting the focus?


#3

It's about getting entering the first PropertyComponent's editor. Let's assume that my first PropertyComponent in a PropertyPanel is a TextPropertyComponent. I want to be able to jump into editing the text in that TextPropertyComponent when the PropertyPanel receives keyboard focus.

I can see in the debugger that I'm getting keyboard focus in the component that contains the PropertyPanel and, in fact, if I press Tab a few times, I can enter the edit fields of a TableListBox that's also a child of the same component, but I can't seem to enter the PropertyPanel PropertyComponents themselves.

I have the following in the constructor of the Component containing the PropertyPanel:

setWantsKeyboardFocus(true);
propertyPanel.setWantsKeyboardFocus(true);

#4

There are a ton of focus-related methods in Component - you can set the focus order, set whether a parent component allows children to be traversed, even set a custom traversal class.. Exactly what you might need to change is hard to guess, but there's almost certainly a method to do what you need!


#5

Yeah, I had a good look through those and I think it would be easy to achieve what I want if I was in control of the PropertyPanel, but there's no access to the PropertyComponents within a PropertyPanel, as far as I can tell.

I'm assuming at this stage that I'll need to implement my own PropertyPanel that does this. Does that sound right?


#6

Hmm.. I'd have hoped it'd be possible by changing focus orders, but not sure TBH.


#7

I've made a little progress by having a play with the PropertyPanel code. If I add a focusGained override to the PropertyPanel class, as follows:

void PropertyPanel::focusGained(FocusChangeType /* cause */)
{
    if (propertyHolderComponent->getNumSections() > 0)
    {
        SectionComponent* firstSection = propertyHolderComponent->getSection (0);

        if (firstSection->getNumPropertyComponents() > 0)
            firstSection->getPropertyComponent (0)->grabKeyboardFocus();
    }
}

(Having added the two following public methods to the PropertyPanel::SectionComponent class)

int getNumPropertyComponents() { return propertyComps.size(); }
PropertyComponent* getPropertyComponent (const int index) const { return propertyComps[index]; }

This definitely transfers the focus into the first PropertyComponent, but to the description Label, not the actual Component (TextEditor in my testing). I can then tab down to the second PropertyComponent and shift-tab back to the first, but can't quite work out how to get it to select the first directly.


#8

Just wondering if anyone has solved this problem before, as I'm a bit stuck moving forward with this?


#9

Hi Jules,

The following change works for me, but is it too evil a hack to add to Juce?

diff --git a/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp b/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp
index dc93d7c..89ab50b 100644
--- a/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp
+++ b/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp
@@ -196,10 +196,12 @@ void PropertyPanel::init()
     addAndMakeVisible (viewport);
     viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
     viewport.setFocusContainer (true);
+    propertyHolderComponent->setWantsKeyboardFocus(true);
 }
 PropertyPanel::~PropertyPanel()
 {
+    setWantsKeyboardFocus(false);
     clear();
 }
@@ -221,6 +223,15 @@ void PropertyPanel::resized()
     updatePropHolderLayout();
 }
+void PropertyPanel::focusGained(FocusChangeType cause)
+{
+    if (cause == focusChangedDirectly && propertyHolderComponent != nullptr)
+    {
+        propertyHolderComponent->grabKeyboardFocus();
+        propertyHolderComponent->moveKeyboardFocusToSibling(true);
+    }
+}
+
 //==============================================================================
 void PropertyPanel::clear()
 {
diff --git a/modules/juce_gui_basics/properties/juce_PropertyPanel.h b/modules/juce_gui_basics/properties/juce_PropertyPanel.h
index 3bd6115..5a4ec9f 100644
--- a/modules/juce_gui_basics/properties/juce_PropertyPanel.h
+++ b/modules/juce_gui_basics/properties/juce_PropertyPanel.h
@@ -148,6 +148,8 @@ public:
     /** @internal */
     void resized() override;
+    void focusGained(FocusChangeType cause) override;
+
 private:
     class SectionComponent;

Thanks,
Will


#10

Thinking about it, as an alternative, I'd be happy with a protected getPropertyHolderComponent() method, as I could then inherit from the PropertyPanel and perform my nefarious acts on that instead.


#11

I'm a bit confused about why that's necessary - if the propertycomponent has setWantsKeyboardFocus (false) then calling grabKeyboardFocus on it should pass the focus down to its child (?)


#12

I've tried that too, but the focus gets stolen by the PropertyPanel's Viewport on the way down through the focus tree. If I change the PropertyPanel's init() method to have viewport.setWantsKeyboardFocus (false); in it, the focus traversal gives up at that point and passes focus back up to the parent.

If I comment out the viewport.setFocusContainer (true); line, it carries on passing it down to the first PropertyComponent, but then I run into the issue that the LabelComp of the TextPropertyComponent doesn't give focus to the TextEditor automatically.

It all hurts my head a bit ;)

The method I described above has been the only way I've managed so far to get it to actually select the first TextPropertyComponent's TextEditor.