ComboBox class extensibility


#1

Hi,

ComboBox class is very useful to expose a user choice from a list of values in a small GUI space.
But sometime there is a lot of choices and classical popup menu is not very user friendly.
(When PopupMenu is bigger than screen for instance)

To resolve this issue, it can be interesting to allow ComboBox’s menu customization.
The actual ComboBox interface do not permit it, but with the following modification it’s working nicely:

First move the declaration of private struct ItemInfo in a protected block and add to ComboBox a new virtual method with the following prototype:

Then modify ComboBox::showPopup method to call the default createPopupMenu implementation:

[code]void ComboBox::showPopup()
{
if (! menuActive)
{
const int selectedId = getSelectedId();

    PopupMenu menu;
    menu.setLookAndFeel (&getLookAndFeel());

    if (items.size() == 0)
        menu.addItem (1, noChoicesMessage, false);
    else
        createPopupMenu(menu, items); //Construct the popup menu here.

    menuActive = true;

    menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
                                            .withItemThatMustBeVisible (selectedId)
                                            .withMinimumWidth (getWidth())
                                            .withMaximumNumColumns (1)
                                            .withStandardItemHeight (jlimit (12, 24, getHeight())),
                        ModalCallbackFunction::forComponent (popupMenuFinishedCallback, this));
}

}[/code]

The PopupMenu related code of showPopup methods is moved into createPopupMenu default implementation.

[code]void ComboBox::createPopupMenu(PopupMenu& menu, const OwnedArray& items)
{
const int selectedId = getSelectedId();
for (int i = 0; i < items.size(); ++i)
{
const ItemInfo* const item = items.getUnchecked(i);

    if (item->isSeparator())
        menu.addSeparator();
    else if (item->isHeading)
        menu.addSectionHeader (item->name);
    else
        menu.addItem (item->itemId, item->name,
                      item->isEnabled, item->itemId == selectedId);
}

}[/code]

Now the ComboBox child classes can extend createPopupMenu method.
For instance, they can create menu with sub menu when special prefixes or separator character are found in item->name.
This allow having smaller popup with grouped item choices.

If I’m not the only one interested in this feature, it can be great having something like that in future JUCE release.
Thanks in advance.


ComboBox with sub menus in the JUCE code
#2

I have post this subject just before Juce 2 release and TheVinn forum flooding :wink:
What an unlucky timing !

Before forget this post definitely,
let me know if my design wrong, ugly or insane…
I’ll be thankful.


#3

Yes, sorry - it’s sometimes hard to see wade through all the hundreds of Vinnie posts and see everything!

And yes, good idea in principal, although perhaps the customisation belongs in the lookandfeel class rather than involving subclasses of ComboBox?


#4

To convert ComboBox items list into a hierarchical submenus,
I was thinking that needed parameters and algorithms should be different for each instance of same ComboBox child class.

You are probably right because in my case, it’s only ComboBox child class dependent.

Thanks for your interest in this post.


#5

Thanks for showing the easy way of expanding the Combobox. 

But I will suggest whatever he has explained that to do in self created class but just extending combobox to that class.

void ComboBox::createPopupMenu(PopupMenu& menu, const OwnedArray<ItemInfo>& items)
{
    const int selectedId = getSelectedId();
    PopupMenu subMenu;
    bool hasSubMenu = false;
    int k = 0;
    for (int i = 0; i < items.size(); ++i)
    {
        const ItemInfo* const item = items.getUnchecked(i);
        if (!item->isSeparator())
        {
            if (item->isHeading)
                subMenu.addSectionHeader (item->name);
            else
                subMenu.addItem (item->itemId, item->name,
                item->isEnabled, item->itemId == selectedId);
        }
        else
        {
            hasSubMenu = true;
            menu.addSubMenu("Group:"+String(k), subMenu);
            k++;
            subMenu.clear();
        }
    }
    if (!hasSubMenu)
    {
        menu = subMenu;
    }
}