ComboBox text changed listener


#1

How can I listen for a text change in the ComboBox? I'm trying to create an autocompletion-like system using a ComboBox, but I can only figure out how to use something that behaves like Label::Listener::labelTextChanged() by inheriting and overriding ComboBox::labelTextChanged(). What I really want is something that acts like TextEditor::Listener::textEditorTextChanged(), meaning that I want the listener to be called when any text is entered in the ComboBox's label, not just after I press enter or something like that.

Is there any way to do this? I saw a post from a while back where someone used ComboBox for an autocompletion system, so I imagine it can somehow be done.


#2

I guess you could intercept the method that creates its TextEditor, and attach your own listener to the editor that gets returned..


#3

I'm not seeing any method that creates its TextEditor. Could you point me in some direction?


#4

Label::createEditorComponent()


#5

Sorry, I think I'm lost. I don't know how to get access to the Label in the ComboBox without changing the actual ComboBox code, which is something I want to stay away from. It might be nice if you added this feature to ComboBox. I'm sure other people need it from time to time too.


#6

Something along these lines. Using a LookAndFeel (to get at the point when the label is created) , Label::Listener and TextEditor::Listener.

This is really basic and just picks an item that is already listed when you type 3 or more characters. It doesn't autocomplete if there are two or more matches. You have to be really quick at typing to type two things that start with the same three chars! It needs some some sort of timer for a timeout, but I guess you already have the logic for that?

 

class AutoComboBox   : public Component,
                       public LookAndFeel_V2,
                       public Label::Listener,
                       public TextEditor::Listener
{
public:
    AutoComboBox()
    {
        addAndMakeVisible (box);
        box.setLookAndFeel (this);
        box.setEditableText (true);
    }
    
    Label* createComboBoxTextBox (ComboBox& /*comboBox*/) override
    {
        Label* label = new Label();
        label->addListener (this);
        return  label;
    }

    void labelTextChanged (Label* labelThatHasChanged) override
    {
        const String text (labelThatHasChanged->getText());

        if (! items.contains (text, true))
        {
            const int itemID = items.size() + 1;
            box.addItem (text, itemID);
            items.add (text);
        }
    }

    void editorShown (Label* label, TextEditor& editor) override
    {
        editor.addListener (this);
    }

    void textEditorTextChanged (TextEditor& editor) override
    {
        const String text (editor.getText());

        if (text.length() > 2)
        {
            int foundIndex = -1;
            
            for (int i = 0; i < items.size(); ++i)
            {
                if (items.getReference (i).startsWithIgnoreCase (text))
                {
                    if (foundIndex >= 0)
                    {
                        foundIndex = -1; // two matches, don't use either!
                        break;
                    }
                    
                    foundIndex = i;
                }
            }
            
            if (foundIndex >= 0)
            {
                const int itemID = foundIndex + 1;
                box.setSelectedId (itemID);
            }
        }
    }

    void resized() override
    {
        box.setBounds (getLocalBounds());
    }

private:
    ComboBox box;
    StringArray items;

    //==========================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoComboBox)
};

#7

...or I guess you mean something more like this that presents a list of choices. This time a list is popped up when you type two or more chars. You need to add your own listener class for this.

class AutoComboBox   : public Component,
                       public LookAndFeel_V2,
                       public Label::Listener,
                       public TextEditor::Listener
{
public:
    AutoComboBox()
    {
        addAndMakeVisible (box);
        box.setLookAndFeel (this);
        box.setEditableText (true);
        
        const char* itemStrings[] = { "Martin", "Mark", "Mary", "Marian", "John", "Jonathan", "Joan", "Joanne", nullptr };
        items.addArray (StringArray (itemStrings));
    }
    
    Label* createComboBoxTextBox (ComboBox& /*comboBox*/) override
    {
        Label* label = new Label();
        label->addListener (this);
        return  label;
    }

    void labelTextChanged (Label* /*labelThatHasChanged*/) override
    {
    }

    void editorShown (Label* label, TextEditor& editor) override
    {
        editor.addListener (this);
    }

    void textEditorTextChanged (TextEditor& editor) override
    {
        const String text (editor.getText());

        if (text.length() > 1)
        {
            box.clear();
            
            for (int i = 0; i < items.size(); ++i)
            {
                const String item (items[i]);
                
                if (item.startsWithIgnoreCase (text))
                    box.addItem (item, i + 1);
            }
        
            if (box.getNumItems() > 0)
                box.showPopup();
        }
    }

    void resized() override
    {
        box.setBounds (getLocalBounds());
    }

private:
    ComboBox box;
    StringArray items;

    //==========================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoComboBox)
};


#8

Thanks man. That seems like it just might work. I'll have to try it out when I get back on the computer and let you know how it worked. 

The functionality I'm looking for is something like (or almost exactly like) Sublime Text's Command Palette and file searcher. I think that's like the best feature ever. So my idea is to have a text editor of some sort and as you type in, it will perform fuzzy string searching on an array of either commands or files in a directory. Then, if there are any matches that fit the criteria, it will populate some drop down menu with the results. For the commands, the ApplicationCommandManager seems like a perfect fit to integrate this with.

I actually don't even want to use a ComboBox in the end, but I can't get anything else working and I'll be okay with at least using it for testing/experimentation. I would actually rather just use a TextEditor (or Label) in conjunction with a PopupMenu so I can use custom Components for the items in the PopupMenu. Specifically, I want to use something like AttributedString so I can draw the matching characters in different colors (like Sublime).

The problem I'm having with that is that when I do get a match and the PopupMenu gets populated with some items, my text editor loses focus and I can't for the life of me figure out a way around that. I'm afraid that might also happen with the ComboBox, but I'll just have to wait and see. 

I also want to create some autocompletion system for a text editor in the future, so I really need to find out how I can prevent this focus problem. I'm not too worried about the autocompletion system right now because I plan on this text editor to be a very long term project, but the command/file searcher thing is something I think is widely applicable and I would like to figure it out as soon as possible.

Anyways, again, thanks for your reply and hopefully what you posted can at least help me get something working so I can easily test it. By the way, I do have a pretty decent fuzzy string searching system going already using this small header only library called Simstring. I've made couple of fuzzy string searching tools so far that work okay, but Simstring seems to be the best so far.


#9

Wow what do you know, I have the same exact problem I had when using just a TextEditor and a PopupMenu: I lose focus as soon as I populate the ComboBox. I have no idea how I can do this. As far as I can tell, there's no way of making a PopupMenu lose focus. I've read several posts on here with people having the same problem, but it doesn't look like any solution was given. Anyways, your code above did work when it came to the listener thing, so thanks for that. Unfortunately, it seems like it's just not going to be useful.

Do you have any suggestions on keeping the focus on the text editor as a PopupMenu pops up Jules, or anyone else? I've tried so many things and I just can't find a solution. 


#10

Thanks for this solution. In my case I needed to react to the enter key being pressed (even if no change to the text was done)

But, wow, what a convoluted way to get at this signal! If only I could simply get the Label of the ComboBox and attach myself as a listener things could be much simpler in my code. Now, I have to jump back and forth between the LookAndFeel and my ProcessorEditor in my audio plugin to react to the enter key in my ComboBox…