TextPropertyComponent with only integers?

Is there a way to make the TextPropertyComponent to handle only integers?

The getText/setText override seems a dead end.

If the TextPropertyComponent would expose the TextEditor there was the chance to use the InputFilter.

I think that would be the preferred way.

1 Like

Good idea, but sadly AFAIK everything is kept private in TextPropertyComponent.
The narrow interface makes it hard for subclasses to change the behaviour.
(Or there is something obvious i can not see).
I thought to put a CachedValue<int> somewhere but without any success. :thinking:

If you’re overriding setText() in a custom TextPropertyComponent, could you just do some checks that the text only contains characters 0-9?

void setText (const juce::String& text) override
{
    jassert(text.containsOnly ("0123456789");

    const auto intValue = text.getIntValue();
}
1 Like

The non destructive way is to inherit the TextPropertyComponent and add your InputFilter in the virtual createTextEditor command:

class MyTextPropertyComponent : public juce::TextPropertyComponent
{
    class NumberFilter : public juce::TextEditor::InputFilter
    {
    public:
        NumberFilter() = default;
        juce::String filterNewText (juce::TextEditor &, const juce::String &newInput)
        {
            // do your filter here
        }
    };

    juce::TextEditor* createEditorComponent() override
    {
        auto* ed = juce::TextPropertyComponent::createEditorComponent();
        ed->setInputFilter (new NumberFilter(), true);
        return ed;
    }
};

Hope that helps

2 Likes

@ImJimmi

After scratching my head one hour more i see how to do that now.
I need to override both getText/setText methods.
Not so hard but not easy (intuitive) to understand the trick at first.
I’ll post the result later.

@daniel

Thanks, i’ll try it and see if it is better that the last minute idea i get. :grinning_face_with_smiling_eyes:

Never mind, it doesn’t work. I looked at the wrong class, i.e. the wrapped TextPropertyComponent::LabelComp. We have no access to that, so it is impossible without the juce team allowing to access the editor.

Something like this would be needed:

juce::TextEditor* TextPropertyComponent::getTextEditor();

And even that has the problem that the textEditor in the TextPropertyComponent is in fact derrived from juce::Label. That means the TextEditor we want to modify is only created once clicked on the Label.

Options (for the juce authors)
a) the InputFilter could be set on the Label and the label could be accessed with a getter
b) the InputFilter could be set on the TextPropertyComponent (awkward)
c) add a Callback to override:

TextPropertyComponent::editingStarted (juce::TextEditor& editor) override
{
    editor.setInputFilter (new MyInputFilter(), true);
}
1 Like

Something like that seems to work roughly (old value could be cached to avoid to set to zero when bad entry is catched). One problem is that the var into the ValueTree attached seems to be set twice. Once with the wrong value, and just after with the good one. It could be a problem.

public:
    juce::String getText() const override
    {
        return parsed (TextPropertyComponent::getText());
    }
        
    void setText (const juce::String& s) override
    {
        TextPropertyComponent::setText (parsed (s));
    }

private:
    juce::String parsed (const juce::String& s) const
    {
        return juce::String (s.getIntValue());
    }

If you need to preserve leading zeroes (so that “001” does not become “1”), String::retainCharacters ("0123456789") might be a better option than String::getIntValue().

1 Like

Ok it works well, except that it changes the type of the var in my ValueTree! Since it is created with TextPropertyComponent (const Value &valueToControl, …) and that the Value is always assigned with a text string. Is there an obvious way to avoid that?

the JUCE framework uses “private” for way too many things. most forum posts deal with people wanting things to be accessible that are clearly there. idk why it’s done like that cause as far as i know the “protected” keyword is made just for that: member variables that should only be accessible when you make a sub class and else hidden from the user to make it easier to actually use a class’s object. my personal approach is: only make things private if you are really really really really really really (i typed out all of these manually) sure that this particular object will never have anyone wanting to access it

The problem is that the Value is owned/refered by the Label class inside.
And the Label class is used everywhere in JUCE. :laughing:

Is there a way to wrap a Value inside a kinda type constrainer (such as CachedValue) that could also be used inside the TextPropertyComponent constructor? :thinking:

I wanted to find a solution for this same issue, and so found this thread.

Here’s what I came up with –– seems to be working well.

Setting the allowDecimal_ argument to false effectively makes the component display only integers. Setting allowDecimal_ to true means the content is restrained to any numeral plus decimal points – but note that does not constrain the value to floats, since it allows for multiple decimal points. This is useful for entering values like IP addresses. Of course, with a little more work, constraining to a valid float string would also be possible.

The trick here is it holds its own copy of the valueToControl, via the referTo call. Then it acts as a listener for any changes to that value. When it sees a change, it calls TextPropertyComponent::refresh, which forces the overridden getText to run, constraining the value.

class NumericTextPropertyComponent  : public TextPropertyComponent
                                    , private Value::Listener
{
public:
    NumericTextPropertyComponent (const Value& valueToControl, const String& propertyName, int maxNumChars, bool allowDecimal_)
    : TextPropertyComponent (valueToControl, propertyName, maxNumChars, false /* multiline */)
    , allowDecimal (allowDecimal_)
    {
        textValue.referTo (valueToControl);
        textValue.addListener (this);
    }
    
    String getText() const override
    {
        return TextPropertyComponent::getText().retainCharacters (allowDecimal ? ".0123456789" : "0123456789");
    }
    
private:
    
    void valueChanged (Value&) override
    {
        // calling refresh forces TextPropertyComponent to call textEditor->setText (getText()) whenever the value changes
        refresh();
    }
    
    bool allowDecimal;
    Value textValue;
};