Maybe a Bug with TextEditor Filter

i am using the LengthAndCharacterRestriction class indirectly via setInputRestrictions() to filter my TextEditor.

PropertyTextEditor.cpp

/*
  ==============================================================================

    PropertyTextEditor.cpp
    Created: 19 Aug 2024 5:45:26pm
    Author:  Dirk Schiller

  ==============================================================================
*/

#include "PropertyTextEditor.h"

// JUCE Modules / juce_gui_basics / widgets / juce_TextEditor.cpp

// void TextEditor::drawContent (Graphics& g)
// ...
// if (! selection.isEmpty())
// ...
// 
// // >> DSchiller - Rounded Selection + Little X-Padding
// 
// int xPadding = 0;
// juce::Path path;
// path.addRoundedRectangle(boundingBox.toPath().getBounds().getX() - xPadding, boundingBox.toPath().getBounds().getY(), boundingBox.toPath().getBounds().getWidth() + 2 * xPadding, boundingBox.toPath().getBounds().getHeight(), 2);
// 
// g.fillPath (path, transform);
// 
// // << DSchiller - Rounded Selection
//
// // DSchiller - Original
// // g.fillPath (boundingBox.toPath(), transform);

// bool TextEditor::selectAll()
// ...
// // >> DSchiller - Change caret position
// moveCaretTo (0, false);
// moveCaretTo (getTotalNumChars(), true);
// // << DSchiller - Change caret position
//
// // DSchiller - Original
// // moveCaretTo (getTotalNumChars(), false);
// // moveCaretTo (0, true);

PropertyTextEditor::PropertyTextEditor(const juce::String& name) {
    setBounds(0, 0, 104, 31);
    auto typeface = juce::Typeface::createSystemTypefaceFor(Assets::RobotoMonoRegular_ttf, Assets::RobotoMonoRegular_ttfSize);
    juce::FontOptions options(typeface);
    juce::Font font(options);
    font.setHeight(21.f);
    setFont(font);
    setColour(textColourId, juce::Colour(0xfffff95e));
    applyFontToAllText(font);
    setColour(highlightColourId, juce::Colour(0xfffff95e));
    setColour(highlightedTextColourId, juce::Colour(0xff111111));
    setJustification(juce::Justification::topRight);
    setCaretVisible(true);
    int caretColourId = 0x1000204;
    setColour(caretColourId, juce::Colour(0xff0084ff));
    setInputRestrictions(8, "0123456789");
}

void PropertyTextEditor::mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel)
{
    giveAwayKeyboardFocus();
    auto currentText = getText();
    int currentValue = currentText.getIntValue();
    float scrollAmount = wheel.deltaY;
    int increment = 1;
        if (event.mods.isCtrlDown())
        {
            increment = 100;
        }
        else if (event.mods.isShiftDown())
        {
            increment = 10;
        }
    if (scrollAmount > 0)
    {
        currentValue -= increment * fmax(abs(scrollAmount) * 10, 1);
    }
    else if (scrollAmount < 0)
    {
        currentValue += increment * fmax(abs(scrollAmount) * 10, 1);
    }
    if (currentValue < 0) {
        currentValue = 0;
    }
    setText(juce::String(currentValue), juce::dontSendNotification);
}

void PropertyTextEditor::paint(juce::Graphics& g) {
    g.setColour(juce::Colour(0xff444444));
    g.fillRoundedRectangle(0, 0, getWidth(), getHeight(), 5);
}

void PropertyTextEditor::paintOverChildren(juce::Graphics& g) { }

PropertyTextEditor.h

/*
  ==============================================================================

    PropertyTextEditor.h
    Created: 19 Aug 2024 5:45:26pm
    Author:  Dirk Schiller

  ==============================================================================
*/

#pragma once

#include <JuceHeader.h>

class PropertyTextEditor : public juce::TextEditor
{
public:
    PropertyTextEditor(const juce::String& name);
    ~PropertyTextEditor() override = default;
    
    void mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) override;
    void paint(juce::Graphics& g) override;
    void paintOverChildren(juce::Graphics& g) override;
    
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PropertyTextEditor)
};

When i enter text and select it being the caret at the left position of the text and i press any of the filtered keys eg. Spacebar then a) the text get’s removed and b) the caret doesn’t go back to the correct position.

i expect that when any of the filtered, not allowed keys is pressed, the selected text should not be removed.

Also when i start using setInputRestrictions i get a leak detection error when i quit the application:

*** Leaked objects detected: 1 instance(s) of class LengthAndCharacterRestriction
JUCE Assertion failure in juce_LeakedObjectDetector.h:104

Also when i reset the filter i still get leaks on quit:

PropertyTextEditor::~PropertyTextEditor() {
    // Clear input restrictions to enforce cleanup
    setInputRestrictions(0, {});
}

About the leak:

I was curious and looked throught the sources, the setInputFilter propagates the takeOwnership = true to the OptionalScopedPointer inputFilter.

When you see the leak, do you hit stop debugging or continue?
Chances are that you are leaking the TextEditor, which will also leak the contained InputFilter.

Please try to hit continue until the program exits normally and check all leaks that are reported.

Hey Daniel, i see the leak when i quit my app via Application / Quit Application and then i see this:

i restored all my codes previous state and see another leak for EditorAccessibilityHandler which i even do not use at all. It seems also to be a class related to the TextEditor: JUCE: AccessibilityHandler Class Reference

Understood. And now can you click on “Continue”, “Resume” or whatever it is called in your IDE?

The rationale is, the LeakDetector will highlight classes that are leaked. But it pauses execution on every class. If a class that contains a TextEditor that contains a LengthAndCharacterRestriction instance, then you won’t see that when you hit “Stop” on the first leak.
And it goes from inside the scope to the outer, so the one to tackle first is the last reported leak.

i see. Thank’s! i hit continue in Xcode until the app stops running and get this leaks - a lot:

*** Leaked objects detected: 1 instance(s) of class EditorAccessibilityHandler
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class TextEditorTextInterface
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class LengthAndCharacterRestriction
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class CoreTextTypeface
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class Typeface
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class MemoryBlock
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class PropertyTextEditor
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 2 instance(s) of class ScrollBar
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class Viewport
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class DragToScrollListener
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 2 instance(s) of class AnimatedPosition
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 2 instance(s) of class SharedCursorHandle
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class TextEditor
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class OwnedArray
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class Font
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class UndoManager
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 2 instance(s) of class OwnedArray
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class SimpleValueSource
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class ValueSource
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class AccessibilityHandler
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class AccessibilityNativeImpl
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 7 instance(s) of class Component
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 7 instance(s) of class MouseCursor
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 2 instance(s) of class Image
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class MouseInputSource
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 4 instance(s) of class AsyncUpdater
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 6 instance(s) of class Timer
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 6 instance(s) of class SharedResourcePointer
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class TimerThread
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 1 instance(s) of class Thread
JUCE Assertion failure in juce_LeakedObjectDetector.h:104
*** Leaked objects detected: 3 instance(s) of class WaitableEvent
JUCE Assertion failure in juce_LeakedObjectDetector.h:104

Not sure what to do with those leaks as it seems all internal classes except the PropertyTextEditor. Is there a way to find out what is the one leaking instance in PropertyTextEditor ?

Ok wow, that is a lot. It seems something very central to your app is leaked…

Make sure to add the

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyClass)

to all your classes. That will make sure a leaked instance of your own class will show up in that list.

Eventually all of your leaks should be gone, so you have to go one my one.
I would do a full text search for new (case sensitive and word only) and double check every object, if it is assigned to an owning pointer (unique_ptr) or if not, if it is destroyed properly. Best to make a habit to use unique_ptr as much as possible.

Indeed in one of my classes i forgot the detector macro:

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NodeManager)

Adding this line does not fix the issue of leaks and produces the same list of leaks.

i also changed all new declarations to juce::OwnedArray’s and std::unique_ptr’s and std::vector<std::unique_ptr<class>> but still getting the leaks.

Am i right that juce::OwnedArray’s also okay to use regarding leaks ?

Anyhow thank you a lot for the help and pointing towards using unique pointers and owned Juce classes.

Interestingly when i do not use the TextEditor at all, all the leaks disappear also no matter if i use new <anyClass> or std::unique_ptr<class> for other objects / classes.

When i minimize the TextEditor code to the bare minimum i still get all the leak errrors:

PropertyTextEditor.h

#pragma once

#include <JuceHeader.h>

class PropertyTextEditor : public juce::TextEditor
{
public:
    PropertyTextEditor(const juce::String& name);
    ~PropertyTextEditor() override = default;
    
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PropertyTextEditor)
};

PropertyTextEditor.cpp

#include "PropertyTextEditor.h"

PropertyTextEditor::PropertyTextEditor(const juce::String& name) : juce::TextEditor(name) {
    setBounds(0, 0, 104, 31);
}

Finally, of course need to make the TextEditor a std::unique_ptr as well.

NodeBase.h

from:

PropertyTextEditor* intervalProperty;

to:

std::unique_ptr<PropertyTextEditor> intervalProperty;

NodeBase.cpp

from:

...
    intervalProperty = new PropertyTextEditor("Hello");
    intervalProperty->setBounds(162, 90, intervalProperty->getWidth(), intervalProperty->getHeight());
    addAndMakeVisible(intervalProperty);
...

to:

...
    intervalProperty = std::make_unique<PropertyTextEditor>("Hello");
    intervalProperty->setBounds(162, 90, intervalProperty->getWidth(), intervalProperty->getHeight());
    addAndMakeVisible(intervalProperty.get());
...

That makes all leak warnings disappear.

But this does not fix the wrong caret position for the TextEditor when pressing a filtered key.

Thank you for your help Daniel :folded_hands:

Hooray, glad you found it.

Now somebody can attend to the UX issues you mentioned.