Some suggestions


#1

Hi all!

First of all , I'm new to JUCE and new to these forumes. I have toyed with JUCE for a week now and I have to say - it's everything that was missing in our C++ life ! everything that we needed for modern and agile C++ development lays in these libraries and everything is cross-platform which is fantasic! GUI, networking , XML & JSON , Utilities and much more that we had to find external libraries for is simply there.

I have some suggestions for the next versions of JUCE:

1) first of all , the use of lambda functions. for example , if one want to add onClick functionlity to a button he must either inherit a button and override some function there , or inherit from Listener and do the same. in C++11 , the first thing that comes to mind is the ability to attach functionality on-the-fly with lamda functions. something like

myButton.setCacllBack( []{ ...My function here...  } );  

this can be applied on every class that the developer has to inherit and override to change some of the functionality.

2) the way to create a json string is a bit off.. first you have to create DynamicObject, then set all the properties , then construct a var variable with the DynamicObject's memory address then use JSON::toString(). this is a verbose way.. my suggestion is that JSON::parse can have  DynamicObject& argument at the first place , or var have the ability to be set with new properties

3) support of server-side HTTP requests and responses . the support is only for the client side (maybe it's not possible on all OS?)

4) in text editor - to be able to set font on specific sub-text (denoted by first and lasr indexes)  and to be able to get one the from sub-text (or array of them)


#2

1. Would be cool yes, but what if the owner of the lambda dies before the control, and a callback happens? The listener system needs to have add/remove functionality. Also, JUCE would need both anyway, since it's supposed to work with non-C++11 compliant compilers.


#3

Everything can be configured to work . here is my example of how to make it work with juce::Button::Listener

juce::Button::Listener* createListenerFromCallback(const std::function<void()>& callBack){

    class _Listener : public juce::Button::Listener{
        private:
            std::function<void()> m_CallBack;

        public:
            _Listener(const std::function<void()>& callback_):
                m_CallBack(callback_){}
            
            virtual void buttonClicked(juce::Button*){
                m_CallBack();
            }

        };

        return new _Listener(callBack);
}

 

since std::function can be created from pointer to function , lamda function or another std::function - you can send all three as valid arguments , thus making it easier to add callbacks than inherit&override

ofcourse , this was what came to mind when I wanted to add this functionality on existing JUCE code , a cleaner code can be written a different way.

 


#4

Sure, you _can_ make it work, but why bother? You're already writing vastly more code than what's needed just inheriting from the listener. Also, that system is linked to single events - many listeners transmit a number of events. And who owns the listener you just created? Because right now you're leaking the object:

class SomeObject
{
    SomeObject(juce::Button * b)
        : someButton(*b)
    {
        someButton.addListener
        (
            createListenerFromCallback([&]() { printf("event!"); })
        );
        // guess you could make some C# syntatic sugar like this:
        someButton += [&]() { printf("event2!"); };
        // but then you limit controls to only signaling one event.
    }
    ~SomeObject()
    {
        // how do we remove the listener? Otherwise, someButton
        // has a dangling pointer to us, and the listener is leaked as well.
    }
    juce::Button & someButton;
};

You will eventually need to do something like this... :

class SomeObject
{
    SomeObject(juce::Button * b)
        : someButton(*b)
    {
        // C# syntax is now not possible, since we need to store the returned
        // object to delete and de-register it later, following RAII.
        // also this code must be duplicated for EACH event youre listening to,
        // for EACH button you're listening to!
        auto callback = createListenerFromCallback([&](){ printf("event3"); });
        internalListenerList.push_back(callback);
        someButton.addListener(callback);
    }
    ~SomeObject()
    {
        for (auto & listener : internalListenerList)
        {
            someButton.removeListener(listener);
            delete listener; // eww.
        }
    }
    std::vector<juce::Button::Listener *> internalListenerList;
};

When you could've done just this... :


class SomeObject
    : public juce::Button::Listener
{
    SomeObject(juce::Button * b)
        : someButton(*b)
    {
        b.addListener(this);
    }
    void onButtonPress(juce::Button * b) override { printf("event4!"); }
    ~SomeObject()
    {
        someButton.removeListener(this);
    }
    juce::Button & someButton;
};

I agree, the concept can be a bit terse, especially if you're listening to a lot of stuff, but I still think it's the simplest, safest and best method (and quickest, creating lots of lambda objects both takes up memory and there's overhead of calling through std::function). Since it has to be compatible with non-C++11 compilers, you would need two event systems for everything, doubling a lot of code..

 

But nothing is stopping you for making something like this yourself.


#5

Glad you like the library!

I do intend to add lambdas for some callbacks as an option where compilers support it, though like Mayae says there are some drawbacks with doing this. One of the most annoying things about std::function is that it's not possible to compare two std::function objects, so it's impossible to remove one from an array, and therefore impossible to un-register one as a listener. So a Button could have an addCallback method, but not removeCallback, so you'd only be able to use it in situations where the callback is definitely going to be valid until the button itself has been deleted. There are certainly times when this would be handy, and other times where it would be less elegant than a listener class. No reason not to support both in the long-run though!


#6

Usually you would make the addCallback return an id (a token) which would be used to remove this callback

 

my 2 cents


#7

Yes, I've done it that way in the past, but it's a faff to deal with tokens!


#8

I don't get the whole register-unregister callback thing. when you asign a callback- it's added , when you asign different callback the old callback gets removes and the new one gets kick in.

this is how it's done in Java script when you asing some function on some HTML oncklick event.

this is how it's done on Java swing when you asign ActionListener object.

the only place where it's different is on .Net framwork where you can add many delegates as callback-chain , but then - who decided that it's better and should be followed on all costs?


#9

No, that seems to be the case in point. Firstly, all the languages you listed are garbage collected.. thereby eliminating the question of leaking memory.

when you asign a callback- it's added , when you asign different callback the old callback gets removes and the new one gets kick in.

What about multiple listeners from different objects, for different events?


#10

No, that seems to be the case in point. Firstly, all the languages you listed are garbage collected.. thereby eliminating the question of leaking memory.

It's up the Listener-Owner to delete it's listeners when new one is attached or the owner gets out of scope. 

 

What about multiple listeners from different objects, for different events?

I can ask the same about Java and Javascript.


#11

It's not very pretty because you have to manually add the callback ... maybe there's a way of making it neater...?


class CallbackHandle
{
    ...
};

class ListenerHolder
{
    public:
        ~ListenerHolder()
        {
            for (auto h: handles)
                h.object.remove(h.handle); 
        }
        void add(const CallbackHandle &);
    private:
        std::vector<CallbackHandle> handles;
};
class ComponentExample
{
    public:
        CallbackHandle addClickedCallback(std::function<void(void)> callback);
        void remove(const CallbackHandle & handle); 
};      
class ThingListeningThatMightGetDeleted
{
    public:
        ThingListeningThatMightGetDeleted()
        {
            holder.add(component.addClickedCallback([this]() { someAction(); })); 
        }
    private:
        ListenerHolder holder;
};