Browse Source

Simplification/refactoring of the PopupMenu class by adding a subclass PopupMenu::Item which holds all the info about an item. You can now use this for more control over adding items and the PopupMenu::MenuItemIterator uses it to return info about existing items.

tags/2021-05-28
jules 9 years ago
parent
commit
e5fd6d6e01
3 changed files with 336 additions and 317 deletions
  1. +259
    -283
      modules/juce_gui_basics/menus/juce_PopupMenu.cpp
  2. +61
    -19
      modules/juce_gui_basics/menus/juce_PopupMenu.h
  3. +16
    -15
      modules/juce_gui_basics/native/juce_mac_MainMenu.mm

+ 259
- 283
modules/juce_gui_basics/menus/juce_PopupMenu.cpp View File

@@ -22,120 +22,68 @@
==============================================================================
*/
//==============================================================================
namespace PopupMenuSettings
{
const int scrollZone = 24;
const int borderSize = 2;
const int timerInterval = 50;
const int dismissCommandId = 0x6287345f;
const int sectionHeaderID = 0x4734a34f;
static bool menuWasHiddenBecauseOfAppChange = false;
}
class PopupMenu::Item
//==============================================================================
struct PopupMenu::HelperClasses
{
public:
Item() : itemID (0), isActive (true), isSeparator (true), isTicked (false),
usesColour (false), commandManager (nullptr)
{}
Item (const int itemId,
const String& name,
const bool active,
const bool ticked,
Drawable* drawable,
const Colour colour,
const bool useColour,
CustomComponent* const custom,
const PopupMenu* const sub,
ApplicationCommandManager* const manager)
: itemID (itemId), text (name), textColour (colour),
isActive (active), isSeparator (false), isTicked (ticked),
usesColour (useColour), iconDrawable (drawable),
customComp (custom), subMenu (createCopyIfNotNull (sub)), commandManager (manager)
{
if (commandManager != nullptr && itemID != 0)
{
String shortcutKey;
const Array<KeyPress> keyPresses (commandManager->getKeyMappings()
->getKeyPressesAssignedToCommand (itemID));
for (int i = 0; i < keyPresses.size(); ++i)
{
const String key (keyPresses.getReference(i).getTextDescriptionWithIcons());
if (shortcutKey.isNotEmpty())
shortcutKey << ", ";
if (key.length() == 1 && key[0] < 128)
shortcutKey << "shortcut: '" << key << '\'';
else
shortcutKey << key;
}
class MouseSourceState;
class MenuWindow;
shortcutKey = shortcutKey.trim();
static bool canBeTriggered (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.itemID != 0 && ! item.isSectionHeader; }
static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.subMenu != nullptr && item.subMenu->items.size() > 0; }
static const Colour* getColour (const PopupMenu::Item& item) noexcept { return item.colour != Colour (0x00000000) ? &item.colour : nullptr; }
static bool hasSubMenu (const PopupMenu::Item& item) noexcept { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); }
if (shortcutKey.isNotEmpty())
text << "<end>" << shortcutKey;
}
//==============================================================================
struct HeaderItemComponent : public PopupMenu::CustomComponent
{
HeaderItemComponent (const String& name) : PopupMenu::CustomComponent (false)
{
setName (name);
}
Item (const Item& other)
: itemID (other.itemID),
text (other.text),
textColour (other.textColour),
isActive (other.isActive),
isSeparator (other.isSeparator),
isTicked (other.isTicked),
usesColour (other.usesColour),
iconDrawable (other.iconDrawable != nullptr ? other.iconDrawable->createCopy() : nullptr),
customComp (other.customComp),
subMenu (createCopyIfNotNull (other.subMenu.get())),
commandManager (other.commandManager)
{}
bool canBeTriggered() const noexcept { return isActive && itemID != 0 && itemID != PopupMenuSettings::sectionHeaderID; }
bool hasActiveSubMenu() const noexcept { return isActive && subMenu != nullptr && subMenu->items.size() > 0; }
//==============================================================================
const int itemID;
String text;
const Colour textColour;
const bool isActive, isSeparator, isTicked, usesColour;
ScopedPointer<Drawable> iconDrawable;
ReferenceCountedObjectPtr<CustomComponent> customComp;
ScopedPointer<PopupMenu> subMenu;
ApplicationCommandManager* const commandManager;
void paint (Graphics& g) override
{
getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName());
}
private:
Item& operator= (const Item&);
void getIdealSize (int& idealWidth, int& idealHeight) override
{
getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight);
idealHeight += idealHeight / 2;
idealWidth += idealWidth / 4;
}
JUCE_LEAK_DETECTOR (Item)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderItemComponent)
};
//==============================================================================
struct PopupMenu::HelperClasses
struct ItemComponent : public Component
{
class MouseSourceState;
class MenuWindow;
//==============================================================================
class ItemComponent : public Component
{
public:
ItemComponent (const PopupMenu::Item& info, int standardItemHeight, MenuWindow& parent)
: itemInfo (info),
ItemComponent (const PopupMenu::Item& i, int standardItemHeight, MenuWindow& parent)
: item (i),
customComp (i.customComponent),
isHighlighted (false)
{
addAndMakeVisible (itemInfo.customComp);
if (item.isSectionHeader)
customComp = new HeaderItemComponent (item.text);
addAndMakeVisible (customComp);
parent.addAndMakeVisible (this);
shortcutKeyDescription = getShortcutKeyDescription();
int itemW = 80;
int itemH = 16;
getIdealSize (itemW, itemH, standardItemHeight);
@@ -146,45 +94,33 @@ public:
~ItemComponent()
{
removeChildComponent (itemInfo.customComp);
removeChildComponent (customComp);
}
void getIdealSize (int& idealWidth, int& idealHeight, const int standardItemHeight)
{
if (itemInfo.customComp != nullptr)
itemInfo.customComp->getIdealSize (idealWidth, idealHeight);
if (customComp != nullptr)
customComp->getIdealSize (idealWidth, idealHeight);
else
getLookAndFeel().getIdealPopupMenuItemSize (itemInfo.text,
itemInfo.isSeparator,
getLookAndFeel().getIdealPopupMenuItemSize (getTextForMeasurement(),
item.isSeparator,
standardItemHeight,
idealWidth, idealHeight);
}
void paint (Graphics& g) override
{
if (itemInfo.customComp == nullptr)
{
String mainText (itemInfo.text);
String endText;
const int endIndex = mainText.indexOf ("<end>");
if (endIndex >= 0)
{
endText = mainText.substring (endIndex + 5).trim();
mainText = mainText.substring (0, endIndex);
}
getLookAndFeel()
.drawPopupMenuItem (g, getLocalBounds(),
itemInfo.isSeparator,
itemInfo.isActive,
isHighlighted,
itemInfo.isTicked,
itemInfo.subMenu != nullptr && (itemInfo.itemID == 0 || itemInfo.subMenu->getNumItems() > 0),
mainText, endText,
itemInfo.iconDrawable,
itemInfo.usesColour ? &(itemInfo.textColour) : nullptr);
}
if (customComp == nullptr)
getLookAndFeel().drawPopupMenuItem (g, getLocalBounds(),
item.isSeparator,
item.isEnabled,
isHighlighted,
item.isTicked,
hasSubMenu (item),
item.text,
shortcutKeyDescription,
item.image,
getColour (item));
}
void resized() override
@@ -195,24 +131,60 @@ public:
void setHighlighted (bool shouldBeHighlighted)
{
shouldBeHighlighted = shouldBeHighlighted && itemInfo.isActive;
shouldBeHighlighted = shouldBeHighlighted && item.isEnabled;
if (isHighlighted != shouldBeHighlighted)
{
isHighlighted = shouldBeHighlighted;
if (itemInfo.customComp != nullptr)
itemInfo.customComp->setHighlighted (shouldBeHighlighted);
if (customComp != nullptr)
customComp->setHighlighted (shouldBeHighlighted);
repaint();
}
}
PopupMenu::Item itemInfo;
PopupMenu::Item item;
String shortcutKeyDescription;
private:
// NB: we use a copy of the one from the item info in case we're using our own section comp
ReferenceCountedObjectPtr<CustomComponent> customComp;
bool isHighlighted;
String getShortcutKeyDescription() const
{
if (item.commandManager != nullptr && item.itemID != 0)
{
String shortcutKey;
const Array<KeyPress> keyPresses (item.commandManager->getKeyMappings()
->getKeyPressesAssignedToCommand (item.itemID));
for (int i = 0; i < keyPresses.size(); ++i)
{
const String key (keyPresses.getReference(i).getTextDescriptionWithIcons());
if (shortcutKey.isNotEmpty())
shortcutKey << ", ";
if (key.length() == 1 && key[0] < 128)
shortcutKey << "shortcut: '" << key << '\'';
else
shortcutKey << key;
}
return shortcutKey.trim();
}
return String();
}
String getTextForMeasurement() const
{
return shortcutKeyDescription.isNotEmpty() ? item.text + " " + shortcutKeyDescription
: item.text;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
};
@@ -744,43 +716,43 @@ public:
for (int i = items.size(); --i >= 0;)
{
ItemComponent* const m = items.getUnchecked(i);
if (m != nullptr
&& m->itemInfo.itemID == itemID
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
if (ItemComponent* const m = items.getUnchecked(i))
{
const int currentY = m->getY();
if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight())
if (m->item.itemID == itemID
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
{
if (wantedY < 0)
wantedY = jlimit (PopupMenuSettings::scrollZone,
jmax (PopupMenuSettings::scrollZone,
windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())),
currentY);
const int currentY = m->getY();
const Rectangle<int> mon (Desktop::getInstance().getDisplays()
.getDisplayContaining (windowPos.getPosition()).userArea);
if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight())
{
if (wantedY < 0)
wantedY = jlimit (PopupMenuSettings::scrollZone,
jmax (PopupMenuSettings::scrollZone,
windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())),
currentY);
int deltaY = wantedY - currentY;
const Rectangle<int> mon (Desktop::getInstance().getDisplays()
.getDisplayContaining (windowPos.getPosition()).userArea);
windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()),
jmin (windowPos.getHeight(), mon.getHeight()));
int deltaY = wantedY - currentY;
const int newY = jlimit (mon.getY(),
mon.getBottom() - windowPos.getHeight(),
windowPos.getY() + deltaY);
windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()),
jmin (windowPos.getHeight(), mon.getHeight()));
deltaY -= newY - windowPos.getY();
const int newY = jlimit (mon.getY(),
mon.getBottom() - windowPos.getHeight(),
windowPos.getY() + deltaY);
childYOffset -= deltaY;
windowPos.setPosition (windowPos.getX(), newY);
deltaY -= newY - windowPos.getY();
updateYPositions();
}
childYOffset -= deltaY;
windowPos.setPosition (windowPos.getX(), newY);
break;
updateYPositions();
}
break;
}
}
}
}
@@ -877,9 +849,9 @@ public:
activeSubMenu = nullptr;
if (childComp != nullptr
&& childComp->itemInfo.hasActiveSubMenu())
&& hasActiveSubMenu (childComp->item))
{
activeSubMenu = new HelperClasses::MenuWindow (*(childComp->itemInfo.subMenu), this,
activeSubMenu = new HelperClasses::MenuWindow (*(childComp->item.subMenu), this,
options.withTargetScreenArea (childComp->getScreenBounds())
.withMinimumWidth (0)
.withTargetComponent (nullptr),
@@ -897,11 +869,11 @@ public:
void triggerCurrentlyHighlightedItem()
{
if (currentChild != nullptr
&& currentChild->itemInfo.canBeTriggered()
&& (currentChild->itemInfo.customComp == nullptr
|| currentChild->itemInfo.customComp->isTriggeredAutomatically()))
&& canBeTriggered (currentChild->item)
&& (currentChild->item.customComponent == nullptr
|| currentChild->item.customComponent->isTriggeredAutomatically()))
{
dismissMenu (&currentChild->itemInfo);
dismissMenu (&currentChild->item);
}
}
@@ -917,7 +889,7 @@ public:
if (ItemComponent* mic = items.getUnchecked ((start + items.size()) % items.size()))
{
if (mic->itemInfo.canBeTriggered() || mic->itemInfo.hasActiveSubMenu())
if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item))
{
setCurrentlyHighlightedChild (mic);
break;
@@ -973,7 +945,7 @@ public:
if (! window.windowIsStillValid())
return;
startTimer (PopupMenuSettings::timerInterval);
startTimerHz (20);
handleMousePosition (e.getScreenPosition());
}
@@ -1175,9 +1147,8 @@ private:
};
//==============================================================================
class NormalComponentWrapper : public PopupMenu::CustomComponent
struct NormalComponentWrapper : public PopupMenu::CustomComponent
{
public:
NormalComponentWrapper (Component* const comp, const int w, const int h,
const bool triggerMenuItemAutomaticallyWhenClicked)
: PopupMenu::CustomComponent (triggerMenuItemAutomaticallyWhenClicked),
@@ -1198,38 +1169,11 @@ public:
child->setBounds (getLocalBounds());
}
private:
const int width, height;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NormalComponentWrapper)
};
//==============================================================================
class HeaderItemComponent : public PopupMenu::CustomComponent
{
public:
HeaderItemComponent (const String& name)
: PopupMenu::CustomComponent (false)
{
setName (name);
}
void paint (Graphics& g) override
{
getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName());
}
void getIdealSize (int& idealWidth, int& idealHeight) override
{
getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight);
idealHeight += idealHeight / 2;
idealWidth += idealWidth / 4;
}
private:
JUCE_LEAK_DETECTOR (HeaderItemComponent)
};
};
//==============================================================================
@@ -1282,14 +1226,68 @@ void PopupMenu::clear()
items.clear();
}
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked)
//==============================================================================
PopupMenu::Item::Item() noexcept
: itemID (0),
commandManager (nullptr),
colour (0x00000000),
isEnabled (true),
isTicked (false),
isSeparator (false),
isSectionHeader (false)
{
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user
// didn't pick anything, so you shouldn't use it as the id
// for an item..
}
items.add (new Item (itemResultID, itemText, isActive, isTicked, nullptr,
Colours::black, false, nullptr, nullptr, nullptr));
PopupMenu::Item::Item (const Item& other)
: text (other.text),
itemID (other.itemID),
subMenu (createCopyIfNotNull (other.subMenu.get())),
image (other.image != nullptr ? other.image->createCopy() : nullptr),
customComponent (other.customComponent),
commandManager (other.commandManager),
colour (other.colour),
isEnabled (other.isEnabled),
isTicked (other.isTicked),
isSeparator (other.isSeparator),
isSectionHeader (other.isSectionHeader)
{
}
PopupMenu::Item& PopupMenu::Item::operator= (const Item& other)
{
text = other.text;
itemID = other.itemID;
subMenu = createCopyIfNotNull (other.subMenu.get());
image = (other.image != nullptr ? other.image->createCopy() : nullptr);
customComponent = other.customComponent;
commandManager = other.commandManager;
colour = other.colour;
isEnabled = other.isEnabled;
isTicked = other.isTicked;
isSeparator = other.isSeparator;
isSectionHeader = other.isSectionHeader;
return *this;
}
void PopupMenu::addItem (const Item& newItem)
{
// An ID of 0 is used as a return value to indicate that the user
// didn't pick anything, so you shouldn't use it as the ID for an item..
jassert (newItem.itemID != 0
|| newItem.isSeparator || newItem.isSectionHeader
|| newItem.subMenu != nullptr);
items.add (new Item (newItem));
}
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked)
{
Item i;
i.text = itemText;
i.itemID = itemResultID;
i.isEnabled = isActive;
i.isTicked = isTicked;
addItem (i);
}
static Drawable* createDrawableFromImage (const Image& im)
@@ -1306,23 +1304,18 @@ static Drawable* createDrawableFromImage (const Image& im)
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, const Image& iconToUse)
{
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user
// didn't pick anything, so you shouldn't use it as the id
// for an item..
items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse),
Colours::black, false, nullptr, nullptr, nullptr));
addItem (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse));
}
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, Drawable* iconToUse)
{
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user
// didn't pick anything, so you shouldn't use it as the id
// for an item..
items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse,
Colours::black, false, nullptr, nullptr, nullptr));
Item i;
i.text = itemText;
i.itemID = itemResultID;
i.isEnabled = isActive;
i.isTicked = isTicked;
i.image = iconToUse;
addItem (i);
}
void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager,
@@ -1337,53 +1330,59 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager,
ApplicationCommandInfo info (*registeredInfo);
ApplicationCommandTarget* const target = commandManager->getTargetForCommand (commandID, info);
items.add (new Item (commandID,
displayName.isNotEmpty() ? displayName
: info.shortName,
target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0,
(info.flags & ApplicationCommandInfo::isTicked) != 0,
iconToUse,
Colours::black,
false,
nullptr, nullptr,
commandManager));
Item i;
i.text = displayName.isNotEmpty() ? displayName : info.shortName;
i.itemID = (int) commandID;
i.commandManager = commandManager;
i.isEnabled = target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0;
i.isTicked = (info.flags & ApplicationCommandInfo::isTicked) != 0;
i.image = iconToUse;
addItem (i);
}
}
void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour,
bool isActive, bool isTicked, Drawable* iconToUse)
{
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user
// didn't pick anything, so you shouldn't use it as the id
// for an item..
items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse,
itemTextColour, true, nullptr, nullptr, nullptr));
Item i;
i.text = itemText;
i.itemID = itemResultID;
i.colour = itemTextColour;
i.isEnabled = isActive;
i.isTicked = isTicked;
i.image = iconToUse;
addItem (i);
}
void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour,
bool isActive, bool isTicked, const Image& iconToUse)
{
addColouredItem (itemResultID, itemText, itemTextColour, isActive, isTicked, createDrawableFromImage (iconToUse));
Item i;
i.text = itemText;
i.itemID = itemResultID;
i.colour = itemTextColour;
i.isEnabled = isActive;
i.isTicked = isTicked;
i.image = createDrawableFromImage (iconToUse);
addItem (i);
}
void PopupMenu::addCustomItem (int itemID, CustomComponent* cc, const PopupMenu* subMenu)
void PopupMenu::addCustomItem (int itemResultID, CustomComponent* cc, const PopupMenu* subMenu)
{
jassert (itemID != 0); // 0 is used as a return value to indicate that the user
// didn't pick anything, so you shouldn't use it as the id
// for an item..
items.add (new Item (itemID, String::empty, true, false, nullptr,
Colours::black, false, cc, subMenu, nullptr));
Item i;
i.itemID = itemResultID;
i.customComponent = cc;
i.subMenu = createCopyIfNotNull (subMenu);
addItem (i);
}
void PopupMenu::addCustomItem (int itemResultID, Component* customComponent, int idealWidth, int idealHeight,
bool triggerMenuItemAutomaticallyWhenClicked, const PopupMenu* subMenu)
{
items.add (new Item (itemResultID, String::empty, true, false, nullptr, Colours::black, false,
new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight,
triggerMenuItemAutomaticallyWhenClicked),
subMenu, nullptr));
addCustomItem (itemResultID,
new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight,
triggerMenuItemAutomaticallyWhenClicked),
subMenu);
}
void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive)
@@ -1400,19 +1399,32 @@ void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu,
void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive,
Drawable* iconToUse, bool isTicked, int itemResultID)
{
items.add (new Item (itemResultID, subMenuName, isActive && (itemResultID != 0 || subMenu.getNumItems() > 0), isTicked,
iconToUse, Colours::black, false, nullptr, &subMenu, nullptr));
Item i;
i.text = subMenuName;
i.itemID = itemResultID;
i.subMenu = new PopupMenu (subMenu);
i.isEnabled = isActive && (itemResultID != 0 || subMenu.getNumItems() > 0);
i.isTicked = isTicked;
i.image = iconToUse;
addItem (i);
}
void PopupMenu::addSeparator()
{
if (items.size() > 0 && ! items.getLast()->isSeparator)
items.add (new Item());
{
Item i;
i.isSeparator = true;
addItem (i);
}
}
void PopupMenu::addSectionHeader (const String& title)
{
addCustomItem (PopupMenuSettings::sectionHeaderID, new HelperClasses::HeaderItemComponent (title));
Item i;
i.text = title;
i.isSectionHeader = true;
addItem (i);
}
//==============================================================================
@@ -1486,9 +1498,8 @@ Component* PopupMenu::createWindow (const Options& options,
//==============================================================================
// This invokes any command manager commands and deletes the menu window when it is dismissed
class PopupMenuCompletionCallback : public ModalComponentManager::Callback
struct PopupMenuCompletionCallback : public ModalComponentManager::Callback
{
public:
PopupMenuCompletionCallback()
: managerOfChosenCommand (nullptr),
prevFocused (Component::getCurrentlyFocusedComponent()),
@@ -1524,7 +1535,6 @@ public:
ScopedPointer<Component> component;
WeakReference<Component> prevFocused, prevTopLevel;
private:
JUCE_DECLARE_NON_COPYABLE (PopupMenuCompletionCallback)
};
@@ -1651,10 +1661,8 @@ bool PopupMenu::containsCommandItem (const int commandID) const
const Item& mi = *items.getUnchecked (i);
if ((mi.itemID == commandID && mi.commandManager != nullptr)
|| (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID)))
{
|| (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID)))
return true;
}
}
return false;
@@ -1671,7 +1679,7 @@ bool PopupMenu::containsAnyActiveItems() const noexcept
if (mi.subMenu->containsAnyActiveItems())
return true;
}
else if (mi.isActive)
else if (mi.isEnabled)
{
return true;
}
@@ -1708,7 +1716,7 @@ void PopupMenu::CustomComponent::triggerMenuItem()
{
if (HelperClasses::MenuWindow* const pmw = mic->findParentComponentOfClass<HelperClasses::MenuWindow>())
{
pmw->dismissMenu (&mic->itemInfo);
pmw->dismissMenu (&mic->item);
}
else
{
@@ -1725,53 +1733,21 @@ void PopupMenu::CustomComponent::triggerMenuItem()
}
//==============================================================================
PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m)
: subMenu (nullptr),
itemId (0),
isSeparator (false),
isTicked (false),
isEnabled (false),
isCustomComponent (false),
isSectionHeader (false),
customColour (nullptr),
menu (m),
index (0)
{
}
PopupMenu::MenuItemIterator::~MenuItemIterator()
{
}
PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m) : menu (m), index (0) {}
PopupMenu::MenuItemIterator::~MenuItemIterator() {}
bool PopupMenu::MenuItemIterator::next()
{
if (index >= menu.items.size())
return false;
const Item* const item = menu.items.getUnchecked (index);
++index;
if (item->isSeparator && index >= menu.items.size()) // (avoid showing a separator at the end)
return false;
const Item* const item = menu.items.getUnchecked (index++);
itemName = item->customComp != nullptr ? item->customComp->getName() : item->text;
subMenu = item->subMenu;
itemId = item->itemID;
isSeparator = item->isSeparator;
isTicked = item->isTicked;
isEnabled = item->isActive;
isSectionHeader = dynamic_cast<HelperClasses::HeaderItemComponent*> (static_cast<CustomComponent*> (item->customComp)) != nullptr;
isCustomComponent = (! isSectionHeader) && item->customComp != nullptr;
customColour = item->usesColour ? &(item->textColour) : nullptr;
icon = item->iconDrawable;
commandManager = item->commandManager;
return true;
return ! (item->isSeparator && index >= menu.items.size()); // (avoid showing a separator at the end)
}
void PopupMenu::MenuItemIterator::addItemTo (PopupMenu& targetMenu)
const PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const noexcept
{
targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, icon != nullptr ? icon->createCopy() : nullptr,
customColour != nullptr ? *customColour : Colours::black,
customColour != nullptr, nullptr, subMenu, commandManager));
jassert (isPositiveAndBelow (index - 1, menu.items.size()));
return *menu.items.getUnchecked (index - 1);
}

+ 61
- 19
modules/juce_gui_basics/menus/juce_PopupMenu.h View File

@@ -104,6 +104,63 @@ public:
/** Resets the menu, removing all its items. */
void clear();
/** Describes a popup menu item. */
struct Item
{
/** Creates a null item.
You'll need to set some fields after creating an Item before you
can add it to a PopupMenu
*/
Item() noexcept;
/** Creates a copy of an item. */
Item (const Item&);
/** Creates a copy of an item. */
Item& operator= (const Item&);
/** The menu item's name. */
String text;
/** The menu item's ID. This can not be 0 if you want the item to be triggerable! */
int itemID;
/** A sub-menu, or nullptr if there isn't one. */
ScopedPointer<PopupMenu> subMenu;
/** A drawable to use as an icon, or nullptr if there isn't one. */
ScopedPointer<Drawable> image;
/** A custom component for the item to display, or nullptr if there isn't one. */
ReferenceCountedObjectPtr<CustomComponent> customComponent;
/** A command manager to use to automatically invoke the command, or nullptr if none is specified. */
ApplicationCommandManager* commandManager;
/** A colour to use to draw the menu text.
By default this is transparent black, which means that the LookAndFeel should choose the colour.
*/
Colour colour;
/** True if this menu item is enabled. */
bool isEnabled;
/** True if this menu item should have a tick mark next to it. */
bool isTicked;
/** True if this menu item is a separator line. */
bool isSeparator;
/** True if this menu item is a section header. */
bool isSectionHeader;
};
/** Adds an item to the menu.
You can call this method for full control over the item that is added, or use the other
addItem helper methods if you want to pass arguments rather than creating an Item object.
*/
void addItem (const Item& newItem);
/** Appends a new text item for this menu to show.
@param itemResultID the number that will be returned from the show() method
@@ -274,7 +331,6 @@ public:
int itemResultID = 0);
/** Appends a separator to the menu, to help break it up into sections.
The menu class is smart enough not to display separators at the top or bottom
of the menu, and it will replace mutliple adjacent separators with a single
one, so your code can be quite free and easy about adding these, and it'll
@@ -283,14 +339,12 @@ public:
void addSeparator();
/** Adds a non-clickable text item to the menu.
This is a bold-font items which can be used as a header to separate the items
into named groups.
*/
void addSectionHeader (const String& title);
/** Returns the number of items that the menu currently contains.
(This doesn't count separators).
*/
int getNumItems() const noexcept;
@@ -486,21 +540,10 @@ public:
*/
bool next();
/** Adds an item to the target menu which has all the properties of this item. */
void addItemTo (PopupMenu& targetMenu);
//==============================================================================
String itemName;
const PopupMenu* subMenu;
int itemId;
bool isSeparator;
bool isTicked;
bool isEnabled;
bool isCustomComponent;
bool isSectionHeader;
const Colour* customColour;
const Drawable* icon;
ApplicationCommandManager* commandManager;
/** 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;
private:
//==============================================================================
@@ -621,7 +664,6 @@ public:
private:
//==============================================================================
JUCE_PUBLIC_IN_DLL_BUILD (class Item)
JUCE_PUBLIC_IN_DLL_BUILD (struct HelperClasses)
friend struct HelperClasses;
friend class MenuBarComponent;


+ 16
- 15
modules/juce_gui_basics/native/juce_mac_MainMenu.mm View File

@@ -181,16 +181,17 @@ public:
void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
const int topLevelMenuId, const int topLevelIndex)
{
NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf ("<end>", false, true));
const PopupMenu::Item& i = iter.getItem();
NSString* text = juceStringToNS (i.text);
if (text == nil)
text = nsEmptyString();
if (iter.isSeparator)
if (i.isSeparator)
{
[menuToAddTo addItem: [NSMenuItem separatorItem]];
}
else if (iter.isSectionHeader)
else if (i.isSectionHeader)
{
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
action: nil
@@ -198,9 +199,9 @@ public:
[item setEnabled: false];
}
else if (iter.subMenu != nullptr)
else if (i.subMenu != nullptr)
{
if (iter.itemName == recentItemsMenuName)
if (i.text == recentItemsMenuName)
{
if (recent == nullptr)
recent = new RecentFilesMenuItem();
@@ -219,10 +220,10 @@ public:
action: nil
keyEquivalent: nsEmptyString()];
[item setTag: iter.itemId];
[item setEnabled: iter.isEnabled];
[item setTag: i.itemID];
[item setEnabled: i.isEnabled];
NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex, false);
NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false);
[menuToAddTo setSubmenu: sub forItem: item];
[sub release];
}
@@ -232,19 +233,19 @@ public:
action: @selector (menuItemInvoked:)
keyEquivalent: nsEmptyString()];
[item setTag: iter.itemId];
[item setEnabled: iter.isEnabled];
[item setState: iter.isTicked ? NSOnState : NSOffState];
[item setTag: i.itemID];
[item setEnabled: i.isEnabled];
[item setState: i.isTicked ? NSOnState : NSOffState];
[item setTarget: (id) callback];
NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) iter.commandManager]];
NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) i.commandManager]];
[info addObject: [NSNumber numberWithInt: topLevelIndex]];
[item setRepresentedObject: info];
if (iter.commandManager != nullptr)
if (i.commandManager != nullptr)
{
const Array<KeyPress> keyPresses (iter.commandManager->getKeyMappings()
->getKeyPressesAssignedToCommand (iter.itemId));
const Array<KeyPress> keyPresses (i.commandManager->getKeyMappings()
->getKeyPressesAssignedToCommand (i.itemID));
if (keyPresses.size() > 0)
{


Loading…
Cancel
Save