Browse Source

Added sub-menu support to ComboBoxes

tags/2021-05-28
hogliux 9 years ago
parent
commit
00c0671c6b
4 changed files with 132 additions and 134 deletions
  1. +39
    -16
      modules/juce_gui_basics/menus/juce_PopupMenu.cpp
  2. +12
    -4
      modules/juce_gui_basics/menus/juce_PopupMenu.h
  3. +72
    -96
      modules/juce_gui_basics/widgets/juce_ComboBox.cpp
  4. +9
    -18
      modules/juce_gui_basics/widgets/juce_ComboBox.h

+ 39
- 16
modules/juce_gui_basics/menus/juce_PopupMenu.cpp View File

@@ -162,7 +162,7 @@ private:
for (int i = 0; i < keyPresses.size(); ++i) for (int i = 0; i < keyPresses.size(); ++i)
{ {
const String key (keyPresses.getReference(i).getTextDescriptionWithIcons());
const String key (keyPresses.getReference (i).getTextDescriptionWithIcons());
if (shortcutKey.isNotEmpty()) if (shortcutKey.isNotEmpty())
shortcutKey << ", "; shortcutKey << ", ";
@@ -230,7 +230,7 @@ public:
for (int i = 0; i < menu.items.size(); ++i) for (int i = 0; i < menu.items.size(); ++i)
{ {
PopupMenu::Item* const item = menu.items.getUnchecked(i);
PopupMenu::Item* const item = menu.items.getUnchecked (i);
if (i < menu.items.size() - 1 || ! item->isSeparator) if (i < menu.items.size() - 1 || ! item->isSeparator)
items.add (new ItemComponent (*item, options.standardHeight, *this)); items.add (new ItemComponent (*item, options.standardHeight, *this));
@@ -431,7 +431,7 @@ public:
for (int i = mouseSourceStates.size(); --i >= 0;) for (int i = mouseSourceStates.size(); --i >= 0;)
{ {
mouseSourceStates.getUnchecked(i)->timerCallback();
mouseSourceStates.getUnchecked (i)->timerCallback();
if (deletionChecker == nullptr) if (deletionChecker == nullptr)
return; return;
@@ -512,7 +512,7 @@ public:
{ {
for (int i = mouseSourceStates.size(); --i >= 0;) for (int i = mouseSourceStates.size(); --i >= 0;)
{ {
MouseSourceState& ms = *mouseSourceStates.getUnchecked(i);
MouseSourceState& ms = *mouseSourceStates.getUnchecked (i);
if (ms.source == source) if (ms.source == source)
return ms; return ms;
} }
@@ -538,7 +538,7 @@ public:
bool isAnyMouseOver() const bool isAnyMouseOver() const
{ {
for (int i = 0; i < mouseSourceStates.size(); ++i) for (int i = 0; i < mouseSourceStates.size(); ++i)
if (mouseSourceStates.getUnchecked(i)->isOver())
if (mouseSourceStates.getUnchecked (i)->isOver())
return true; return true;
return false; return false;
@@ -767,7 +767,7 @@ public:
for (int i = items.size(); --i >= 0;) for (int i = items.size(); --i >= 0;)
{ {
if (ItemComponent* const m = items.getUnchecked(i))
if (ItemComponent* const m = items.getUnchecked (i))
{ {
if (m->item.itemID == itemID if (m->item.itemID == itemID
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4) && windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
@@ -1185,7 +1185,7 @@ private:
int amount = 0; int amount = 0;
for (int i = 0; i < window.items.size() && amount == 0; ++i) for (int i = 0; i < window.items.size() && amount == 0; ++i)
amount = ((int) scrollAcceleration) * window.items.getUnchecked(i)->getHeight();
amount = ((int) scrollAcceleration) * window.items.getUnchecked (i)->getHeight();
window.alterChildYPos (amount * direction); window.alterChildYPos (amount * direction);
lastScrollTime = timeNow; lastScrollTime = timeNow;
@@ -1216,7 +1216,7 @@ struct NormalComponentWrapper : public PopupMenu::CustomComponent
void resized() override void resized() override
{ {
if (Component* const child = getChildComponent(0))
if (Component* const child = getChildComponent (0))
child->setBounds (getLocalBounds()); child->setBounds (getLocalBounds());
} }
@@ -1711,7 +1711,7 @@ int PopupMenu::getNumItems() const noexcept
int num = 0; int num = 0;
for (int i = items.size(); --i >= 0;) for (int i = items.size(); --i >= 0;)
if (! items.getUnchecked(i)->isSeparator)
if (! items.getUnchecked (i)->isSeparator)
++num; ++num;
return num; return num;
@@ -1800,21 +1800,44 @@ PopupMenu::CustomCallback::CustomCallback() {}
PopupMenu::CustomCallback::~CustomCallback() {} PopupMenu::CustomCallback::~CustomCallback() {}
//============================================================================== //==============================================================================
PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m) : menu (m), index (0) {}
PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m, bool searchR) : searchRecursively (searchR)
{
currentItem = nullptr;
index.add (0);
menus.add (&m);
}
PopupMenu::MenuItemIterator::~MenuItemIterator() {} PopupMenu::MenuItemIterator::~MenuItemIterator() {}
bool PopupMenu::MenuItemIterator::next() bool PopupMenu::MenuItemIterator::next()
{ {
if (index >= menu.items.size())
if (index.size() == 0 || menus.getLast()->items.size() == 0)
return false; return false;
const Item* const item = menu.items.getUnchecked (index++);
currentItem = menus.getLast()->items.getUnchecked (index.getLast());
if (searchRecursively && currentItem->subMenu != nullptr)
{
index.add (0);
menus.add (currentItem->subMenu);
}
else
index.setUnchecked (index.size() - 1, index.getLast() + 1);
while (index.size() > 0 && index.getLast() >= menus.getLast()->items.size())
{
index.removeLast();
menus.removeLast();
if (index.size() > 0)
index.setUnchecked (index.size() - 1, index.getLast() + 1);
}
return ! (item->isSeparator && index >= menu.items.size()); // (avoid showing a separator at the end)
return true;
} }
const PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const noexcept
PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const noexcept
{ {
jassert (isPositiveAndBelow (index - 1, menu.items.size()));
return *menu.items.getUnchecked (index - 1);
jassert (currentItem != nullptr);
return *(currentItem);
} }

+ 12
- 4
modules/juce_gui_basics/menus/juce_PopupMenu.h View File

@@ -554,8 +554,13 @@ public:
Be careful not to add any items to a menu while it is being iterated, Be careful not to add any items to a menu while it is being iterated,
or things could get out of step. or things could get out of step.
@param menu the menu that needs to be scanned
@param searchRecursively if true, all submenus will be recursed into to
do an exhaustive search
*/ */
MenuItemIterator (const PopupMenu& menu);
MenuItemIterator (const PopupMenu& menu, bool searchRecursively = false);
/** Destructor. */ /** Destructor. */
~MenuItemIterator(); ~MenuItemIterator();
@@ -568,12 +573,15 @@ public:
/** Returns a reference to the description of the current item. /** Returns a reference to the description of the current item.
It is only valid to call this after next() has returned true! It is only valid to call this after next() has returned true!
*/ */
const Item& getItem() const noexcept;
Item& getItem() const noexcept;
private: private:
//============================================================================== //==============================================================================
const PopupMenu& menu;
int index;
bool searchRecursively;
Array <int> index;
Array <const PopupMenu*> menus;
PopupMenu::Item *currentItem;
MenuItemIterator& operator= (const MenuItemIterator&); MenuItemIterator& operator= (const MenuItemIterator&);
JUCE_LEAK_DETECTOR (MenuItemIterator) JUCE_LEAK_DETECTOR (MenuItemIterator)


+ 72
- 96
modules/juce_gui_basics/widgets/juce_ComboBox.cpp View File

@@ -22,27 +22,10 @@
============================================================================== ==============================================================================
*/ */
ComboBox::ItemInfo::ItemInfo (const String& nm, int iid, bool enabled, bool heading)
: name (nm), itemId (iid), isEnabled (enabled), isHeading (heading)
{
}
bool ComboBox::ItemInfo::isSeparator() const noexcept
{
return name.isEmpty();
}
bool ComboBox::ItemInfo::isRealItem() const noexcept
{
return ! (isHeading || name.isEmpty());
}
//==============================================================================
ComboBox::ComboBox (const String& name) ComboBox::ComboBox (const String& name)
: Component (name), : Component (name),
lastCurrentId (0), lastCurrentId (0),
isButtonDown (false), isButtonDown (false),
separatorPending (false),
menuActive (false), menuActive (false),
scrollWheelEnabled (false), scrollWheelEnabled (false),
mouseWheelAccumulator (0), mouseWheelAccumulator (0),
@@ -109,25 +92,19 @@ void ComboBox::addItem (const String& newItemText, const int newItemId)
if (newItemText.isNotEmpty() && newItemId != 0) if (newItemText.isNotEmpty() && newItemId != 0)
{ {
if (separatorPending)
{
separatorPending = false;
items.add (new ItemInfo (String(), 0, false, false));
}
items.add (new ItemInfo (newItemText, newItemId, true, false));
currentMenu.addItem (newItemId, newItemText, true, false);
} }
} }
void ComboBox::addItemList (const StringArray& itemsToAdd, const int firstItemIdOffset) void ComboBox::addItemList (const StringArray& itemsToAdd, const int firstItemIdOffset)
{ {
for (int i = 0; i < itemsToAdd.size(); ++i) for (int i = 0; i < itemsToAdd.size(); ++i)
addItem (itemsToAdd[i], i + firstItemIdOffset);
currentMenu.addItem (i + firstItemIdOffset, itemsToAdd[i]);
} }
void ComboBox::addSeparator() void ComboBox::addSeparator()
{ {
separatorPending = (items.size() > 0);
currentMenu.addSeparator();
} }
void ComboBox::addSectionHeading (const String& headingName) void ComboBox::addSectionHeading (const String& headingName)
@@ -137,67 +114,69 @@ void ComboBox::addSectionHeading (const String& headingName)
if (headingName.isNotEmpty()) if (headingName.isNotEmpty())
{ {
if (separatorPending)
{
separatorPending = false;
items.add (new ItemInfo (String(), 0, false, false));
}
items.add (new ItemInfo (headingName, 0, true, true));
currentMenu.addSectionHeader (headingName);
} }
} }
void ComboBox::setItemEnabled (const int itemId, const bool shouldBeEnabled) void ComboBox::setItemEnabled (const int itemId, const bool shouldBeEnabled)
{ {
if (ItemInfo* const item = getItemForId (itemId))
if (PopupMenu::Item* item = getItemForId (itemId))
item->isEnabled = shouldBeEnabled; item->isEnabled = shouldBeEnabled;
} }
bool ComboBox::isItemEnabled (int itemId) const noexcept bool ComboBox::isItemEnabled (int itemId) const noexcept
{ {
const ItemInfo* const item = getItemForId (itemId);
const PopupMenu::Item* item = getItemForId (itemId);
return item != nullptr && item->isEnabled; return item != nullptr && item->isEnabled;
} }
void ComboBox::changeItemText (const int itemId, const String& newText) void ComboBox::changeItemText (const int itemId, const String& newText)
{ {
if (ItemInfo* const item = getItemForId (itemId))
item->name = newText;
if (PopupMenu::Item* item = getItemForId (itemId))
item->text = newText;
else else
jassertfalse; jassertfalse;
} }
void ComboBox::clear (const NotificationType notification) void ComboBox::clear (const NotificationType notification)
{ {
items.clear();
separatorPending = false;
currentMenu.clear();
if (! label->isEditable()) if (! label->isEditable())
setSelectedItemIndex (-1, notification); setSelectedItemIndex (-1, notification);
} }
//============================================================================== //==============================================================================
ComboBox::ItemInfo* ComboBox::getItemForId (const int itemId) const noexcept
PopupMenu::Item* ComboBox::getItemForId (const int itemId) const noexcept
{ {
if (itemId != 0) if (itemId != 0)
{ {
for (int i = items.size(); --i >= 0;)
if (items.getUnchecked(i)->itemId == itemId)
return items.getUnchecked(i);
PopupMenu::MenuItemIterator iterator (currentMenu, true);
while (iterator.next())
{
PopupMenu::Item &item = iterator.getItem();
if (item.itemID == itemId)
return &item;
}
} }
return nullptr; return nullptr;
} }
ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const noexcept
PopupMenu::Item* ComboBox::getItemForIndex (const int index) const noexcept
{ {
for (int n = 0, i = 0; i < items.size(); ++i)
int n = 0;
PopupMenu::MenuItemIterator iterator (currentMenu, true);
while (iterator.next())
{ {
ItemInfo* const item = items.getUnchecked(i);
PopupMenu::Item &item = iterator.getItem();
if (item->isRealItem())
if (item.itemID != 0)
if (n++ == index) if (n++ == index)
return item;
return &item;
} }
return nullptr; return nullptr;
@@ -206,42 +185,51 @@ ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const noexcept
int ComboBox::getNumItems() const noexcept int ComboBox::getNumItems() const noexcept
{ {
int n = 0; int n = 0;
PopupMenu::MenuItemIterator iterator (currentMenu, true);
for (int i = items.size(); --i >= 0;)
if (items.getUnchecked(i)->isRealItem())
++n;
while (iterator.next())
{
PopupMenu::Item &item = iterator.getItem();
if (item.itemID != 0)
n++;
}
return n; return n;
} }
String ComboBox::getItemText (const int index) const String ComboBox::getItemText (const int index) const
{ {
if (const ItemInfo* const item = getItemForIndex (index))
return item->name;
if (const PopupMenu::Item* const item = getItemForIndex (index))
return item->text;
return String(); return String();
} }
int ComboBox::getItemId (const int index) const noexcept int ComboBox::getItemId (const int index) const noexcept
{ {
if (const ItemInfo* const item = getItemForIndex (index))
return item->itemId;
if (const PopupMenu::Item* const item = getItemForIndex (index))
return item->itemID;
return 0; return 0;
} }
int ComboBox::indexOfItemId (const int itemId) const noexcept int ComboBox::indexOfItemId (const int itemId) const noexcept
{ {
for (int n = 0, i = 0; i < items.size(); ++i)
if (itemId != 0)
{ {
const ItemInfo* const item = items.getUnchecked(i);
int n = 0;
PopupMenu::MenuItemIterator iterator (currentMenu, true);
if (item->isRealItem())
while (iterator.next())
{ {
if (item->itemId == itemId)
PopupMenu::Item &item = iterator.getItem();
if (item.itemID == itemId)
return n; return n;
++n;
else if (item.itemID != 0)
n++;
} }
} }
@@ -266,15 +254,15 @@ void ComboBox::setSelectedItemIndex (const int index, const NotificationType not
int ComboBox::getSelectedId() const noexcept int ComboBox::getSelectedId() const noexcept
{ {
const ItemInfo* const item = getItemForId (currentId.getValue());
const PopupMenu::Item* const item = getItemForId (currentId.getValue());
return (item != nullptr && getText() == item->name) ? item->itemId : 0;
return (item != nullptr && getText() == item->text) ? item->itemID : 0;
} }
void ComboBox::setSelectedId (const int newItemId, const NotificationType notification) void ComboBox::setSelectedId (const int newItemId, const NotificationType notification)
{ {
const ItemInfo* const item = getItemForId (newItemId);
const String newItemText (item != nullptr ? item->name : String());
const PopupMenu::Item* const item = getItemForId (newItemId);
const String newItemText (item != nullptr ? item->text : String());
if (lastCurrentId != newItemId || label->getText() != newItemText) if (lastCurrentId != newItemId || label->getText() != newItemText)
{ {
@@ -290,7 +278,7 @@ void ComboBox::setSelectedId (const int newItemId, const NotificationType notifi
bool ComboBox::selectIfEnabled (const int index) bool ComboBox::selectIfEnabled (const int index)
{ {
if (const ItemInfo* const item = getItemForIndex (index))
if (const PopupMenu::Item* const item = getItemForIndex (index))
{ {
if (item->isEnabled) if (item->isEnabled)
{ {
@@ -325,14 +313,16 @@ String ComboBox::getText() const
void ComboBox::setText (const String& newText, const NotificationType notification) void ComboBox::setText (const String& newText, const NotificationType notification)
{ {
for (int i = items.size(); --i >= 0;)
PopupMenu::MenuItemIterator iterator (currentMenu, true);
while (iterator.next())
{ {
const ItemInfo* const item = items.getUnchecked(i);
PopupMenu::Item &item = iterator.getItem();
if (item->isRealItem()
&& item->name == newText)
if (item.itemID != 0
&& item.text == newText)
{ {
setSelectedId (item->itemId, notification);
setSelectedId (item.itemID, notification);
return; return;
} }
} }
@@ -540,11 +530,19 @@ static void comboBoxPopupMenuFinishedCallback (int result, ComboBox* combo)
void ComboBox::showPopup() void ComboBox::showPopup()
{ {
PopupMenu menu;
menu.setLookAndFeel (&getLookAndFeel());
addItemsToMenu (menu);
PopupMenu::MenuItemIterator iterator (currentMenu, true);
const int selectedId = getSelectedId();
while (iterator.next())
{
PopupMenu::Item &item = iterator.getItem();
menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
if (item.itemID != 0)
item.isTicked = (item.itemID == selectedId);
}
currentMenu.setLookAndFeel(&getLookAndFeel());
currentMenu.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
.withItemThatMustBeVisible (getSelectedId()) .withItemThatMustBeVisible (getSelectedId())
.withMinimumWidth (getWidth()) .withMinimumWidth (getWidth())
.withMaximumNumColumns (1) .withMaximumNumColumns (1)
@@ -552,28 +550,6 @@ void ComboBox::showPopup()
ModalCallbackFunction::forComponent (comboBoxPopupMenuFinishedCallback, this)); ModalCallbackFunction::forComponent (comboBoxPopupMenuFinishedCallback, this));
} }
void ComboBox::addItemsToMenu (PopupMenu& menu) const
{
const int selectedId = getSelectedId();
for (int i = 0; i < items.size(); ++i)
{
const ItemInfo* const item = items.getUnchecked(i);
jassert (item != nullptr);
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);
}
if (items.size() == 0)
menu.addItem (1, noChoicesMessage, false);
}
//============================================================================== //==============================================================================
void ComboBox::mouseDown (const MouseEvent& e) void ComboBox::mouseDown (const MouseEvent& e)
{ {


+ 9
- 18
modules/juce_gui_basics/widgets/juce_ComboBox.h View File

@@ -60,7 +60,7 @@ public:
explicit ComboBox (const String& componentName = String()); explicit ComboBox (const String& componentName = String());
/** Destructor. */ /** Destructor. */
~ComboBox();
virtual ~ComboBox();
//============================================================================== //==============================================================================
/** Sets whether the text in the combo-box is editable. /** Sets whether the text in the combo-box is editable.
@@ -269,8 +269,10 @@ public:
/** Returns true if the popup menu is currently being shown. */ /** Returns true if the popup menu is currently being shown. */
bool isPopupActive() const noexcept { return menuActive; } bool isPopupActive() const noexcept { return menuActive; }
/** Adds the items in this ComboBox to the given menu. */
virtual void addItemsToMenu (PopupMenu&) const;
/** Returns the PopupMenu object associated with the ComboBox.
Can be useful for adding sub-menus to the ComboBox standard PopupMenu
*/
PopupMenu *getRootMenu() { return &currentMenu; }
//============================================================================== //==============================================================================
/** /**
@@ -416,17 +418,6 @@ public:
private: private:
//============================================================================== //==============================================================================
struct ItemInfo
{
ItemInfo (const String&, int itemId, bool isEnabled, bool isHeading);
bool isSeparator() const noexcept;
bool isRealItem() const noexcept;
String name;
int itemId;
bool isEnabled : 1, isHeading : 1;
};
enum EditableState enum EditableState
{ {
editableUnknown, editableUnknown,
@@ -434,18 +425,18 @@ private:
labelIsEditable labelIsEditable
}; };
OwnedArray<ItemInfo> items;
PopupMenu currentMenu;
Value currentId; Value currentId;
int lastCurrentId; int lastCurrentId;
bool isButtonDown, separatorPending, menuActive, scrollWheelEnabled;
bool isButtonDown, menuActive, scrollWheelEnabled;
float mouseWheelAccumulator; float mouseWheelAccumulator;
ListenerList<Listener> listeners; ListenerList<Listener> listeners;
ScopedPointer<Label> label; ScopedPointer<Label> label;
String textWhenNothingSelected, noChoicesMessage; String textWhenNothingSelected, noChoicesMessage;
EditableState labelEditableState; EditableState labelEditableState;
ItemInfo* getItemForId (int) const noexcept;
ItemInfo* getItemForIndex (int) const noexcept;
PopupMenu::Item* getItemForId (int) const noexcept;
PopupMenu::Item* getItemForIndex (int) const noexcept;
bool selectIfEnabled (int index); bool selectIfEnabled (int index);
bool nudgeSelectedItem (int delta); bool nudgeSelectedItem (int delta);
void sendChange (NotificationType); void sendChange (NotificationType);


Loading…
Cancel
Save