diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 248cfd47b4..c1dbcebab9 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -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); } diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.h b/modules/juce_gui_basics/menus/juce_PopupMenu.h index 76b18a2e7a..edfcb088f4 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -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 index; + Array menus; + PopupMenu::Item *currentItem; MenuItemIterator& operator= (const MenuItemIterator&); JUCE_LEAK_DETECTOR (MenuItemIterator) diff --git a/modules/juce_gui_basics/widgets/juce_ComboBox.cpp b/modules/juce_gui_basics/widgets/juce_ComboBox.cpp index 13fc1644c9..e227e7142c 100644 --- a/modules/juce_gui_basics/widgets/juce_ComboBox.cpp +++ b/modules/juce_gui_basics/widgets/juce_ComboBox.cpp @@ -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) { diff --git a/modules/juce_gui_basics/widgets/juce_ComboBox.h b/modules/juce_gui_basics/widgets/juce_ComboBox.h index ca05da6c99..22731d5355 100644 --- a/modules/juce_gui_basics/widgets/juce_ComboBox.h +++ b/modules/juce_gui_basics/widgets/juce_ComboBox.h @@ -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 ¤tMenu; } //============================================================================== /** @@ -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 items; + PopupMenu currentMenu; Value currentId; int lastCurrentId; - bool isButtonDown, separatorPending, menuActive, scrollWheelEnabled; + bool isButtonDown, menuActive, scrollWheelEnabled; float mouseWheelAccumulator; ListenerList listeners; ScopedPointer