Expanding the ComboBox class


#1

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?


#2

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).


#3

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?


#4

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.