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)
{
const String key (keyPresses.getReference(i).getTextDescriptionWithIcons());
const String key (keyPresses.getReference (i).getTextDescriptionWithIcons());
if (shortcutKey.isNotEmpty())
shortcutKey << ", ";
@@ -230,7 +230,7 @@ public:
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)
items.add (new ItemComponent (*item, options.standardHeight, *this));
@@ -431,7 +431,7 @@ public:
for (int i = mouseSourceStates.size(); --i >= 0;)
{
mouseSourceStates.getUnchecked(i)->timerCallback();
mouseSourceStates.getUnchecked (i)->timerCallback();
if (deletionChecker == nullptr)
return;
@@ -512,7 +512,7 @@ public:
{
for (int i = mouseSourceStates.size(); --i >= 0;)
{
MouseSourceState& ms = *mouseSourceStates.getUnchecked(i);
MouseSourceState& ms = *mouseSourceStates.getUnchecked (i);
if (ms.source == source)
return ms;
}
@@ -538,7 +538,7 @@ public:
bool isAnyMouseOver() const
{
for (int i = 0; i < mouseSourceStates.size(); ++i)
if (mouseSourceStates.getUnchecked(i)->isOver())
if (mouseSourceStates.getUnchecked (i)->isOver())
return true;
return false;
@@ -767,7 +767,7 @@ public:
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
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
@@ -1185,7 +1185,7 @@ private:
int amount = 0;
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);
lastScrollTime = timeNow;
@@ -1216,7 +1216,7 @@ struct NormalComponentWrapper : public PopupMenu::CustomComponent
void resized() override
{
if (Component* const child = getChildComponent(0))
if (Component* const child = getChildComponent (0))
child->setBounds (getLocalBounds());
}
@@ -1711,7 +1711,7 @@ int PopupMenu::getNumItems() const noexcept
int num = 0;
for (int i = items.size(); --i >= 0;)
if (! items.getUnchecked(i)->isSeparator)
if (! items.getUnchecked (i)->isSeparator)
++num;
return num;
@@ -1800,21 +1800,44 @@ 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() {}
bool PopupMenu::MenuItemIterator::next()
{
if (index >= menu.items.size())
if (index.size() == 0 || menus.getLast()->items.size() == 0)
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,
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. */
~MenuItemIterator();
@@ -568,12 +573,15 @@ public:
/** Returns a reference to the description of the current item.
It is only valid to call this after next() has returned true!
*/
const Item& getItem() const noexcept;
Item& getItem() const noexcept;
private:
//==============================================================================
const PopupMenu& menu;
int index;
bool searchRecursively;
Array <int> index;
Array <const PopupMenu*> menus;
PopupMenu::Item *currentItem;
MenuItemIterator& operator= (const 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)
: Component (name),
lastCurrentId (0),
isButtonDown (false),
separatorPending (false),
menuActive (false),
scrollWheelEnabled (false),
mouseWheelAccumulator (0),
@@ -109,25 +92,19 @@ void ComboBox::addItem (const String& newItemText, const int newItemId)
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)
{
for (int i = 0; i < itemsToAdd.size(); ++i)
addItem (itemsToAdd[i], i + firstItemIdOffset);
currentMenu.addItem (i + firstItemIdOffset, itemsToAdd[i]);
}
void ComboBox::addSeparator()
{
separatorPending = (items.size() > 0);
currentMenu.addSeparator();
}
void ComboBox::addSectionHeading (const String& headingName)
@@ -137,67 +114,69 @@ void ComboBox::addSectionHeading (const String& headingName)
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)
{
if (ItemInfo* const item = getItemForId (itemId))
if (PopupMenu::Item* item = getItemForId (itemId))
item->isEnabled = shouldBeEnabled;
}
bool ComboBox::isItemEnabled (int itemId) const noexcept
{
const ItemInfo* const item = getItemForId (itemId);
const PopupMenu::Item* item = getItemForId (itemId);
return item != nullptr && item->isEnabled;
}
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
jassertfalse;
}
void ComboBox::clear (const NotificationType notification)
{
items.clear();
separatorPending = false;
currentMenu.clear();
if (! label->isEditable())
setSelectedItemIndex (-1, notification);
}
//==============================================================================
ComboBox::ItemInfo* ComboBox::getItemForId (const int itemId) const noexcept
PopupMenu::Item* ComboBox::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);
PopupMenu::MenuItemIterator iterator (currentMenu, true);
while (iterator.next())
{
PopupMenu::Item &item = iterator.getItem();
if (item.itemID == itemId)
return &item;
}
}
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)
return item;
return &item;
}
return nullptr;
@@ -206,42 +185,51 @@ ComboBox::ItemInfo* ComboBox::getItemForIndex (const int index) const noexcept
int ComboBox::getNumItems() const noexcept
{
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;
}
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();
}
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;
}
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;
++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
{
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)
{
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)
{
@@ -290,7 +278,7 @@ void ComboBox::setSelectedId (const int newItemId, const NotificationType notifi
bool ComboBox::selectIfEnabled (const int index)
{
if (const ItemInfo* const item = getItemForIndex (index))
if (const PopupMenu::Item* const item = getItemForIndex (index))
{
if (item->isEnabled)
{
@@ -325,14 +313,16 @@ String ComboBox::getText() const
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;
}
}
@@ -540,11 +530,19 @@ static void comboBoxPopupMenuFinishedCallback (int result, ComboBox* combo)
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())
.withMinimumWidth (getWidth())
.withMaximumNumColumns (1)
@@ -552,28 +550,6 @@ void ComboBox::showPopup()
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)
{


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

@@ -60,7 +60,7 @@ public:
explicit ComboBox (const String& componentName = String());
/** Destructor. */
~ComboBox();
virtual ~ComboBox();
//==============================================================================
/** 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. */
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:
//==============================================================================
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
{
editableUnknown,
@@ -434,18 +425,18 @@ private:
labelIsEditable
};
OwnedArray<ItemInfo> items;
PopupMenu currentMenu;
Value currentId;
int lastCurrentId;
bool isButtonDown, separatorPending, menuActive, scrollWheelEnabled;
bool isButtonDown, 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;
PopupMenu::Item* getItemForId (int) const noexcept;
PopupMenu::Item* getItemForIndex (int) const noexcept;
bool selectIfEnabled (int index);
bool nudgeSelectedItem (int delta);
void sendChange (NotificationType);


Loading…
Cancel
Save