Expanding the ComboBox class

I am implementing a new class that acts in a similar way to ComboBox. For each item there is an additional String variable (stored in ItemInfo). Since I basically want the same function as ComboBox I will inherit it and add the code I need.

This is the header file.

class BluetoothComboBox : public ComboBox
{
public:
explicit BluetoothComboBox (const String& componentName = String());
~BluetoothComboBox(); // TODO

void addItem (const String& newItemText, const String& newItemMAC, const int newItemId);

private:
//==============================================================================
struct ItemInfo
{
ItemInfo (const String&, const String&, int itemId, bool isEnabled, bool isHeading);
bool isSeparator() const noexcept;
bool isRealItem() const noexcept;

    String name;
    String macAddress;
    int itemId;
    bool isEnabled : 1, isHeading : 1;
};
enum EditableState
{
    editableUnknown,
    labelIsNotEditable,
    labelIsEditable
};
OwnedArray<ItemInfo> items;
String name;
Value currentId;
int lastCurrentId;
bool isButtonDown, separatorPending, menuActive, scrollWheelEnabled;
float mouseWheelAccumulator;
ListenerList<Listener> listeners;
ScopedPointer<Label> label;
String textWhenNothingSelected, noChoicesMessage;
EditableState labelEditableState;
ItemInfo* getItemForId (int) const noexcept;
ItemInfo* getItemForIndex (int) const noexcept;
bool selectIfEnabled (int index);
bool nudgeSelectedItem (int delta);
void sendChange (NotificationType);
void showPopupIfNotActive();

};

And the .cpp file.

#include “BluetoothComboBox.h”

BluetoothComboBox::ItemInfo::ItemInfo (const String& nm, const String& mac, int iid, bool enabled, bool heading)
: name (nm), macAddress (mac), itemId (iid), isEnabled (enabled), isHeading (heading)
{
}

bool BluetoothComboBox::ItemInfo::isSeparator() const noexcept
{
return name.isEmpty();
}

bool BluetoothComboBox::ItemInfo::isRealItem() const noexcept
{
return ! (isHeading || name.isEmpty());
}

//==============================================================================
BluetoothComboBox::BluetoothComboBox (const String& name)
: Component (name),
lastCurrentId (0),
isButtonDown (false),
separatorPending (false),
menuActive (false),
scrollWheelEnabled (false),
mouseWheelAccumulator (0),
noChoicesMessage (TRANS("(no choices)")),
labelEditableState (editableUnknown)
{
ComboBox::setRepaintsOnMouseActivity (true);
ComboBox::lookAndFeelChanged();
currentId.addListener (this);
}

BluetoothComboBox::~BluetoothComboBox()
{
currentId.removeListener (this);
hidePopup();
label = nullptr;
}

//==============================================================================
void BluetoothComboBox::addItem (const String& newItemText, const String& newItemMAC, const int newItemId)
{
// you can’t add empty strings to the list…
jassert (newItemText.isNotEmpty());

// you can't add empty strings to the list..
jassert (newItemMAC.isNotEmpty());
// IDs must be non-zero, as zero is used to indicate a lack of selecion.
jassert (newItemId != 0);
// you shouldn't use duplicate item IDs!
jassert (getItemForId (newItemId) == nullptr);
if (newItemText.isNotEmpty() && newItemId != 0)
{
    if (separatorPending)
    {
        separatorPending = false;
        items.add (new ItemInfo (String(), String(), 0, false, false));
    }
    items.add (new ItemInfo (newItemText, newItemMAC, newItemId, true, false));
}

}

//==============================================================================
BluetoothComboBox::ItemInfo* BluetoothComboBox::getItemForId (const int itemId) const noexcept
{
if (itemId != 0)
{
for (int i = items.size(); --i >= 0;)
if (items.getUnchecked(i)->itemId == itemId)
return items.getUnchecked(i);
}

return nullptr;

}

BluetoothComboBox::ItemInfo* BluetoothComboBox::getItemForIndex (const int index) const noexcept
{
for (int n = 0, i = 0; i < items.size(); ++i)
{
ItemInfo* const item = items.getUnchecked(i);

    if (item->isRealItem())
        if (n++ == index)
            return item;
}
return nullptr;

}

bool BluetoothComboBox::selectIfEnabled (const int index)
{
if (const ItemInfo* const item = getItemForIndex (index))
{
if (item->isEnabled)
{
setSelectedItemIndex (index);
return true;
}
}

return false;

}

bool BluetoothComboBox::nudgeSelectedItem (int delta)
{
for (int i = getSelectedItemIndex() + delta; isPositiveAndBelow (i, getNumItems()); i += delta)
if (selectIfEnabled (i))
return true;

return false;

}

void BluetoothComboBox::showPopupIfNotActive()
{
if (! menuActive)
{
menuActive = true;
showPopup();
}
}

I do feel rather silly copying the code from ComboBox only to add this one additional variable to ItemInfo, but this is the best idea I have had so for. When I compile this code I however get the following compile error:

…/…/Source/BluetoothComboBox.cpp: In constructor ‘BluetoothComboBox::BluetoothComboBox(const juce::String&)’:
…/…/Source/BluetoothComboBox.cpp:31:7: error: type ‘juce::Component’ is not a direct base of ‘BluetoothComboBox’
: Component (name),

How would I go about solving this kind of error? Also, this is surely not the best way to accomplish such a simple task; do the programming wizards here have a better solution?

The

: Component (name)

in the constructor is attempting to call the Component class as the parent. What you need to do is

: ComboBox (name)

then you can also skip all the variable initialisations (as they will be performed by the ComboBox constructor).

Why does your ItemInfo struct have to be part of your ComboBox? It seems like overkill. Can you not just create one each time you add a new item to your combobox, you can retrieve the info using the index of the currently selected combobox item?

I am not trying to be original but only using the same design principles as the original ComboBox, see here.

@t0m Thank you, that seems reasonable. However if I try to retrieve Text with the method getItemText after adding some items by calll the modified addItem method, I get nothing back, just a String().

I believe that the problem is caused by the fact that I have modified ItemInfo class within the BluetoothComboBox. The parent will use the old ItemInfo struct when I call the method if I have not overridden the methods that contain every single ItemInfo (otherwise I think they will use the old ItemInfo class). This will cause a lot of code to be directly copied, rather than reused. I am not a programming expert by any stretch of the imagination but I don’t like copying code that has already been written.

Also I am not sure the way ItemInfo is used inside the ComboBox class is an ideal solution. Many other situations may arise where a user would want to tie some data to his ComboBox items and ItemInfo is not flexible enough to deal with that. In Qt this is resolved in a nice way, see here.