JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_PopupMenu.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29namespace PopupMenuSettings
30{
31 const int scrollZone = 24;
32 const int dismissCommandId = 0x6287345f;
33
34 static bool menuWasHiddenBecauseOfAppChange = false;
35}
36
37//==============================================================================
39{
40
42struct MenuWindow;
43
44static bool canBeTriggered (const PopupMenu::Item& item) noexcept
45{
46 return item.isEnabled
47 && item.itemID != 0
48 && ! item.isSectionHeader
49 && (item.customComponent == nullptr || item.customComponent->isTriggeredAutomatically());
50}
51
52static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept
53{
54 return item.isEnabled
55 && item.subMenu != nullptr
56 && item.subMenu->items.size() > 0;
57}
58
59//==============================================================================
61{
62 HeaderItemComponent (const String& name, const Options& opts)
63 : CustomComponent (false), options (opts)
64 {
65 setName (name);
66 }
67
68 void paint (Graphics& g) override
69 {
70 getLookAndFeel().drawPopupMenuSectionHeaderWithOptions (g,
72 getName(),
73 options);
74 }
75
76 void getIdealSize (int& idealWidth, int& idealHeight) override
77 {
79 false,
80 -1,
83 options);
86 }
87
92
93 const Options& options;
94
96};
97
98//==============================================================================
100{
102 : item (i), parentWindow (parent), options (o), customComp (i.customComponent)
103 {
104 if (item.isSectionHeader)
105 customComp = *new HeaderItemComponent (item.text, options);
106
107 if (customComp != nullptr)
108 {
109 setItem (*customComp, &item);
110 addAndMakeVisible (*customComp);
111 }
112
113 parent.addAndMakeVisible (this);
114
115 updateShortcutKeyDescription();
116
117 int itemW = 80;
118 int itemH = 16;
119 getIdealSize (itemW, itemH, options.getStandardItemHeight());
120 setSize (itemW, jlimit (1, 600, itemH));
121
122 addMouseListener (&parent, false);
123 }
124
125 ~ItemComponent() override
126 {
127 if (customComp != nullptr)
128 setItem (*customComp, nullptr);
129
130 removeChildComponent (customComp.get());
131 }
132
133 void getIdealSize (int& idealWidth, int& idealHeight, const int standardItemHeight)
134 {
135 if (customComp != nullptr)
136 customComp->getIdealSize (idealWidth, idealHeight);
137 else
138 getLookAndFeel().getIdealPopupMenuItemSizeWithOptions (getTextForMeasurement(),
139 item.isSeparator,
142 options);
143 }
144
145 void paint (Graphics& g) override
146 {
147 if (customComp == nullptr)
149 isHighlighted,
150 item,
151 options);
152 }
153
154 void resized() override
155 {
156 if (auto* child = getChildComponent (0))
157 {
158 const auto border = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options);
159 child->setBounds (getLocalBounds().reduced (border, 0));
160 }
161 }
162
163 void setHighlighted (bool shouldBeHighlighted)
164 {
166
167 if (isHighlighted != shouldBeHighlighted)
168 {
169 isHighlighted = shouldBeHighlighted;
170
171 if (customComp != nullptr)
172 customComp->setHighlighted (shouldBeHighlighted);
173
174 if (isHighlighted)
175 if (auto* handler = getAccessibilityHandler())
176 handler->grabFocus();
177
178 repaint();
179 }
180 }
181
182 static bool isAccessibilityHandlerRequired (const PopupMenu::Item& item)
183 {
184 return item.isSectionHeader || hasActiveSubMenu (item) || canBeTriggered (item);
185 }
186
187 PopupMenu::Item item;
188
189private:
190 //==============================================================================
191 class ItemAccessibilityHandler final : public AccessibilityHandler
192 {
193 public:
194 explicit ItemAccessibilityHandler (ItemComponent& itemComponentToWrap)
195 : AccessibilityHandler (itemComponentToWrap,
196 isAccessibilityHandlerRequired (itemComponentToWrap.item) ? AccessibilityRole::menuItem
197 : AccessibilityRole::ignored,
198 getAccessibilityActions (*this, itemComponentToWrap)),
199 itemComponent (itemComponentToWrap)
200 {
201 }
202
203 String getTitle() const override
204 {
205 return itemComponent.item.text;
206 }
207
208 AccessibleState getCurrentState() const override
209 {
212
213 if (hasActiveSubMenu (itemComponent.item))
214 {
215 state = itemComponent.parentWindow.isSubMenuVisible() ? state.withExpandable().withExpanded()
216 : state.withExpandable().withCollapsed();
217 }
218
219 if (itemComponent.item.isTicked)
220 state = state.withCheckable().withChecked();
221
222 return state.isFocused() ? state.withSelected() : state;
223 }
224
225 private:
226 static AccessibilityActions getAccessibilityActions (ItemAccessibilityHandler& handler,
227 ItemComponent& item)
228 {
229 auto onFocus = [&item]
230 {
231 item.parentWindow.disableTimerUntilMouseMoves();
232 item.parentWindow.ensureItemComponentIsVisible (item, -1);
233 item.parentWindow.setCurrentlyHighlightedChild (&item);
234 };
235
236 auto onToggle = [&handler, &item, onFocus]
237 {
238 if (handler.getCurrentState().isSelected())
239 item.parentWindow.setCurrentlyHighlightedChild (nullptr);
240 else
241 onFocus();
242 };
243
244 auto actions = AccessibilityActions().addAction (AccessibilityActionType::focus, std::move (onFocus))
245 .addAction (AccessibilityActionType::toggle, std::move (onToggle));
246
247 if (canBeTriggered (item.item))
248 {
249 actions.addAction (AccessibilityActionType::press, [&item]
250 {
251 item.parentWindow.setCurrentlyHighlightedChild (&item);
252 item.parentWindow.triggerCurrentlyHighlightedItem();
253 });
254 }
255
256 if (hasActiveSubMenu (item.item))
257 {
258 auto showSubMenu = [&item]
259 {
260 item.parentWindow.showSubMenuFor (&item);
261
262 if (auto* subMenu = item.parentWindow.activeSubMenu.get())
263 subMenu->setCurrentlyHighlightedChild (subMenu->items.getFirst());
264 };
265
266 actions.addAction (AccessibilityActionType::press, showSubMenu);
268 }
269
270 return actions;
271 }
272
273 ItemComponent& itemComponent;
274 };
275
276 std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
277 {
278 return item.isSeparator ? createIgnoredAccessibilityHandler (*this)
279 : std::make_unique<ItemAccessibilityHandler> (*this);
280 }
281
282 //==============================================================================
283 MenuWindow& parentWindow;
284 const PopupMenu::Options& options;
285 // NB: we use a copy of the one from the item info in case we're using our own section comp
287 bool isHighlighted = false;
288
289 void updateShortcutKeyDescription()
290 {
291 if (item.commandManager != nullptr
292 && item.itemID != 0
293 && item.shortcutKeyDescription.isEmpty())
294 {
295 String shortcutKey;
296
297 for (auto& keypress : item.commandManager->getKeyMappings()
298 ->getKeyPressesAssignedToCommand (item.itemID))
299 {
300 auto key = keypress.getTextDescriptionWithIcons();
301
302 if (shortcutKey.isNotEmpty())
303 shortcutKey << ", ";
304
305 if (key.length() == 1 && key[0] < 128)
306 shortcutKey << "shortcut: '" << key << '\'';
307 else
308 shortcutKey << key;
309 }
310
311 item.shortcutKeyDescription = shortcutKey.trim();
312 }
313 }
314
315 String getTextForMeasurement() const
316 {
317 return item.shortcutKeyDescription.isNotEmpty() ? item.text + " " + item.shortcutKeyDescription
318 : item.text;
319 }
320
322};
323
324//==============================================================================
326{
327 MenuWindow (const PopupMenu& menu,
328 MenuWindow* parentWindow,
330 bool alignToRectangle,
333 float parentScaleFactor = 1.0f)
334 : Component ("menu"),
335 parent (parentWindow),
336 options (opts.withParentComponent (findNonNullLookAndFeel (menu, parentWindow).getParentComponentForMenuOptions (opts))),
337 managerOfChosenCommand (manager),
338 componentAttachedTo (options.getTargetComponent()),
339 dismissOnMouseUp (shouldDismissOnMouseUp),
340 windowCreationTime (Time::getMillisecondCounter()),
341 lastFocusedTime (windowCreationTime),
342 timeEnteredCurrentChildComp (windowCreationTime),
343 scaleFactor (parentWindow != nullptr ? parentScaleFactor : 1.0f)
344 {
345 setWantsKeyboardFocus (false);
347 setAlwaysOnTop (true);
349
350 setLookAndFeel (findLookAndFeel (menu, parentWindow));
351
352 auto& lf = getLookAndFeel();
353
354 if (auto* pc = options.getParentComponent())
355 {
356 pc->addChildComponent (this);
357 }
358 else
359 {
360 const auto shouldDisableAccessibility = [this]
361 {
362 const auto* compToCheck = parent != nullptr ? parent
363 : options.getTargetComponent();
364
365 return compToCheck != nullptr && ! compToCheck->isAccessible();
366 }();
367
369 setAccessible (false);
370
373 | lf.getMenuWindowFlags());
374
376 }
377
378 if (options.getParentComponent() == nullptr && parentWindow == nullptr && lf.shouldPopupMenuScaleWithTargetComponent (options))
379 if (auto* targetComponent = options.getTargetComponent())
380 scaleFactor = Component::getApproximateScaleFactorForComponent (targetComponent);
381
382 setOpaque (lf.findColour (PopupMenu::backgroundColourId).isOpaque()
384
385 const auto initialSelectedId = options.getInitiallySelectedItemId();
386
387 for (int i = 0; i < menu.items.size(); ++i)
388 {
389 auto& item = menu.items.getReference (i);
390
391 if (i + 1 < menu.items.size() || ! item.isSeparator)
392 {
393 auto* child = items.add (new ItemComponent (item, options, *this));
394 child->setExplicitFocusOrder (1 + i);
395
396 if (initialSelectedId != 0 && item.itemID == initialSelectedId)
397 setCurrentlyHighlightedChild (child);
398 }
399 }
400
401 auto targetArea = options.getTargetScreenArea() / scaleFactor;
402
403 calculateWindowPos (targetArea, alignToRectangle);
404 setTopLeftPosition (windowPos.getPosition());
405
406 if (auto visibleID = options.getItemThatMustBeVisible())
407 {
408 for (auto* item : items)
409 {
410 if (item->item.itemID == visibleID)
411 {
412 const auto targetPosition = [&]
413 {
414 if (auto* pc = options.getParentComponent())
415 return pc->getLocalPoint (nullptr, targetArea.getTopLeft());
416
417 return targetArea.getTopLeft();
418 }();
419
420 auto y = targetPosition.getY() - windowPos.getY();
421 ensureItemComponentIsVisible (*item, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1);
422
423 break;
424 }
425 }
426 }
427
428 resizeToBestWindowPos();
429
430 getActiveWindows().add (this);
431 lf.preparePopupMenuWindow (*this);
432
433 getMouseState (Desktop::getInstance().getMainMouseSource()); // forces creation of a mouse source watcher for the main mouse
434 }
435
436 ~MenuWindow() override
437 {
438 getActiveWindows().removeFirstMatchingValue (this);
440 activeSubMenu.reset();
441 items.clear();
442 }
443
444 //==============================================================================
445 void paint (Graphics& g) override
446 {
447 if (isOpaque())
448 g.fillAll (Colours::white);
449
450 auto& theme = getLookAndFeel();
451 theme.drawPopupMenuBackgroundWithOptions (g, getWidth(), getHeight(), options);
452
453 if (columnWidths.isEmpty())
454 return;
455
456 const auto separatorWidth = theme.getPopupMenuColumnSeparatorWidthWithOptions (options);
457 const auto border = theme.getPopupMenuBorderSizeWithOptions (options);
458
459 auto currentX = 0;
460
461 std::for_each (columnWidths.begin(), std::prev (columnWidths.end()), [&] (int width)
462 {
463 const Rectangle<int> separator (currentX + width,
464 border,
465 separatorWidth,
466 getHeight() - border * 2);
467 theme.drawPopupMenuColumnSeparatorWithOptions (g, separator, options);
468 currentX += width + separatorWidth;
469 });
470 }
471
472 void paintOverChildren (Graphics& g) override
473 {
474 auto& lf = getLookAndFeel();
475
476 if (options.getParentComponent())
477 lf.drawResizableFrame (g, getWidth(), getHeight(),
478 BorderSize<int> (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options)));
479
480 if (canScroll())
481 {
482 if (isTopScrollZoneActive())
483 {
484 lf.drawPopupMenuUpDownArrowWithOptions (g,
485 getWidth(),
486 PopupMenuSettings::scrollZone,
487 true,
488 options);
489 }
490
491 if (isBottomScrollZoneActive())
492 {
493 g.setOrigin (0, getHeight() - PopupMenuSettings::scrollZone);
494 lf.drawPopupMenuUpDownArrowWithOptions (g,
495 getWidth(),
496 PopupMenuSettings::scrollZone,
497 false,
498 options);
499 }
500 }
501 }
502
503 //==============================================================================
504 // hide this and all sub-comps
505 void hide (const PopupMenu::Item* item, bool makeInvisible)
506 {
507 if (isVisible())
508 {
510
511 activeSubMenu.reset();
512 currentChild = nullptr;
513
514 if (item != nullptr
515 && item->commandManager != nullptr
516 && item->itemID != 0)
517 {
518 *managerOfChosenCommand = item->commandManager;
519 }
520
521 auto resultID = options.hasWatchedComponentBeenDeleted() ? 0 : getResultItemID (item);
522
524
525 if (deletionChecker != nullptr)
526 {
527 exitingModalState = true;
528
529 if (makeInvisible)
530 setVisible (false);
531 }
532
533 if (resultID != 0
534 && item != nullptr
535 && item->action != nullptr)
537 }
538 }
539
540 static int getResultItemID (const PopupMenu::Item* item)
541 {
542 if (item == nullptr)
543 return 0;
544
545 if (auto* cc = item->customCallback.get())
546 if (! cc->menuItemTriggered())
547 return 0;
548
549 return item->itemID;
550 }
551
552 void dismissMenu (const PopupMenu::Item* item)
553 {
554 if (parent != nullptr)
555 {
556 parent->dismissMenu (item);
557 }
558 else
559 {
560 if (item != nullptr)
561 {
562 // need a copy of this on the stack as the one passed in will get deleted during this call
563 auto mi (*item);
564 hide (&mi, false);
565 }
566 else
567 {
568 hide (nullptr, true);
569 }
570 }
571 }
572
573 float getDesktopScaleFactor() const override { return scaleFactor * Desktop::getInstance().getGlobalScaleFactor(); }
574
575 void visibilityChanged() override
576 {
577 if (! isShowing())
578 return;
579
580 auto* accessibleFocus = [this]
581 {
582 if (currentChild != nullptr)
583 if (auto* childHandler = currentChild->getAccessibilityHandler())
584 return childHandler;
585
587 }();
588
589 if (accessibleFocus != nullptr)
590 accessibleFocus->grabFocus();
591 }
592
593 //==============================================================================
594 bool keyPressed (const KeyPress& key) override
595 {
597 {
598 selectNextItem (MenuSelectionDirection::forwards);
599 }
600 else if (key.isKeyCode (KeyPress::upKey))
601 {
602 selectNextItem (MenuSelectionDirection::backwards);
603 }
604 else if (key.isKeyCode (KeyPress::leftKey))
605 {
606 if (parent != nullptr)
607 {
608 Component::SafePointer<MenuWindow> parentWindow (parent);
609 ItemComponent* currentChildOfParent = parentWindow->currentChild;
610
611 hide (nullptr, true);
612
613 if (parentWindow != nullptr)
614 parentWindow->setCurrentlyHighlightedChild (currentChildOfParent);
615
616 disableTimerUntilMouseMoves();
617 }
618 else if (componentAttachedTo != nullptr)
619 {
620 componentAttachedTo->keyPressed (key);
621 }
622 }
623 else if (key.isKeyCode (KeyPress::rightKey))
624 {
625 disableTimerUntilMouseMoves();
626
627 if (showSubMenuFor (currentChild))
628 {
629 if (isSubMenuVisible())
630 activeSubMenu->selectNextItem (MenuSelectionDirection::current);
631 }
632 else if (componentAttachedTo != nullptr)
633 {
634 componentAttachedTo->keyPressed (key);
635 }
636 }
638 {
639 triggerCurrentlyHighlightedItem();
640 }
641 else if (key.isKeyCode (KeyPress::escapeKey))
642 {
643 dismissMenu (nullptr);
644 }
645 else
646 {
647 return false;
648 }
649
650 return true;
651 }
652
653 void inputAttemptWhenModal() override
654 {
656
657 for (auto* ms : mouseSourceStates)
658 {
659 ms->timerCallback();
660
661 if (deletionChecker == nullptr)
662 return;
663 }
664
665 if (! isOverAnyMenu())
666 {
667 if (componentAttachedTo != nullptr)
668 {
669 // we want to dismiss the menu, but if we do it synchronously, then
670 // the mouse-click will be allowed to pass through. That's good, except
671 // when the user clicks on the button that originally popped the menu up,
672 // as they'll expect the menu to go away, and in fact it'll just
673 // come back. So only dismiss synchronously if they're not on the original
674 // comp that we're attached to.
675 auto mousePos = componentAttachedTo->getMouseXYRelative();
676
677 if (componentAttachedTo->reallyContains (mousePos, true))
678 {
679 postCommandMessage (PopupMenuSettings::dismissCommandId); // dismiss asynchronously
680 return;
681 }
682 }
683
684 dismissMenu (nullptr);
685 }
686 }
687
688 void handleCommandMessage (int commandId) override
689 {
691
692 if (commandId == PopupMenuSettings::dismissCommandId)
693 dismissMenu (nullptr);
694 }
695
696 //==============================================================================
697 void mouseMove (const MouseEvent& e) override { handleMouseEvent (e); }
698 void mouseDown (const MouseEvent& e) override { handleMouseEvent (e); }
699 void mouseDrag (const MouseEvent& e) override { handleMouseEvent (e); }
700 void mouseUp (const MouseEvent& e) override { handleMouseEvent (e); }
701
702 void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override
703 {
704 alterChildYPos (roundToInt (-10.0f * wheel.deltaY * PopupMenuSettings::scrollZone));
705 }
706
707 void handleMouseEvent (const MouseEvent& e)
708 {
709 getMouseState (e.source).handleMouseEvent (e);
710 }
711
712 bool windowIsStillValid()
713 {
714 if (! isVisible())
715 return false;
716
717 if (componentAttachedTo != options.getTargetComponent())
718 {
719 dismissMenu (nullptr);
720 return false;
721 }
722
723 if (auto* currentlyModalWindow = dynamic_cast<MenuWindow*> (Component::getCurrentlyModalComponent()))
724 if (! treeContains (currentlyModalWindow))
725 return false;
726
727 if (exitingModalState)
728 return false;
729
730 return true;
731 }
732
733 static Array<MenuWindow*>& getActiveWindows()
734 {
736 return activeMenuWindows;
737 }
738
739 MouseSourceState& getMouseState (MouseInputSource source)
740 {
741 MouseSourceState* mouseState = nullptr;
742
743 for (auto* ms : mouseSourceStates)
744 {
745 if (ms->source == source) mouseState = ms;
746 else if (ms->source.getType() != source.getType()) ms->stopTimer();
747 }
748
749 if (mouseState == nullptr)
750 {
751 mouseState = new MouseSourceState (*this, source);
752 mouseSourceStates.add (mouseState);
753 }
754
755 return *mouseState;
756 }
757
758 //==============================================================================
759 bool isOverAnyMenu() const
760 {
761 return parent != nullptr ? parent->isOverAnyMenu()
762 : isOverChildren();
763 }
764
765 bool isOverChildren() const
766 {
767 return isVisible()
768 && (isAnyMouseOver() || (activeSubMenu != nullptr && activeSubMenu->isOverChildren()));
769 }
770
771 bool isAnyMouseOver() const
772 {
773 for (auto* ms : mouseSourceStates)
774 if (ms->isOver())
775 return true;
776
777 return false;
778 }
779
780 bool treeContains (const MenuWindow* const window) const noexcept
781 {
782 auto* mw = this;
783
784 while (mw->parent != nullptr)
785 mw = mw->parent;
786
787 while (mw != nullptr)
788 {
789 if (mw == window)
790 return true;
791
792 mw = mw->activeSubMenu.get();
793 }
794
795 return false;
796 }
797
798 bool doesAnyJuceCompHaveFocus()
799 {
800 if (! detail::WindowingHelpers::isForegroundOrEmbeddedProcess (componentAttachedTo))
801 return false;
802
804 return true;
805
806 for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
807 {
808 if (ComponentPeer::getPeer (i)->isFocused())
809 {
810 hasAnyJuceCompHadFocus = true;
811 return true;
812 }
813 }
814
815 return ! hasAnyJuceCompHadFocus;
816 }
817
818 //==============================================================================
819 Rectangle<int> getParentArea (Point<int> targetPoint, Component* relativeTo = nullptr)
820 {
821 if (relativeTo != nullptr)
822 targetPoint = relativeTo->localPointToGlobal (targetPoint);
823
824 auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (targetPoint * scaleFactor);
825 auto parentArea = display->userArea.getIntersection (display->safeAreaInsets.subtractedFrom (display->totalArea));
826
827 if (auto* pc = options.getParentComponent())
828 {
829 return pc->getLocalArea (nullptr,
830 pc->getScreenBounds()
831 .reduced (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options))
832 .getIntersection (parentArea));
833 }
834
835 return parentArea;
836 }
837
838 void calculateWindowPos (Rectangle<int> target, const bool alignToRectangle)
839 {
840 auto parentArea = getParentArea (target.getCentre()) / scaleFactor;
841
842 if (auto* pc = options.getParentComponent())
843 target = pc->getLocalArea (nullptr, target).getIntersection (parentArea);
844
845 auto maxMenuHeight = parentArea.getHeight() - 24;
846
847 int x, y, widthToUse, heightToUse;
848 layoutMenuItems (parentArea.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse);
849
851 {
852 x = target.getX();
853
854 auto spaceUnder = parentArea.getBottom() - target.getBottom();
855 auto spaceOver = target.getY() - parentArea.getY();
856 auto bufferHeight = 30;
857
858 if (options.getPreferredPopupDirection() == Options::PopupDirection::upwards)
859 y = (heightToUse < spaceOver - bufferHeight || spaceOver >= spaceUnder) ? target.getY() - heightToUse
860 : target.getBottom();
861 else
862 y = (heightToUse < spaceUnder - bufferHeight || spaceUnder >= spaceOver) ? target.getBottom()
863 : target.getY() - heightToUse;
864 }
865 else
866 {
867 bool tendTowardsRight = target.getCentreX() < parentArea.getCentreX();
868
869 if (parent != nullptr)
870 {
871 if (parent->parent != nullptr)
872 {
873 const bool parentGoingRight = (parent->getX() + parent->getWidth() / 2
874 > parent->parent->getX() + parent->parent->getWidth() / 2);
875
876 if (parentGoingRight && target.getRight() + widthToUse < parentArea.getRight() - 4)
877 tendTowardsRight = true;
878 else if ((! parentGoingRight) && target.getX() > widthToUse + 4)
879 tendTowardsRight = false;
880 }
881 else if (target.getRight() + widthToUse < parentArea.getRight() - 32)
882 {
883 tendTowardsRight = true;
884 }
885 }
886
887 auto biggestSpace = jmax (parentArea.getRight() - target.getRight(),
888 target.getX() - parentArea.getX()) - 32;
889
891 {
892 layoutMenuItems (biggestSpace + target.getWidth() / 3, maxMenuHeight, widthToUse, heightToUse);
893
894 if (numColumns > 1)
895 layoutMenuItems (biggestSpace - 4, maxMenuHeight, widthToUse, heightToUse);
896
897 tendTowardsRight = (parentArea.getRight() - target.getRight()) >= (target.getX() - parentArea.getX());
898 }
899
900 x = tendTowardsRight ? jmin (parentArea.getRight() - widthToUse - 4, target.getRight())
901 : jmax (parentArea.getX() + 4, target.getX() - widthToUse);
902
903 if (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) == 0) // workaround for dismissing the window on mouse up when border size is 0
904 x += tendTowardsRight ? 1 : -1;
905
906 const auto border = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options);
907 y = target.getCentreY() > parentArea.getCentreY() ? jmax (parentArea.getY(), target.getBottom() - heightToUse) + border
908 : target.getY() - border;
909 }
910
911 x = jmax (parentArea.getX() + 1, jmin (parentArea.getRight() - (widthToUse + 6), x));
912 y = jmax (parentArea.getY() + 1, jmin (parentArea.getBottom() - (heightToUse + 6), y));
913
914 windowPos.setBounds (x, y, widthToUse, heightToUse);
915
916 // sets this flag if it's big enough to obscure any of its parent menus
917 hideOnExit = parent != nullptr
918 && parent->windowPos.intersects (windowPos.expanded (-4, -4));
919 }
920
921 void layoutMenuItems (const int maxMenuW, const int maxMenuH, int& width, int& height)
922 {
923 // Ensure we don't try to add an empty column after the final item
924 if (auto* last = items.getLast())
925 last->item.shouldBreakAfter = false;
926
927 const auto isBreak = [] (const ItemComponent* item) { return item->item.shouldBreakAfter; };
928 const auto numBreaks = static_cast<int> (std::count_if (items.begin(), items.end(), isBreak));
929 numColumns = numBreaks + 1;
930
931 if (numBreaks == 0)
932 insertColumnBreaks (maxMenuW, maxMenuH);
933
934 workOutManualSize (maxMenuW);
935 height = jmin (contentHeight, maxMenuH);
936
937 needsToScroll = contentHeight > height;
938
939 width = updateYPositions();
940 }
941
942 void insertColumnBreaks (const int maxMenuW, const int maxMenuH)
943 {
944 numColumns = options.getMinimumNumColumns();
945 contentHeight = 0;
946
947 auto maximumNumColumns = options.getMaximumNumColumns() > 0 ? options.getMaximumNumColumns() : 7;
948
949 for (;;)
950 {
951 auto totalW = workOutBestSize (maxMenuW);
952
953 if (totalW > maxMenuW)
954 {
955 numColumns = jmax (1, numColumns - 1);
956 workOutBestSize (maxMenuW); // to update col widths
957 break;
958 }
959
960 if (totalW > maxMenuW / 2
961 || contentHeight < maxMenuH
962 || numColumns >= maximumNumColumns)
963 break;
964
965 ++numColumns;
966 }
967
968 const auto itemsPerColumn = (items.size() + numColumns - 1) / numColumns;
969
970 for (auto i = 0;; i += itemsPerColumn)
971 {
972 const auto breakIndex = i + itemsPerColumn - 1;
973
974 if (breakIndex >= items.size())
975 break;
976
977 items[breakIndex]->item.shouldBreakAfter = true;
978 }
979
980 if (! items.isEmpty())
981 (*std::prev (items.end()))->item.shouldBreakAfter = false;
982 }
983
984 int correctColumnWidths (const int maxMenuW)
985 {
986 auto totalW = std::accumulate (columnWidths.begin(), columnWidths.end(), 0);
987 const auto minWidth = jmin (maxMenuW, options.getMinimumWidth());
988
989 if (totalW < minWidth)
990 {
991 totalW = minWidth;
992
993 for (auto& column : columnWidths)
994 column = totalW / numColumns;
995 }
996
997 return totalW;
998 }
999
1000 void workOutManualSize (const int maxMenuW)
1001 {
1002 contentHeight = 0;
1003 columnWidths.clear();
1004
1005 for (auto it = items.begin(), end = items.end(); it != end;)
1006 {
1007 const auto isBreak = [] (const ItemComponent* item) { return item->item.shouldBreakAfter; };
1008 const auto nextBreak = std::find_if (it, end, isBreak);
1009 const auto columnEnd = nextBreak == end ? end : std::next (nextBreak);
1010
1011 const auto getMaxWidth = [] (int acc, const ItemComponent* item) { return jmax (acc, item->getWidth()); };
1012 const auto colW = std::accumulate (it, columnEnd, options.getStandardItemHeight(), getMaxWidth);
1013 const auto adjustedColW = jmin (maxMenuW / jmax (1, numColumns - 2),
1014 colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2);
1015
1016 const auto sumHeight = [] (int acc, const ItemComponent* item) { return acc + item->getHeight(); };
1017 const auto colH = std::accumulate (it, columnEnd, 0, sumHeight);
1018
1019 contentHeight = jmax (contentHeight, colH);
1020 columnWidths.add (adjustedColW);
1021 it = columnEnd;
1022 }
1023
1024 contentHeight += getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2;
1025
1026 correctColumnWidths (maxMenuW);
1027 }
1028
1029 int workOutBestSize (const int maxMenuW)
1030 {
1031 contentHeight = 0;
1032 int childNum = 0;
1033
1034 for (int col = 0; col < numColumns; ++col)
1035 {
1036 int colW = options.getStandardItemHeight(), colH = 0;
1037
1038 auto numChildren = jmin (items.size() - childNum,
1039 (items.size() + numColumns - 1) / numColumns);
1040
1041 for (int i = numChildren; --i >= 0;)
1042 {
1043 colW = jmax (colW, items.getUnchecked (childNum + i)->getWidth());
1044 colH += items.getUnchecked (childNum + i)->getHeight();
1045 }
1046
1047 colW = jmin (maxMenuW / jmax (1, numColumns - 2),
1048 colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2);
1049
1050 columnWidths.set (col, colW);
1051 contentHeight = jmax (contentHeight, colH);
1052
1054 }
1055
1056 return correctColumnWidths (maxMenuW);
1057 }
1058
1059 void ensureItemComponentIsVisible (const ItemComponent& itemComp, int wantedY)
1060 {
1061 if (windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
1062 {
1063 auto currentY = itemComp.getY();
1064
1065 if (wantedY > 0 || currentY < 0 || itemComp.getBottom() > windowPos.getHeight())
1066 {
1067 if (wantedY < 0)
1068 wantedY = jlimit (PopupMenuSettings::scrollZone,
1069 jmax (PopupMenuSettings::scrollZone,
1070 windowPos.getHeight() - (PopupMenuSettings::scrollZone + itemComp.getHeight())),
1071 currentY);
1072
1073 auto parentArea = getParentArea (windowPos.getPosition(), options.getParentComponent()) / scaleFactor;
1074 auto deltaY = wantedY - currentY;
1075
1076 windowPos.setSize (jmin (windowPos.getWidth(), parentArea.getWidth()),
1077 jmin (windowPos.getHeight(), parentArea.getHeight()));
1078
1079 auto newY = jlimit (parentArea.getY(),
1080 parentArea.getBottom() - windowPos.getHeight(),
1081 windowPos.getY() + deltaY);
1082
1083 deltaY -= newY - windowPos.getY();
1084
1085 childYOffset -= deltaY;
1086 windowPos.setPosition (windowPos.getX(), newY);
1087
1088 updateYPositions();
1089 }
1090 }
1091 }
1092
1093 void resizeToBestWindowPos()
1094 {
1095 auto r = windowPos;
1096
1097 if (childYOffset < 0)
1098 {
1099 r = r.withTop (r.getY() - childYOffset);
1100 }
1101 else if (childYOffset > 0)
1102 {
1103 auto spaceAtBottom = r.getHeight() - (contentHeight - childYOffset);
1104
1105 if (spaceAtBottom > 0)
1106 r.setSize (r.getWidth(), r.getHeight() - spaceAtBottom);
1107 }
1108
1109 setBounds (r);
1110 updateYPositions();
1111 }
1112
1113 void alterChildYPos (int delta)
1114 {
1115 if (canScroll())
1116 {
1117 childYOffset += delta;
1118
1119 childYOffset = [&]
1120 {
1121 if (delta < 0)
1122 return jmax (childYOffset, 0);
1123
1124 if (delta > 0)
1125 {
1126 const auto limit = contentHeight
1127 - windowPos.getHeight()
1128 + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options);
1129 return jmin (childYOffset, limit);
1130 }
1131
1132 return childYOffset;
1133 }();
1134
1135 updateYPositions();
1136 }
1137 else
1138 {
1139 childYOffset = 0;
1140 }
1141
1142 resizeToBestWindowPos();
1143 repaint();
1144 }
1145
1146 int updateYPositions()
1147 {
1149 const auto initialY = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options)
1150 - (childYOffset + (getY() - windowPos.getY()));
1151
1152 auto col = 0;
1153 auto x = 0;
1154 auto y = initialY;
1155
1156 for (const auto& item : items)
1157 {
1158 jassert (col < columnWidths.size());
1159 const auto columnWidth = columnWidths[col];
1160 item->setBounds (x, y, columnWidth, item->getHeight());
1161 y += item->getHeight();
1162
1163 if (item->item.shouldBreakAfter)
1164 {
1165 col += 1;
1167 y = initialY;
1168 }
1169 }
1170
1171 return std::accumulate (columnWidths.begin(), columnWidths.end(), 0)
1172 + (separatorWidth * (columnWidths.size() - 1));
1173 }
1174
1175 void setCurrentlyHighlightedChild (ItemComponent* child)
1176 {
1177 if (currentChild != nullptr)
1178 currentChild->setHighlighted (false);
1179
1180 currentChild = child;
1181
1182 if (currentChild != nullptr)
1183 {
1184 currentChild->setHighlighted (true);
1185 timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter();
1186 }
1187
1188 if (auto* handler = getAccessibilityHandler())
1189 handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
1190 }
1191
1192 bool isSubMenuVisible() const noexcept { return activeSubMenu != nullptr && activeSubMenu->isVisible(); }
1193
1194 bool showSubMenuFor (ItemComponent* childComp)
1195 {
1196 activeSubMenu.reset();
1197
1198 if (childComp == nullptr || ! hasActiveSubMenu (childComp->item))
1199 return false;
1200
1201 activeSubMenu.reset (new HelperClasses::MenuWindow (*(childComp->item.subMenu), this,
1202 options.forSubmenu()
1203 .withTargetScreenArea (childComp->getScreenBounds())
1204 .withMinimumWidth (0),
1205 false, dismissOnMouseUp, managerOfChosenCommand, scaleFactor));
1206
1207 activeSubMenu->setVisible (true); // (must be called before enterModalState on Windows to avoid DropShadower confusion)
1208 activeSubMenu->enterModalState (false);
1209 activeSubMenu->toFront (false);
1210 return true;
1211 }
1212
1213 void triggerCurrentlyHighlightedItem()
1214 {
1215 if (currentChild != nullptr && canBeTriggered (currentChild->item))
1216 {
1217 dismissMenu (&currentChild->item);
1218 }
1219 }
1220
1221 enum class MenuSelectionDirection
1222 {
1223 forwards,
1224 backwards,
1225 current
1226 };
1227
1228 void selectNextItem (MenuSelectionDirection direction)
1229 {
1230 disableTimerUntilMouseMoves();
1231
1232 auto start = [&]
1233 {
1234 auto index = items.indexOf (currentChild);
1235
1236 if (index >= 0)
1237 return index;
1238
1239 return direction == MenuSelectionDirection::backwards ? items.size() - 1
1240 : 0;
1241 }();
1242
1243 auto preIncrement = (direction != MenuSelectionDirection::current && currentChild != nullptr);
1244
1245 for (int i = items.size(); --i >= 0;)
1246 {
1247 if (preIncrement)
1248 start += (direction == MenuSelectionDirection::backwards ? -1 : 1);
1249
1250 if (auto* mic = items.getUnchecked ((start + items.size()) % items.size()))
1251 {
1252 if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item))
1253 {
1254 setCurrentlyHighlightedChild (mic);
1255 return;
1256 }
1257 }
1258
1259 if (! preIncrement)
1260 preIncrement = true;
1261 }
1262 }
1263
1264 void disableTimerUntilMouseMoves()
1265 {
1266 disableMouseMoves = true;
1267
1268 if (parent != nullptr)
1269 parent->disableTimerUntilMouseMoves();
1270 }
1271
1272 bool canScroll() const noexcept { return childYOffset != 0 || needsToScroll; }
1273 bool isTopScrollZoneActive() const noexcept { return canScroll() && childYOffset > 0; }
1274 bool isBottomScrollZoneActive() const noexcept { return canScroll() && childYOffset < contentHeight - windowPos.getHeight(); }
1275
1276 //==============================================================================
1278 {
1279 return std::make_unique<AccessibilityHandler> (*this,
1280 AccessibilityRole::popupMenu,
1282 {
1283 if (currentChild != nullptr)
1284 {
1285 if (auto* handler = currentChild->getAccessibilityHandler())
1286 handler->grabFocus();
1287 }
1288 else
1289 {
1290 selectNextItem (MenuSelectionDirection::forwards);
1291 }
1292 }));
1293 }
1294
1295 LookAndFeel* findLookAndFeel (const PopupMenu& menu, MenuWindow* parentWindow) const
1296 {
1297 return parentWindow != nullptr ? &(parentWindow->getLookAndFeel())
1298 : menu.lookAndFeel.get();
1299 }
1300
1301 LookAndFeel& findNonNullLookAndFeel (const PopupMenu& menu, MenuWindow* parentWindow) const
1302 {
1303 if (auto* result = findLookAndFeel (menu, parentWindow))
1304 return *result;
1305
1306 return getLookAndFeel();
1307 }
1308
1309 //==============================================================================
1310 MenuWindow* parent;
1311 const Options options;
1313 ApplicationCommandManager** managerOfChosenCommand;
1314 WeakReference<Component> componentAttachedTo;
1315 Rectangle<int> windowPos;
1316 bool hasBeenOver = false, needsToScroll = false;
1317 bool dismissOnMouseUp, hideOnExit = false, disableMouseMoves = false, hasAnyJuceCompHadFocus = false;
1318 int numColumns = 0, contentHeight = 0, childYOffset = 0;
1320 std::unique_ptr<MenuWindow> activeSubMenu;
1321 Array<int> columnWidths;
1322 uint32 windowCreationTime, lastFocusedTime, timeEnteredCurrentChildComp;
1323 OwnedArray<MouseSourceState> mouseSourceStates;
1324 float scaleFactor;
1325 bool exitingModalState = false;
1326
1328};
1329
1330//==============================================================================
1332{
1333public:
1335 : window (w), source (s), lastScrollTime (Time::getMillisecondCounter())
1336 {
1337 startTimerHz (20);
1338 }
1339
1340 void handleMouseEvent (const MouseEvent& e)
1341 {
1342 if (! window.windowIsStillValid())
1343 return;
1344
1345 startTimerHz (20);
1346 handleMousePosition (e.getScreenPosition());
1347 }
1348
1349 void timerCallback() override
1350 {
1351 #if JUCE_WINDOWS
1352 // touch and pen devices on Windows send an offscreen mouse move after mouse up events
1353 // but we don't want to forward these on as they will dismiss the menu
1354 if ((source.isTouch() || source.isPen()) && ! isValidMousePosition())
1355 return;
1356 #endif
1357
1358 if (window.windowIsStillValid())
1359 handleMousePosition (source.getScreenPosition().roundToInt());
1360 }
1361
1362 bool isOver() const
1363 {
1364 return window.reallyContains (window.getLocalPoint (nullptr, source.getScreenPosition()).roundToInt(), true);
1365 }
1366
1367 MenuWindow& window;
1368 MouseInputSource source;
1369
1370private:
1371 Point<int> lastMousePos;
1372 double scrollAcceleration = 0;
1373 uint32 lastScrollTime, lastMouseMoveTime = 0;
1374 bool isDown = false;
1375
1376 void handleMousePosition (Point<int> globalMousePos)
1377 {
1378 auto localMousePos = window.getLocalPoint (nullptr, globalMousePos);
1380
1381 if (timeNow > window.timeEnteredCurrentChildComp + 100
1382 && window.reallyContains (localMousePos, true)
1383 && window.currentChild != nullptr
1384 && ! (window.disableMouseMoves || window.isSubMenuVisible()))
1385 {
1386 window.showSubMenuFor (window.currentChild);
1387 }
1388
1389 highlightItemUnderMouse (globalMousePos, localMousePos, timeNow);
1390
1391 const bool overScrollArea = scrollIfNecessary (localMousePos, timeNow);
1392 const bool isOverAny = window.isOverAnyMenu();
1393
1394 if (window.hideOnExit && window.hasBeenOver && ! isOverAny)
1395 window.hide (nullptr, true);
1396 else
1397 checkButtonState (localMousePos, timeNow, isDown, overScrollArea, isOverAny);
1398 }
1399
1400 void checkButtonState (Point<int> localMousePos, const uint32 timeNow,
1401 const bool wasDown, const bool overScrollArea, const bool isOverAny)
1402 {
1403 isDown = window.hasBeenOver
1406
1407 const auto reallyContained = window.reallyContains (localMousePos, true);
1408
1409 if (! window.doesAnyJuceCompHaveFocus() && ! reallyContained)
1410 {
1411 if (timeNow > window.lastFocusedTime + 10)
1412 {
1413 PopupMenuSettings::menuWasHiddenBecauseOfAppChange = true;
1414 window.dismissMenu (nullptr);
1415 // Note: This object may have been deleted by the previous call.
1416 }
1417 }
1418 else if (wasDown && timeNow > window.windowCreationTime + 250 && ! isDown && ! overScrollArea)
1419 {
1420 if (reallyContained)
1421 window.triggerCurrentlyHighlightedItem();
1422 else if ((window.hasBeenOver || ! window.dismissOnMouseUp) && ! isOverAny)
1423 window.dismissMenu (nullptr);
1424
1425 // Note: This object may have been deleted by the previous call.
1426 }
1427 else
1428 {
1429 window.lastFocusedTime = timeNow;
1430 }
1431 }
1432
1433 void highlightItemUnderMouse (Point<int> globalMousePos, Point<int> localMousePos, const uint32 timeNow)
1434 {
1435 if (globalMousePos != lastMousePos || timeNow > lastMouseMoveTime + 350)
1436 {
1437 const auto isMouseOver = window.reallyContains (localMousePos, true);
1438
1439 if (isMouseOver)
1440 window.hasBeenOver = true;
1441
1442 if (lastMousePos.getDistanceFrom (globalMousePos) > 2)
1443 {
1444 lastMouseMoveTime = timeNow;
1445
1446 if (window.disableMouseMoves && isMouseOver)
1447 window.disableMouseMoves = false;
1448 }
1449
1450 if (window.disableMouseMoves || (window.activeSubMenu != nullptr && window.activeSubMenu->isOverChildren()))
1451 return;
1452
1453 const bool isMovingTowardsMenu = isMouseOver && globalMousePos != lastMousePos
1454 && isMovingTowardsSubmenu (globalMousePos);
1455
1456 lastMousePos = globalMousePos;
1457
1458 if (! isMovingTowardsMenu)
1459 {
1460 auto* c = window.getComponentAt (localMousePos);
1461
1462 if (c == &window)
1463 c = nullptr;
1464
1465 auto* itemUnderMouse = dynamic_cast<ItemComponent*> (c);
1466
1467 if (itemUnderMouse == nullptr && c != nullptr)
1468 itemUnderMouse = c->findParentComponentOfClass<ItemComponent>();
1469
1470 if (itemUnderMouse != window.currentChild
1471 && (isMouseOver || (window.activeSubMenu == nullptr) || ! window.activeSubMenu->isVisible()))
1472 {
1473 if (isMouseOver && (c != nullptr) && (window.activeSubMenu != nullptr))
1474 window.activeSubMenu->hide (nullptr, true);
1475
1476 if (! isMouseOver)
1477 {
1478 if (! window.hasBeenOver)
1479 return;
1480
1481 itemUnderMouse = nullptr;
1482 }
1483
1484 window.setCurrentlyHighlightedChild (itemUnderMouse);
1485 }
1486 }
1487 }
1488 }
1489
1490 bool isMovingTowardsSubmenu (Point<int> newGlobalPos) const
1491 {
1492 if (window.activeSubMenu == nullptr)
1493 return false;
1494
1495 // try to intelligently guess whether the user is moving the mouse towards a currently-open
1496 // submenu. To do this, look at whether the mouse stays inside a triangular region that
1497 // extends from the last mouse pos to the submenu's rectangle..
1498
1499 auto itemScreenBounds = window.activeSubMenu->getScreenBounds();
1500 auto subX = (float) itemScreenBounds.getX();
1501
1502 auto oldGlobalPos = lastMousePos;
1503
1504 if (itemScreenBounds.getX() > window.getX())
1505 {
1506 oldGlobalPos -= Point<int> (2, 0); // to enlarge the triangle a bit, in case the mouse only moves a couple of pixels
1507 }
1508 else
1509 {
1510 oldGlobalPos += Point<int> (2, 0);
1511 subX += (float) itemScreenBounds.getWidth();
1512 }
1513
1514 Path areaTowardsSubMenu;
1515 areaTowardsSubMenu.addTriangle ((float) oldGlobalPos.x, (float) oldGlobalPos.y,
1516 subX, (float) itemScreenBounds.getY(),
1517 subX, (float) itemScreenBounds.getBottom());
1518
1519 return areaTowardsSubMenu.contains (newGlobalPos.toFloat());
1520 }
1521
1522 bool scrollIfNecessary (Point<int> localMousePos, const uint32 timeNow)
1523 {
1524 if (window.canScroll()
1525 && isPositiveAndBelow (localMousePos.x, window.getWidth())
1526 && (isPositiveAndBelow (localMousePos.y, window.getHeight()) || source.isDragging()))
1527 {
1528 if (window.isTopScrollZoneActive() && localMousePos.y < PopupMenuSettings::scrollZone)
1529 return scroll (timeNow, -1);
1530
1531 if (window.isBottomScrollZoneActive() && localMousePos.y > window.getHeight() - PopupMenuSettings::scrollZone)
1532 return scroll (timeNow, 1);
1533 }
1534
1535 scrollAcceleration = 1.0;
1536 return false;
1537 }
1538
1539 bool scroll (const uint32 timeNow, const int direction)
1540 {
1541 if (timeNow > lastScrollTime + 20)
1542 {
1543 scrollAcceleration = jmin (4.0, scrollAcceleration * 1.04);
1544 int amount = 0;
1545
1546 for (int i = 0; i < window.items.size() && amount == 0; ++i)
1547 amount = ((int) scrollAcceleration) * window.items.getUnchecked (i)->getHeight();
1548
1549 window.alterChildYPos (amount * direction);
1550 lastScrollTime = timeNow;
1551 }
1552
1553 return true;
1554 }
1555
1556 #if JUCE_WINDOWS
1558 {
1559 auto screenPos = source.getScreenPosition();
1560 auto localPos = (window.activeSubMenu == nullptr) ? window.getLocalPoint (nullptr, screenPos)
1561 : window.activeSubMenu->getLocalPoint (nullptr, screenPos);
1562
1563 if (localPos.x < 0 && localPos.y < 0)
1564 return false;
1565
1566 return true;
1567 }
1568 #endif
1569
1571};
1572
1573//==============================================================================
1575{
1578 width (w), height (h)
1579 {
1580 addAndMakeVisible (comp);
1581 }
1582
1583 void getIdealSize (int& idealWidth, int& idealHeight) override
1584 {
1585 idealWidth = width;
1586 idealHeight = height;
1587 }
1588
1589 void resized() override
1590 {
1591 if (auto* child = getChildComponent (0))
1592 child->setBounds (getLocalBounds());
1593 }
1594
1595 const int width, height;
1596
1598};
1599
1600};
1601
1602//==============================================================================
1604 : items (other.items),
1605 lookAndFeel (other.lookAndFeel)
1606{
1607}
1608
1610{
1611 if (this != &other)
1612 {
1613 items = other.items;
1614 lookAndFeel = other.lookAndFeel;
1615 }
1616
1617 return *this;
1618}
1619
1621 : items (std::move (other.items)),
1622 lookAndFeel (std::move (other.lookAndFeel))
1623{
1624}
1625
1627{
1628 items = std::move (other.items);
1629 lookAndFeel = other.lookAndFeel;
1630 return *this;
1631}
1632
1633PopupMenu::~PopupMenu() = default;
1634
1636{
1637 items.clear();
1638}
1639
1640//==============================================================================
1641PopupMenu::Item::Item() = default;
1642PopupMenu::Item::Item (String t) : text (std::move (t)), itemID (-1) {}
1643
1644PopupMenu::Item::Item (Item&&) = default;
1645PopupMenu::Item& PopupMenu::Item::operator= (Item&&) = default;
1646
1648 : text (other.text),
1649 itemID (other.itemID),
1650 action (other.action),
1651 subMenu (createCopyIfNotNull (other.subMenu.get())),
1652 image (other.image != nullptr ? other.image->createCopy() : nullptr),
1653 customComponent (other.customComponent),
1654 customCallback (other.customCallback),
1655 commandManager (other.commandManager),
1656 shortcutKeyDescription (other.shortcutKeyDescription),
1657 colour (other.colour),
1658 isEnabled (other.isEnabled),
1659 isTicked (other.isTicked),
1660 isSeparator (other.isSeparator),
1661 isSectionHeader (other.isSectionHeader),
1662 shouldBreakAfter (other.shouldBreakAfter)
1663{}
1664
1665PopupMenu::Item& PopupMenu::Item::operator= (const Item& other)
1666{
1667 text = other.text;
1668 itemID = other.itemID;
1669 action = other.action;
1670 subMenu.reset (createCopyIfNotNull (other.subMenu.get()));
1671 image = other.image != nullptr ? other.image->createCopy() : std::unique_ptr<Drawable>();
1672 customComponent = other.customComponent;
1673 customCallback = other.customCallback;
1674 commandManager = other.commandManager;
1675 shortcutKeyDescription = other.shortcutKeyDescription;
1676 colour = other.colour;
1677 isEnabled = other.isEnabled;
1678 isTicked = other.isTicked;
1679 isSeparator = other.isSeparator;
1680 isSectionHeader = other.isSectionHeader;
1681 shouldBreakAfter = other.shouldBreakAfter;
1682 return *this;
1683}
1684
1686{
1687 isTicked = shouldBeTicked;
1688 return *this;
1689}
1690
1692{
1693 isEnabled = shouldBeEnabled;
1694 return *this;
1695}
1696
1698{
1699 action = std::move (newAction);
1700 return *this;
1701}
1702
1704{
1705 itemID = newID;
1706 return *this;
1707}
1708
1710{
1711 colour = newColour;
1712 return *this;
1713}
1714
1716{
1717 customComponent = comp;
1718 return *this;
1719}
1720
1722{
1723 image = std::move (newImage);
1724 return *this;
1725}
1726
1728{
1729 isTicked = shouldBeTicked;
1730 return std::move (*this);
1731}
1732
1734{
1735 isEnabled = shouldBeEnabled;
1736 return std::move (*this);
1737}
1738
1740{
1741 action = std::move (newAction);
1742 return std::move (*this);
1743}
1744
1746{
1747 itemID = newID;
1748 return std::move (*this);
1749}
1750
1752{
1753 colour = newColour;
1754 return std::move (*this);
1755}
1756
1758{
1759 customComponent = comp;
1760 return std::move (*this);
1761}
1762
1764{
1765 image = std::move (newImage);
1766 return std::move (*this);
1767}
1768
1770{
1771 // An ID of 0 is used as a return value to indicate that the user
1772 // didn't pick anything, so you shouldn't use it as the ID for an item.
1773 jassert (newItem.itemID != 0
1774 || newItem.isSeparator || newItem.isSectionHeader
1775 || newItem.subMenu != nullptr);
1776
1777 items.add (std::move (newItem));
1778}
1779
1781{
1782 addItem (std::move (itemText), true, false, std::move (action));
1783}
1784
1785void PopupMenu::addItem (String itemText, bool isActive, bool isTicked, std::function<void()> action)
1786{
1787 Item i (std::move (itemText));
1788 i.action = std::move (action);
1789 i.isEnabled = isActive;
1790 i.isTicked = isTicked;
1791 addItem (std::move (i));
1792}
1793
1794void PopupMenu::addItem (int itemResultID, String itemText, bool isActive, bool isTicked)
1795{
1796 Item i (std::move (itemText));
1797 i.itemID = itemResultID;
1798 i.isEnabled = isActive;
1799 i.isTicked = isTicked;
1800 addItem (std::move (i));
1801}
1802
1803static std::unique_ptr<Drawable> createDrawableFromImage (const Image& im)
1804{
1805 if (im.isValid())
1806 {
1807 auto d = new DrawableImage();
1808 d->setImage (im);
1809 return std::unique_ptr<Drawable> (d);
1810 }
1811
1812 return {};
1813}
1814
1815void PopupMenu::addItem (int itemResultID, String itemText, bool isActive, bool isTicked, const Image& iconToUse)
1816{
1817 addItem (itemResultID, std::move (itemText), isActive, isTicked, createDrawableFromImage (iconToUse));
1818}
1819
1821 bool isTicked, std::unique_ptr<Drawable> iconToUse)
1822{
1823 Item i (std::move (itemText));
1824 i.itemID = itemResultID;
1825 i.isEnabled = isActive;
1826 i.isTicked = isTicked;
1827 i.image = std::move (iconToUse);
1828 addItem (std::move (i));
1829}
1830
1832 const CommandID commandID,
1834{
1835 addCommandItem (commandManager, commandID, displayName, nullptr);
1836}
1837
1839 const CommandID commandID,
1842{
1843 jassert (commandManager != nullptr && commandID != 0);
1844
1845 if (auto* registeredInfo = commandManager->getCommandForID (commandID))
1846 {
1848 auto* target = commandManager->getTargetForCommand (commandID, info);
1849
1850 Item i;
1851 i.text = displayName.isNotEmpty() ? std::move (displayName) : info.shortName;
1852 i.itemID = (int) commandID;
1853 i.commandManager = commandManager;
1854 i.isEnabled = target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0;
1856 i.image = std::move (iconToUse);
1857 addItem (std::move (i));
1858 }
1859}
1860
1862 bool isActive, bool isTicked, std::unique_ptr<Drawable> iconToUse)
1863{
1864 Item i (std::move (itemText));
1865 i.itemID = itemResultID;
1867 i.isEnabled = isActive;
1868 i.isTicked = isTicked;
1869 i.image = std::move (iconToUse);
1870 addItem (std::move (i));
1871}
1872
1874 bool isActive, bool isTicked, const Image& iconToUse)
1875{
1876 Item i (std::move (itemText));
1877 i.itemID = itemResultID;
1879 i.isEnabled = isActive;
1880 i.isTicked = isTicked;
1881 i.image = createDrawableFromImage (iconToUse);
1882 addItem (std::move (i));
1883}
1884
1888 const String& itemTitle)
1889{
1890 Item i;
1891 i.text = itemTitle;
1892 i.itemID = itemResultID;
1893 i.customComponent = cc.release();
1894 i.subMenu.reset (createCopyIfNotNull (subMenu.get()));
1895
1896 // If this assertion is hit, this item will be visible to screen readers but with
1897 // no name, which may be confusing to users.
1898 // It's probably a good idea to add a title for this menu item that describes
1899 // the meaning of the item, or the contents of the submenu, as appropriate.
1900 // If you don't want this menu item to be press-able directly, pass "false" to the
1901 // constructor of the CustomComponent.
1902 jassert (! (HelperClasses::ItemComponent::isAccessibilityHandlerRequired (i) && itemTitle.isEmpty()));
1903
1904 addItem (std::move (i));
1905}
1906
1908 Component& customComponent,
1909 int idealWidth, int idealHeight,
1912 const String& itemTitle)
1913{
1914 auto comp = std::make_unique<HelperClasses::NormalComponentWrapper> (customComponent, idealWidth, idealHeight,
1916 addCustomItem (itemResultID, std::move (comp), std::move (subMenu), itemTitle);
1917}
1918
1920{
1921 addSubMenu (std::move (subMenuName), std::move (subMenu), isActive, nullptr, false, 0);
1922}
1923
1925 const Image& iconToUse, bool isTicked, int itemResultID)
1926{
1927 addSubMenu (std::move (subMenuName), std::move (subMenu), isActive,
1928 createDrawableFromImage (iconToUse), isTicked, itemResultID);
1929}
1930
1933{
1934 Item i (std::move (subMenuName));
1935 i.itemID = itemResultID;
1936 i.isEnabled = isActive && (itemResultID != 0 || subMenu.getNumItems() > 0);
1937 i.subMenu.reset (new PopupMenu (std::move (subMenu)));
1938 i.isTicked = isTicked;
1939 i.image = std::move (iconToUse);
1940 addItem (std::move (i));
1941}
1942
1944{
1945 if (items.size() > 0 && ! items.getLast().isSeparator)
1946 {
1947 Item i;
1948 i.isSeparator = true;
1949 addItem (std::move (i));
1950 }
1951}
1952
1954{
1955 Item i (std::move (title));
1956 i.itemID = 0;
1957 i.isSectionHeader = true;
1958 addItem (std::move (i));
1959}
1960
1962{
1963 if (! items.isEmpty())
1964 std::prev (items.end())->shouldBreakAfter = true;
1965}
1966
1967//==============================================================================
1969{
1970 targetArea.setPosition (Desktop::getMousePosition());
1971}
1972
1973template <typename Member, typename Item>
1974static PopupMenu::Options with (PopupMenu::Options options, Member&& member, Item&& item)
1975{
1976 options.*member = std::forward<Item> (item);
1977 return options;
1978}
1979
1981{
1982 auto o = with (with (*this, &Options::targetComponent, comp), &Options::topLevelTarget, comp);
1983
1984 if (comp != nullptr)
1985 o.targetArea = comp->getScreenBounds();
1986
1987 return o;
1988}
1989
1991{
1992 return withTargetComponent (&comp);
1993}
1994
1996{
1997 return with (*this, &Options::targetArea, area);
1998}
1999
2004
2006{
2007 return with (with (*this, &Options::isWatchingForDeletion, true),
2008 &Options::componentToWatchForDeletion,
2009 &comp);
2010}
2011
2013{
2014 return with (*this, &Options::minWidth, w);
2015}
2016
2018{
2019 return with (*this, &Options::minColumns, cols);
2020}
2021
2023{
2024 return with (*this, &Options::maxColumns, cols);
2025}
2026
2028{
2029 return with (*this, &Options::standardHeight, height);
2030}
2031
2033{
2034 return with (*this, &Options::visibleItemID, idOfItemToBeVisible);
2035}
2036
2038{
2039 return with (*this, &Options::parentComponent, parent);
2040}
2041
2043{
2044 return with (*this, &Options::preferredPopupDirection, direction);
2045}
2046
2048{
2049 return with (*this, &Options::initiallySelectedItemId, idOfItemToBeSelected);
2050}
2051
2053{
2054 return with (*this, &Options::targetComponent, nullptr);
2055}
2056
2057Component* PopupMenu::createWindow (const Options& options,
2058 ApplicationCommandManager** managerOfChosenCommand) const
2059{
2060 #if JUCE_WINDOWS
2061 const auto scope = [&]() -> std::unique_ptr<ScopedThreadDPIAwarenessSetter>
2062 {
2063 if (auto* target = options.getTargetComponent())
2064 if (auto* handle = target->getWindowHandle())
2065 return std::make_unique<ScopedThreadDPIAwarenessSetter> (handle);
2066
2067 return nullptr;
2068 }();
2069 #endif
2070
2071 return items.isEmpty() ? nullptr
2072 : new HelperClasses::MenuWindow (*this, nullptr, options,
2073 ! options.getTargetScreenArea().isEmpty(),
2075 managerOfChosenCommand);
2076}
2077
2078//==============================================================================
2079// This invokes any command manager commands and deletes the menu window when it is dismissed
2081{
2082 PopupMenuCompletionCallback() = default;
2083
2084 void modalStateFinished (int result) override
2085 {
2086 if (managerOfChosenCommand != nullptr && result != 0)
2087 {
2089 info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
2090
2091 managerOfChosenCommand->invoke (info, true);
2092 }
2093
2094 // (this would be the place to fade out the component, if that's what's required)
2095 component.reset();
2096
2097 if (PopupMenuSettings::menuWasHiddenBecauseOfAppChange)
2098 return;
2099
2100 if (auto* focusComponent = Component::getCurrentlyFocusedComponent())
2101 {
2102 const auto focusedIsNotMinimised = [focusComponent]
2103 {
2104 if (auto* peer = focusComponent->getPeer())
2105 return ! peer->isMinimised();
2106
2107 return false;
2108 }();
2109
2110 if (focusedIsNotMinimised)
2111 {
2112 if (auto* topLevel = focusComponent->getTopLevelComponent())
2113 topLevel->toFront (true);
2114
2115 if (focusComponent->isShowing() && ! focusComponent->hasKeyboardFocus (true))
2116 focusComponent->grabKeyboardFocus();
2117 }
2118 }
2119 }
2120
2121 ApplicationCommandManager* managerOfChosenCommand = nullptr;
2123
2125};
2126
2127int PopupMenu::showWithOptionalCallback (const Options& options,
2128 ModalComponentManager::Callback* userCallback,
2129 [[maybe_unused]] bool canBeModal)
2130{
2132 std::unique_ptr<PopupMenuCompletionCallback> callback (new PopupMenuCompletionCallback());
2133
2134 if (auto* window = createWindow (options, &(callback->managerOfChosenCommand)))
2135 {
2136 callback->component.reset (window);
2137
2138 PopupMenuSettings::menuWasHiddenBecauseOfAppChange = false;
2139
2140 window->setVisible (true); // (must be called before enterModalState on Windows to avoid DropShadower confusion)
2141 window->enterModalState (false, userCallbackDeleter.release());
2142 ModalComponentManager::getInstance()->attachCallback (window, callback.release());
2143
2144 window->toFront (false); // need to do this after making it modal, or it could
2145 // be stuck behind other comps that are already modal..
2146
2147 #if JUCE_MODAL_LOOPS_PERMITTED
2148 if (userCallback == nullptr && canBeModal)
2149 return window->runModalLoop();
2150 #else
2151 jassert (! (userCallback == nullptr && canBeModal));
2152 #endif
2153 }
2154
2155 return 0;
2156}
2157
2158//==============================================================================
2159#if JUCE_MODAL_LOOPS_PERMITTED
2160int PopupMenu::showMenu (const Options& options)
2161{
2162 return showWithOptionalCallback (options, nullptr, true);
2163}
2164#endif
2165
2167{
2168 showWithOptionalCallback (options, nullptr, false);
2169}
2170
2172{
2173 #if ! JUCE_MODAL_LOOPS_PERMITTED
2174 jassert (userCallback != nullptr);
2175 #endif
2176
2177 showWithOptionalCallback (options, userCallback, false);
2178}
2179
2181{
2182 showWithOptionalCallback (options, ModalCallbackFunction::create (userCallback), false);
2183}
2184
2185//==============================================================================
2186#if JUCE_MODAL_LOOPS_PERMITTED
2187int PopupMenu::show (int itemIDThatMustBeVisible, int minimumWidth,
2190{
2191 return showWithOptionalCallback (Options().withItemThatMustBeVisible (itemIDThatMustBeVisible)
2192 .withMinimumWidth (minimumWidth)
2193 .withMaximumNumColumns (maximumNumColumns)
2194 .withStandardItemHeight (standardItemHeight),
2195 callback, true);
2196}
2197
2198int PopupMenu::showAt (Rectangle<int> screenAreaToAttachTo,
2199 int itemIDThatMustBeVisible, int minimumWidth,
2201 ModalComponentManager::Callback* callback)
2202{
2203 return showWithOptionalCallback (Options().withTargetScreenArea (screenAreaToAttachTo)
2204 .withItemThatMustBeVisible (itemIDThatMustBeVisible)
2205 .withMinimumWidth (minimumWidth)
2206 .withMaximumNumColumns (maximumNumColumns)
2207 .withStandardItemHeight (standardItemHeight),
2208 callback, true);
2209}
2210
2211int PopupMenu::showAt (Component* componentToAttachTo,
2212 int itemIDThatMustBeVisible, int minimumWidth,
2214 ModalComponentManager::Callback* callback)
2215{
2216 auto options = Options().withItemThatMustBeVisible (itemIDThatMustBeVisible)
2217 .withMinimumWidth (minimumWidth)
2220
2221 if (componentToAttachTo != nullptr)
2222 options = options.withTargetComponent (componentToAttachTo);
2223
2224 return showWithOptionalCallback (options, callback, true);
2225}
2226#endif
2227
2229{
2230 auto& windows = HelperClasses::MenuWindow::getActiveWindows();
2231 auto numWindows = windows.size();
2232
2233 for (int i = numWindows; --i >= 0;)
2234 {
2235 if (auto* pmw = windows[i])
2236 {
2237 pmw->setLookAndFeel (nullptr);
2238 pmw->dismissMenu (nullptr);
2239 }
2240 }
2241
2242 return numWindows > 0;
2243}
2244
2245//==============================================================================
2247{
2248 int num = 0;
2249
2250 for (auto& mi : items)
2251 if (! mi.isSeparator)
2252 ++num;
2253
2254 return num;
2255}
2256
2257bool PopupMenu::containsCommandItem (const int commandID) const
2258{
2259 for (auto& mi : items)
2260 if ((mi.itemID == commandID && mi.commandManager != nullptr)
2261 || (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID)))
2262 return true;
2263
2264 return false;
2265}
2266
2268{
2269 for (auto& mi : items)
2270 {
2271 if (mi.subMenu != nullptr)
2272 {
2273 if (mi.subMenu->containsAnyActiveItems())
2274 return true;
2275 }
2276 else if (mi.isEnabled)
2277 {
2278 return true;
2279 }
2280 }
2281
2282 return false;
2283}
2284
2286{
2287 lookAndFeel = newLookAndFeel;
2288}
2289
2290void PopupMenu::setItem (CustomComponent& c, const Item* itemToUse)
2291{
2292 c.item = itemToUse;
2293 c.repaint();
2294}
2295
2296//==============================================================================
2298
2300 : triggeredAutomatically (autoTrigger)
2301{
2302}
2303
2304void PopupMenu::CustomComponent::setHighlighted (bool shouldBeHighlighted)
2305{
2306 isHighlighted = shouldBeHighlighted;
2307 repaint();
2308}
2309
2311{
2313 {
2314 if (auto* pmw = mic->findParentComponentOfClass<HelperClasses::MenuWindow>())
2315 {
2316 pmw->dismissMenu (&mic->item);
2317 }
2318 else
2319 {
2320 // something must have gone wrong with the component hierarchy if this happens..
2322 }
2323 }
2324 else
2325 {
2326 // why isn't this component inside a menu? Not much point triggering the item if
2327 // there's no menu.
2329 }
2330}
2331
2332//==============================================================================
2333PopupMenu::CustomCallback::CustomCallback() {}
2334PopupMenu::CustomCallback::~CustomCallback() {}
2335
2336//==============================================================================
2338{
2339 index.add (0);
2340 menus.add (&m);
2341}
2342
2344
2346{
2347 if (index.size() == 0 || menus.getLast()->items.size() == 0)
2348 return false;
2349
2350 currentItem = const_cast<PopupMenu::Item*> (&(menus.getLast()->items.getReference (index.getLast())));
2351
2352 if (searchRecursively && currentItem->subMenu != nullptr)
2353 {
2354 index.add (0);
2355 menus.add (currentItem->subMenu.get());
2356 }
2357 else
2358 {
2359 index.setUnchecked (index.size() - 1, index.getLast() + 1);
2360 }
2361
2362 while (index.size() > 0 && index.getLast() >= (int) menus.getLast()->items.size())
2363 {
2364 index.removeLast();
2365 menus.removeLast();
2366
2367 if (index.size() > 0)
2368 index.setUnchecked (index.size() - 1, index.getLast() + 1);
2369 }
2370
2371 return true;
2372}
2373
2375{
2376 jassert (currentItem != nullptr);
2377 return *(currentItem);
2378}
2379
2381
2383 bool, bool, bool,
2384 bool, bool,
2385 const String&,
2386 const String&,
2387 const Drawable*,
2388 const Colour*) {}
2389
2390void PopupMenu::LookAndFeelMethods::drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>&,
2391 const String&) {}
2392
2393void PopupMenu::LookAndFeelMethods::drawPopupMenuUpDownArrow (Graphics&, int, int, bool) {}
2394
2396
2397int PopupMenu::LookAndFeelMethods::getPopupMenuBorderSize() { return 0; }
2398
2399} // namespace juce
T accumulate(T... args)
A simple wrapper for building a collection of supported accessibility actions and corresponding callb...
virtual AccessibleState getCurrentState() const
Returns the current state of the UI element.
AccessibleState withExpandable() const noexcept
Sets the expandable flag and returns the new state.
AccessibleState withCheckable() const noexcept
Sets the checkable flag and returns the new state.
AccessibleState withAccessibleOffscreen() const noexcept
Sets the accessible offscreen flag and returns the new state.
AccessibleState withSelectable() const noexcept
Sets the selectable flag and returns the new state.
AccessibleState withExpanded() const noexcept
Sets the expanded flag and returns the new state.
AccessibleState withChecked() const noexcept
Sets the checked flag and returns the new state.
bool isFocused() const noexcept
Returns true if the UI element is focused.
AccessibleState withCollapsed() const noexcept
Sets the collapsed flag and returns the new state.
One of these objects holds a list of all the commands your app can perform, and despatches these comm...
ApplicationCommandTarget * getTargetForCommand(CommandID commandID, ApplicationCommandInfo &upToDateInfo)
Tries to find the best target to use to perform a given command.
const ApplicationCommandInfo * getCommandForID(CommandID commandID) const noexcept
Returns the details about a given command ID.
bool invoke(const ApplicationCommandTarget::InvocationInfo &invocationInfo, bool asynchronously)
Sends a command to the default target.
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
Definition juce_Array.h:222
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:215
ElementType * begin() noexcept
Returns a pointer to the first element in the array.
Definition juce_Array.h:328
ElementType * end() noexcept
Returns a pointer to the element which follows the last element in the array.
Definition juce_Array.h:344
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:418
void set(int indexToChange, ParameterType newValue)
Replaces an element with a new value.
Definition juce_Array.h:542
void clear()
Removes all elements from the array.
Definition juce_Array.h:188
ElementType getLast() const noexcept
Returns the last element in the array, or a default value if the array is empty.
Definition juce_Array.h:300
Specifies a set of gaps to be left around the sides of a rectangle.
Represents a colour, also including a transparency value.
Definition juce_Colour.h:38
static ModifierKeys getCurrentModifiersRealtime() noexcept
On desktop platforms this method will check all the mouse and key states and return a ModifierKeys ob...
static int getNumPeers() noexcept
Returns the number of currently-active peers.
@ windowIsTemporary
Indicates that the window is a temporary popup, like a menu, tooltip, etc.
@ windowIgnoresKeyPresses
Tells the window not to catch any keypresses.
static ComponentPeer * getPeer(int index) noexcept
Returns one of the currently-active peers.
Holds a pointer to some type of Component, which automatically becomes null if the component is delet...
The base class for all JUCE user-interface objects.
void setLookAndFeel(LookAndFeel *newLookAndFeel)
Sets the look and feel to use for this component.
bool isAccessible() const noexcept
Returns true if this component and its children are visible to accessibility clients.
static Component *JUCE_CALLTYPE getCurrentlyModalComponent(int index=0) noexcept
Returns one of the components that are currently modal.
void setAccessible(bool shouldBeAccessible)
Sets whether this component and its children are visible to accessibility clients.
bool isVisible() const noexcept
Tests whether the component is visible or not.
void exitModalState(int returnValue=0)
Ends a component's modal state.
bool reallyContains(Point< int > localPoint, bool returnTrueIfWithinAChild)
Returns true if a given point lies in this component, taking any overlapping siblings into account.
bool isOpaque() const noexcept
Returns true if no parts of this component are transparent.
void setFocusContainerType(FocusContainerType containerType) noexcept
Sets whether this component is a container for components that can have their focus traversed,...
int getHeight() const noexcept
Returns the component's height in pixels.
int getX() const noexcept
Returns the x coordinate of the component's left edge.
Point< int > getLocalPoint(const Component *sourceComponent, Point< int > pointRelativeToSourceComponent) const
Converts a point to be relative to this component's coordinate space.
static Component *JUCE_CALLTYPE getCurrentlyFocusedComponent() noexcept
Returns the component that currently has the keyboard focus.
bool isShowing() const
Tests whether this component and all its parents are visible.
static float JUCE_CALLTYPE getApproximateScaleFactorForComponent(const Component *targetComponent)
Returns the approximate scale factor for a given component by traversing its parent hierarchy and app...
void addAndMakeVisible(Component *child, int zOrder=-1)
Adds a child component to this one, and also makes the child visible if it isn't already.
void setAlwaysOnTop(bool shouldStayOnTop)
Sets whether the component should always be kept at the front of its siblings.
void addMouseListener(MouseListener *newListener, bool wantsEventsForAllNestedChildComponents)
Registers a listener to be told when mouse events occur in this component.
AccessibilityHandler * getAccessibilityHandler()
Returns the accessibility handler for this component, or nullptr if this component is not accessible.
void setOpaque(bool shouldBeOpaque)
Indicates whether any parts of the component might be transparent.
void postCommandMessage(int commandId)
Dispatches a numbered message to this component.
void repaint()
Marks the whole component as needing to be redrawn.
Component() noexcept
Creates a component.
@ focusContainer
The component will act as a top-level component within which focus is passed around.
Rectangle< int > getScreenBounds() const
Returns the bounds of this component, relative to the screen's top-left.
int getY() const noexcept
Returns the y coordinate of the top of this component.
virtual void setName(const String &newName)
Sets the name of this component.
void removeChildComponent(Component *childToRemove)
Removes one of this component's child-components.
Component * getChildComponent(int index) const noexcept
Returns one of this component's child components, by it index.
virtual void addToDesktop(int windowStyleFlags, void *nativeWindowToAttachTo=nullptr)
Makes this component appear as a window on the desktop.
void setBounds(int x, int y, int width, int height)
Changes the component's position and size.
void setSize(int newWidth, int newHeight)
Changes the size of the component.
void setWantsKeyboardFocus(bool wantsFocus) noexcept
Sets a flag to indicate whether this component wants keyboard focus or not.
void setMouseClickGrabsKeyboardFocus(bool shouldGrabFocus)
Chooses whether a click on this component automatically grabs the focus.
int getWidth() const noexcept
Returns the component's width in pixels.
LookAndFeel & getLookAndFeel() const noexcept
Finds the appropriate look-and-feel to use for this component.
Rectangle< int > getLocalBounds() const noexcept
Returns the component's bounds, relative to its own origin.
void setTopLeftPosition(int x, int y)
Moves the component to a new position.
virtual void setVisible(bool shouldBeVisible)
Makes the component visible or invisible.
String getName() const noexcept
Returns the name of this component.
virtual void handleCommandMessage(int commandId)
Called to handle a command that was sent by postCommandMessage().
float getGlobalScaleFactor() const noexcept
Returns the current global scale factor, as set by setGlobalScaleFactor().
const Displays & getDisplays() const noexcept
Returns the Displays object representing the connected displays.
void addGlobalMouseListener(MouseListener *listener)
Registers a MouseListener that will receive all mouse events that occur on any component.
static Point< int > getMousePosition()
Returns the mouse position.
static Desktop &JUCE_CALLTYPE getInstance()
There's only one desktop object, and this method will return it.
void removeGlobalMouseListener(MouseListener *listener)
Unregisters a MouseListener that was added with addGlobalMouseListener().
static bool canUseSemiTransparentWindows() noexcept
True if the OS supports semitransparent windows.
Rectangle< int > userArea
The total area of this display in logical pixels which isn't covered by OS-dependent objects like the...
const Display * getDisplayForPoint(Point< int > point, bool isPhysical=false) const noexcept
Returns the Display object representing the display containing a given Point (either in logical or ph...
A drawable object which is a bitmap image.
The base class for objects which can draw themselves, e.g.
A graphics context, used for drawing a component or image.
void fillAll() const
Fills the context's entire clip region with the current colour or brush.
void setOrigin(Point< int > newOrigin)
Moves the position of the context's origin.
Holds a fixed-size bitmap.
Definition juce_Image.h:58
Represents a key press, including any modifier keys that are needed.
static const int upKey
key-code for the cursor-up key
bool isKeyCode(int keyCodeToCompare) const noexcept
Checks whether the KeyPress's key is the same as the one provided, without checking the modifiers.
static const int rightKey
key-code for the cursor-right key
static const int downKey
key-code for the cursor-down key
static const int spaceKey
key-code for the space bar
static const int escapeKey
key-code for the escape key
static const int returnKey
key-code for the return key
static const int leftKey
key-code for the cursor-left key
LookAndFeel objects define the appearance of all the JUCE widgets, and subclasses can be used to appl...
static bool callAsync(std::function< void()> functionToCall)
Asynchronously invokes a function or C++11 lambda on the message thread.
static ModalComponentManager::Callback * create(CallbackFn &&fn)
This is a utility function to create a ModalComponentManager::Callback that will call a callable obje...
Receives callbacks when a modal component is dismissed.
static ModifierKeys currentModifiers
This object represents the last-known state of the keyboard and mouse buttons.
bool isAnyMouseButtonDown() const noexcept
Tests for any of the mouse-button flags.
Contains position and status information about a mouse event.
MouseInputSource source
The source device that generated this event.
Point< int > getScreenPosition() const
Returns the mouse position of this event, in global screen coordinates.
Represents a linear source of mouse events from a mouse device or individual finger in a multi-touch ...
Point< float > getScreenPosition() const noexcept
Returns the last-known screen position of this source.
bool isDragging() const noexcept
Returns true if this device is currently being pressed.
An array designed for holding objects.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
ValueType getDistanceFrom(Point other) const noexcept
Returns the straight-line distance between this point and another one.
Definition juce_Point.h:165
constexpr Point< int > roundToInt() const noexcept
Casts this point to a Point<int> object using roundToInt() to convert the values.
Definition juce_Point.h:245
A user-defined component that can be used as an item in a popup menu.
void triggerMenuItem()
Dismisses the menu, indicating that this item has been chosen.
CustomComponent()
Creates a custom item that is triggered automatically.
void timerCallback() override
The user-defined callback routine that actually gets called periodically.
bool next()
Returns true if there is another item, and sets up all this object's member variables to reflect that...
Item & getItem() const
Returns a reference to the description of the current item.
MenuItemIterator(const PopupMenu &menu, bool searchRecursively=false)
Creates an iterator that will scan through the items in the specified menu.
Class used to create a set of options to pass to the show() method.
Options withMinimumNumColumns(int minNumColumns) const
Sets the minimum number of columns in the popup window.
Options withMousePosition() const
Sets the target screen area to match the current mouse position.
Rectangle< int > getTargetScreenArea() const noexcept
Gets the target screen area.
Options withStandardItemHeight(int standardHeight) const
Sets the default height of each item in the popup menu.
Options withPreferredPopupDirection(PopupDirection direction) const
Sets the direction of the popup menu relative to the target screen area.
Options withMaximumNumColumns(int maxNumColumns) const
Sets the maximum number of columns in the popup window.
Options withTargetScreenArea(Rectangle< int > targetArea) const
Sets the region of the screen next to which the menu should be displayed.
Options withMinimumWidth(int minWidth) const
Sets the minimum width of the popup window.
int getStandardItemHeight() const noexcept
Gets the default height of items in the menu.
Options forSubmenu() const
Returns a copy of these options with the target component set to null.
Options withDeletionCheck(Component &componentToWatchForDeletion) const
If the passed component has been deleted when the popup menu exits, the selected item's action will n...
Options withTargetComponent(Component *targetComponent) const
Sets the target component to use when displaying the menu.
Options()
By default, the target screen area will be the current mouse position.
Component * getTargetComponent() const noexcept
Gets the target component.
Options withItemThatMustBeVisible(int idOfItemToBeVisible) const
Sets an item which must be visible when the menu is initially drawn.
Options withParentComponent(Component *parentComponent) const
Sets a component that the popup menu will be drawn into.
Options withInitiallySelectedItem(int idOfItemToBeSelected) const
Sets an item to select in the menu.
Creates and displays a popup-menu.
void addCommandItem(ApplicationCommandManager *commandManager, CommandID commandID, String displayName, std::unique_ptr< Drawable > iconToUse)
Adds an item that represents one of the commands in a command manager object.
bool containsCommandItem(int commandID) const
Returns true if the menu contains a command item that triggers the given command.
void addSeparator()
Appends a separator to the menu, to help break it up into sections.
void showMenuAsync(const Options &options)
Runs the menu asynchronously.
void addCustomItem(int itemResultID, std::unique_ptr< CustomComponent > customComponent, std::unique_ptr< const PopupMenu > optionalSubMenu=nullptr, const String &itemTitle={})
Appends a custom menu item.
void addColouredItem(int itemResultID, String itemText, Colour itemTextColour, bool isEnabled=true, bool isTicked=false, const Image &iconToUse={})
Appends a text item with a special colour.
void addSectionHeader(String title)
Adds a non-clickable text item to the menu.
int getNumItems() const noexcept
Returns the number of items that the menu currently contains.
~PopupMenu()
Destructor.
void setLookAndFeel(LookAndFeel *newLookAndFeel)
Specifies a look-and-feel for the menu and any sub-menus that it has.
@ backgroundColourId
The colour to fill the menu's background with.
PopupMenu()=default
Creates an empty popup menu.
void clear()
Resets the menu, removing all its items.
void addColumnBreak()
Adds a column break to the menu, to help break it up into sections.
void addSubMenu(String subMenuName, PopupMenu subMenu, bool isEnabled=true)
Appends a sub-menu.
PopupMenu & operator=(const PopupMenu &)
Copies this menu from another one.
bool containsAnyActiveItems() const noexcept
Returns true if the menu contains any items that can be used.
void addItem(Item newItem)
Adds an item to the menu.
static bool JUCE_CALLTYPE dismissAllActiveMenus()
Closes any menus that are currently open.
Manages a rectangle and allows geometric operations to be performed on it.
bool intersects(Rectangle other) const noexcept
Returns true if any part of another rectangle overlaps this one.
Rectangle withPosition(ValueType newX, ValueType newY) const noexcept
Returns a rectangle with the same size as this one, but a new position.
ValueType getX() const noexcept
Returns the x coordinate of the rectangle's left-hand-side.
Point< ValueType > getPosition() const noexcept
Returns the rectangle's top-left position as a Point.
Rectangle getIntersection(Rectangle other) const noexcept
Returns the region that is the overlap between this and another rectangle.
void setSize(ValueType newWidth, ValueType newHeight) noexcept
Changes the rectangle's size, leaving the position of its top-left corner unchanged.
ValueType getWidth() const noexcept
Returns the width of the rectangle.
void setPosition(Point< ValueType > newPos) noexcept
Changes the position of the rectangle's top-left corner (leaving its size unchanged).
ValueType getY() const noexcept
Returns the y coordinate of the rectangle's top edge.
bool isEmpty() const noexcept
Returns true if the rectangle's width or height are zero or less.
Rectangle withTop(ValueType newTop) const noexcept
Returns a new rectangle with a different y position, but the same bottom edge as this one.
ValueType getHeight() const noexcept
Returns the height of the rectangle.
Rectangle expanded(ValueType deltaX, ValueType deltaY) const noexcept
Returns a rectangle that is larger than this one by a given amount.
void setBounds(ValueType newX, ValueType newY, ValueType newWidth, ValueType newHeight) noexcept
Changes all the rectangle's coordinates.
A smart-pointer class which points to a reference-counted object.
The JUCE String class!
Definition juce_String.h:53
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
static uint32 getApproximateMillisecondCounter() noexcept
Less-accurate but faster version of getMillisecondCounter().
static uint32 getMillisecondCounter() noexcept
Returns the number of millisecs since a fixed event (usually system startup).
Makes repeated callbacks to a virtual method at a specified time interval.
Definition juce_Timer.h:52
void startTimerHz(int timerFrequencyHz) noexcept
Starts the timer with an interval specified in Hertz.
This class acts as a pointer which will automatically become null if the object to which it points is...
T count_if(T... args)
T find_if(T... args)
T for_each(T... args)
T get(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE(className)
This is a shorthand macro for deleting a class's copy constructor and copy assignment operator.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
#define JUCE_CALLTYPE
This macro defines the C calling convention used as the standard for JUCE calls.
typedef int
T make_unique(T... args)
typedef float
JUCE Namespace.
Type * createCopyIfNotNull(const Type *objectToCopy)
If a pointer is non-null, this returns a new copy of the object that it points to,...
Definition juce_Memory.h:60
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
@ rowSelectionChanged
Indicates that the selection of rows in a list or table has changed.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
@ showMenu
Represents the user showing a contextual menu for a UI element.
@ focus
Indicates that the UI element has received focus.
@ toggle
Represents a "toggle" action.
@ press
Represents a "press" action.
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
int CommandID
A type used to hold the unique ID for an application command.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
AccessibilityRole
The list of available roles for an AccessibilityHandler object.
Contains status information about a mouse wheel event.
T next(T... args)
T prev(T... args)
Holds information describing an application command.
int flags
A bitwise-OR of the values specified in the CommandFlags enum.
@ isTicked
Indicates that the command should have a tick next to it on a menu.
@ isDisabled
Indicates that the command can't currently be performed.
String shortName
A short name to describe the command.
Contains contextual details about the invocation of a command.
InvocationMethod invocationMethod
The type of event that triggered this command.
void modalStateFinished(int result) override
Called to indicate that a modal component has been dismissed.
void getIdealSize(int &idealWidth, int &idealHeight) override
Returns a rectangle with the size that this component would like to have.
void paint(Graphics &g) override
Components can override this method to draw their content.
std::unique_ptr< AccessibilityHandler > createAccessibilityHandler() override
Override this method to return a custom AccessibilityHandler for this component.
void paint(Graphics &g) override
Components can override this method to draw their content.
void resized() override
Called when this component's size has been changed.
void inputAttemptWhenModal() override
Called when the user tries to click on a component that is blocked by another modal component.
bool keyPressed(const KeyPress &key) override
Called when a key is pressed.
void handleCommandMessage(int commandId) override
Called to handle a command that was sent by postCommandMessage().
void mouseDrag(const MouseEvent &e) override
Called when the mouse is moved while a button is held down.
void paint(Graphics &g) override
Components can override this method to draw their content.
void visibilityChanged() override
Called when this component's visibility changes.
void mouseWheelMove(const MouseEvent &, const MouseWheelDetails &wheel) override
Called when the mouse-wheel is moved.
float getDesktopScaleFactor() const override
Returns the default scale factor to use for this component when it is placed on the desktop.
void mouseDown(const MouseEvent &e) override
Called when a mouse button is pressed.
void mouseUp(const MouseEvent &e) override
Called when a mouse button is released.
std::unique_ptr< AccessibilityHandler > createAccessibilityHandler() override
Override this method to return a custom AccessibilityHandler for this component.
void mouseMove(const MouseEvent &e) override
Called when the mouse moves inside a component.
void paintOverChildren(Graphics &g) override
Components can override this method to draw over the top of their children.
void resized() override
Called when this component's size has been changed.
void getIdealSize(int &idealWidth, int &idealHeight) override
Returns a rectangle with the size that this component would like to have.
Describes a popup menu item.
ApplicationCommandManager * commandManager
A command manager to use to automatically invoke the command, or nullptr if none is specified.
bool isTicked
True if this menu item should have a tick mark next to it.
bool isSectionHeader
True if this menu item is a section header.
Item & setImage(std::unique_ptr< Drawable >) &noexcept
Sets the image property (and returns a reference to this item to allow chaining).
Item & setTicked(bool shouldBeTicked=true) &noexcept
Sets the isTicked flag (and returns a reference to this item to allow chaining).
std::unique_ptr< Drawable > image
A drawable to use as an icon, or nullptr if there isn't one.
String text
The menu item's name.
std::unique_ptr< PopupMenu > subMenu
A sub-menu, or nullptr if there isn't one.
int itemID
The menu item's ID.
ReferenceCountedObjectPtr< CustomComponent > customComponent
A custom component for the item to display, or nullptr if there isn't one.
bool isEnabled
True if this menu item is enabled.
std::function< void()> action
An optional function which should be invoked when this menu item is triggered.
Item & setCustomComponent(ReferenceCountedObjectPtr< CustomComponent > customComponent) &noexcept
Sets the customComponent property (and returns a reference to this item to allow chaining).
Item & setEnabled(bool shouldBeEnabled) &noexcept
Sets the isEnabled flag (and returns a reference to this item to allow chaining).
bool isSeparator
True if this menu item is a separator line.
Item & setAction(std::function< void()> action) &noexcept
Sets the action property (and returns a reference to this item to allow chaining).
Colour colour
A colour to use to draw the menu text.
Item()
Creates a null item.
Item & setID(int newID) &noexcept
Sets the itemID property (and returns a reference to this item to allow chaining).
Item & setColour(Colour) &noexcept
Sets the colour property (and returns a reference to this item to allow chaining).
virtual int getPopupMenuColumnSeparatorWidthWithOptions(const Options &)=0
Return the amount of space that should be left between popup menu columns.
virtual void getIdealPopupMenuItemSizeWithOptions(const String &text, bool isSeparator, int standardMenuItemHeight, int &idealWidth, int &idealHeight, const Options &)=0
Finds the best size for an item in a popup menu.
virtual void drawPopupMenuItemWithOptions(Graphics &, const Rectangle< int > &area, bool isHighlighted, const Item &item, const Options &)=0
Draws one of the items in a popup menu.
virtual void getIdealPopupMenuItemSize(const String &text, bool isSeparator, int standardMenuItemHeight, int &idealWidth, int &idealHeight)
Finds the best size for an item in a popup menu.
virtual void drawPopupMenuBackground(Graphics &, int width, int height)
Fills the background of a popup menu component.
virtual void drawPopupMenuItem(Graphics &, const Rectangle< int > &area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String &text, const String &shortcutKeyText, const Drawable *icon, const Colour *textColour)
Draws one of the items in a popup menu.