The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1853 lines
61KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_PopupMenu.h"
  21. #include "../lookandfeel/juce_LookAndFeel.h"
  22. #include "../juce_ComponentDeletionWatcher.h"
  23. #include "../juce_Desktop.h"
  24. #include "../../graphics/imaging/juce_Image.h"
  25. #include "../keyboard/juce_KeyPressMappingSet.h"
  26. #include "../../../events/juce_Timer.h"
  27. #include "../../../threads/juce_Process.h"
  28. #include "../../../core/juce_Time.h"
  29. static VoidArray activeMenuWindows;
  30. //==============================================================================
  31. class MenuItemInfo
  32. {
  33. public:
  34. //==============================================================================
  35. const int itemId;
  36. String text;
  37. const Colour textColour;
  38. const bool active, isSeparator, isTicked, usesColour;
  39. ScopedPointer <Image> image;
  40. PopupMenuCustomComponent* const customComp;
  41. ScopedPointer <PopupMenu> subMenu;
  42. ApplicationCommandManager* const commandManager;
  43. //==============================================================================
  44. MenuItemInfo() throw()
  45. : itemId (0),
  46. active (true),
  47. isSeparator (true),
  48. isTicked (false),
  49. usesColour (false),
  50. customComp (0),
  51. commandManager (0)
  52. {
  53. }
  54. MenuItemInfo (const int itemId_,
  55. const String& text_,
  56. const bool active_,
  57. const bool isTicked_,
  58. const Image* im,
  59. const Colour& textColour_,
  60. const bool usesColour_,
  61. PopupMenuCustomComponent* const customComp_,
  62. const PopupMenu* const subMenu_,
  63. ApplicationCommandManager* const commandManager_) throw()
  64. : itemId (itemId_),
  65. text (text_),
  66. textColour (textColour_),
  67. active (active_),
  68. isSeparator (false),
  69. isTicked (isTicked_),
  70. usesColour (usesColour_),
  71. customComp (customComp_),
  72. commandManager (commandManager_)
  73. {
  74. if (subMenu_ != 0)
  75. subMenu = new PopupMenu (*subMenu_);
  76. if (customComp != 0)
  77. customComp->refCount_++;
  78. if (im != 0)
  79. image = im->createCopy();
  80. if (commandManager_ != 0 && itemId_ != 0)
  81. {
  82. String shortcutKey;
  83. Array <KeyPress> keyPresses (commandManager_->getKeyMappings()
  84. ->getKeyPressesAssignedToCommand (itemId_));
  85. for (int i = 0; i < keyPresses.size(); ++i)
  86. {
  87. const String key (keyPresses.getReference(i).getTextDescription());
  88. if (shortcutKey.isNotEmpty())
  89. shortcutKey << ", ";
  90. if (key.length() == 1)
  91. shortcutKey << "shortcut: '" << key << '\'';
  92. else
  93. shortcutKey << key;
  94. }
  95. shortcutKey = shortcutKey.trim();
  96. if (shortcutKey.isNotEmpty())
  97. text << "<end>" << shortcutKey;
  98. }
  99. }
  100. MenuItemInfo (const MenuItemInfo& other) throw()
  101. : itemId (other.itemId),
  102. text (other.text),
  103. textColour (other.textColour),
  104. active (other.active),
  105. isSeparator (other.isSeparator),
  106. isTicked (other.isTicked),
  107. usesColour (other.usesColour),
  108. customComp (other.customComp),
  109. commandManager (other.commandManager)
  110. {
  111. if (other.subMenu != 0)
  112. subMenu = new PopupMenu (*(other.subMenu));
  113. if (other.image != 0)
  114. image = other.image->createCopy();
  115. if (customComp != 0)
  116. customComp->refCount_++;
  117. }
  118. ~MenuItemInfo() throw()
  119. {
  120. if (customComp != 0 && --(customComp->refCount_) == 0)
  121. delete customComp;
  122. }
  123. bool canBeTriggered() const throw()
  124. {
  125. return active && ! (isSeparator || (subMenu != 0));
  126. }
  127. bool hasActiveSubMenu() const throw()
  128. {
  129. return active && (subMenu != 0);
  130. }
  131. juce_UseDebuggingNewOperator
  132. private:
  133. const MenuItemInfo& operator= (const MenuItemInfo&);
  134. };
  135. //==============================================================================
  136. class MenuItemComponent : public Component
  137. {
  138. bool isHighlighted;
  139. public:
  140. MenuItemInfo itemInfo;
  141. //==============================================================================
  142. MenuItemComponent (const MenuItemInfo& itemInfo_)
  143. : isHighlighted (false),
  144. itemInfo (itemInfo_)
  145. {
  146. if (itemInfo.customComp != 0)
  147. addAndMakeVisible (itemInfo.customComp);
  148. }
  149. ~MenuItemComponent()
  150. {
  151. if (itemInfo.customComp != 0)
  152. removeChildComponent (itemInfo.customComp);
  153. }
  154. void getIdealSize (int& idealWidth,
  155. int& idealHeight,
  156. const int standardItemHeight)
  157. {
  158. if (itemInfo.customComp != 0)
  159. {
  160. itemInfo.customComp->getIdealSize (idealWidth, idealHeight);
  161. }
  162. else
  163. {
  164. getLookAndFeel().getIdealPopupMenuItemSize (itemInfo.text,
  165. itemInfo.isSeparator,
  166. standardItemHeight,
  167. idealWidth,
  168. idealHeight);
  169. }
  170. }
  171. void paint (Graphics& g)
  172. {
  173. if (itemInfo.customComp == 0)
  174. {
  175. String mainText (itemInfo.text);
  176. String endText;
  177. const int endIndex = mainText.indexOf (T("<end>"));
  178. if (endIndex >= 0)
  179. {
  180. endText = mainText.substring (endIndex + 5).trim();
  181. mainText = mainText.substring (0, endIndex);
  182. }
  183. getLookAndFeel()
  184. .drawPopupMenuItem (g, getWidth(), getHeight(),
  185. itemInfo.isSeparator,
  186. itemInfo.active,
  187. isHighlighted,
  188. itemInfo.isTicked,
  189. itemInfo.subMenu != 0,
  190. mainText, endText,
  191. itemInfo.image,
  192. itemInfo.usesColour ? &(itemInfo.textColour) : 0);
  193. }
  194. }
  195. void resized()
  196. {
  197. if (getNumChildComponents() > 0)
  198. getChildComponent(0)->setBounds (2, 0, getWidth() - 4, getHeight());
  199. }
  200. void setHighlighted (bool shouldBeHighlighted)
  201. {
  202. shouldBeHighlighted = shouldBeHighlighted && itemInfo.active;
  203. if (isHighlighted != shouldBeHighlighted)
  204. {
  205. isHighlighted = shouldBeHighlighted;
  206. if (itemInfo.customComp != 0)
  207. {
  208. itemInfo.customComp->isHighlighted = shouldBeHighlighted;
  209. itemInfo.customComp->repaint();
  210. }
  211. repaint();
  212. }
  213. }
  214. private:
  215. MenuItemComponent (const MenuItemComponent&);
  216. const MenuItemComponent& operator= (const MenuItemComponent&);
  217. };
  218. //==============================================================================
  219. static const int scrollZone = 24;
  220. static const int borderSize = 2;
  221. static const int timerInterval = 50;
  222. static const int dismissCommandId = 0x6287345f;
  223. static bool wasHiddenBecauseOfAppChange = false;
  224. //==============================================================================
  225. class PopupMenuWindow : public Component,
  226. private Timer
  227. {
  228. public:
  229. //==============================================================================
  230. PopupMenuWindow() throw()
  231. : Component (T("menu")),
  232. owner (0),
  233. currentChild (0),
  234. activeSubMenu (0),
  235. menuBarComponent (0),
  236. managerOfChosenCommand (0),
  237. componentAttachedTo (0),
  238. lastMouseX (0),
  239. lastMouseY (0),
  240. minimumWidth (0),
  241. maximumNumColumns (7),
  242. standardItemHeight (0),
  243. isOver (false),
  244. hasBeenOver (false),
  245. isDown (false),
  246. needsToScroll (false),
  247. hideOnExit (false),
  248. disableMouseMoves (false),
  249. hasAnyJuceCompHadFocus (false),
  250. numColumns (0),
  251. contentHeight (0),
  252. childYOffset (0),
  253. timeEnteredCurrentChildComp (0),
  254. scrollAcceleration (1.0)
  255. {
  256. menuCreationTime = lastFocused = lastScroll = Time::getMillisecondCounter();
  257. setWantsKeyboardFocus (true);
  258. setMouseClickGrabsKeyboardFocus (false);
  259. setOpaque (true);
  260. setAlwaysOnTop (true);
  261. Desktop::getInstance().addGlobalMouseListener (this);
  262. activeMenuWindows.add (this);
  263. }
  264. ~PopupMenuWindow()
  265. {
  266. activeMenuWindows.removeValue (this);
  267. Desktop::getInstance().removeGlobalMouseListener (this);
  268. jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent());
  269. delete activeSubMenu;
  270. deleteAllChildren();
  271. attachedCompWatcher = 0;
  272. }
  273. //==============================================================================
  274. static PopupMenuWindow* create (const PopupMenu& menu,
  275. const bool dismissOnMouseUp,
  276. PopupMenuWindow* const owner_,
  277. const int minX, const int maxX,
  278. const int minY, const int maxY,
  279. const int minimumWidth,
  280. const int maximumNumColumns,
  281. const int standardItemHeight,
  282. const bool alignToRectangle,
  283. const int itemIdThatMustBeVisible,
  284. Component* const menuBarComponent,
  285. ApplicationCommandManager** managerOfChosenCommand,
  286. Component* const componentAttachedTo) throw()
  287. {
  288. if (menu.items.size() > 0)
  289. {
  290. int totalItems = 0;
  291. ScopedPointer <PopupMenuWindow> mw (new PopupMenuWindow());
  292. mw->setLookAndFeel (menu.lookAndFeel);
  293. mw->setWantsKeyboardFocus (false);
  294. mw->minimumWidth = minimumWidth;
  295. mw->maximumNumColumns = maximumNumColumns;
  296. mw->standardItemHeight = standardItemHeight;
  297. mw->dismissOnMouseUp = dismissOnMouseUp;
  298. for (int i = 0; i < menu.items.size(); ++i)
  299. {
  300. MenuItemInfo* const item = (MenuItemInfo*) menu.items.getUnchecked(i);
  301. mw->addItem (*item);
  302. ++totalItems;
  303. }
  304. if (totalItems > 0)
  305. {
  306. mw->owner = owner_;
  307. mw->menuBarComponent = menuBarComponent;
  308. mw->managerOfChosenCommand = managerOfChosenCommand;
  309. mw->componentAttachedTo = componentAttachedTo;
  310. mw->attachedCompWatcher = componentAttachedTo != 0 ? new ComponentDeletionWatcher (componentAttachedTo) : 0;
  311. mw->calculateWindowPos (minX, maxX, minY, maxY, alignToRectangle);
  312. mw->setTopLeftPosition (mw->windowPos.getX(),
  313. mw->windowPos.getY());
  314. mw->updateYPositions();
  315. if (itemIdThatMustBeVisible != 0)
  316. {
  317. const int y = minY - mw->windowPos.getY();
  318. mw->ensureItemIsVisible (itemIdThatMustBeVisible,
  319. (((unsigned int) y) < (unsigned int) mw->windowPos.getHeight()) ? y : -1);
  320. }
  321. mw->resizeToBestWindowPos();
  322. mw->addToDesktop (ComponentPeer::windowIsTemporary
  323. | mw->getLookAndFeel().getMenuWindowFlags());
  324. return mw.release();
  325. }
  326. }
  327. return 0;
  328. }
  329. //==============================================================================
  330. void paint (Graphics& g)
  331. {
  332. getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight());
  333. }
  334. void paintOverChildren (Graphics& g)
  335. {
  336. if (isScrolling())
  337. {
  338. LookAndFeel& lf = getLookAndFeel();
  339. if (isScrollZoneActive (false))
  340. lf.drawPopupMenuUpDownArrow (g, getWidth(), scrollZone, true);
  341. if (isScrollZoneActive (true))
  342. {
  343. g.setOrigin (0, getHeight() - scrollZone);
  344. lf.drawPopupMenuUpDownArrow (g, getWidth(), scrollZone, false);
  345. }
  346. }
  347. }
  348. bool isScrollZoneActive (bool bottomOne) const
  349. {
  350. return isScrolling()
  351. && (bottomOne
  352. ? childYOffset < contentHeight - windowPos.getHeight()
  353. : childYOffset > 0);
  354. }
  355. //==============================================================================
  356. void addItem (const MenuItemInfo& item) throw()
  357. {
  358. MenuItemComponent* const mic = new MenuItemComponent (item);
  359. addAndMakeVisible (mic);
  360. int itemW = 80;
  361. int itemH = 16;
  362. mic->getIdealSize (itemW, itemH, standardItemHeight);
  363. mic->setSize (itemW, jlimit (2, 600, itemH));
  364. mic->addMouseListener (this, false);
  365. }
  366. //==============================================================================
  367. // hide this and all sub-comps
  368. void hide (const MenuItemInfo* const item) throw()
  369. {
  370. if (isVisible())
  371. {
  372. jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent());
  373. deleteAndZero (activeSubMenu);
  374. currentChild = 0;
  375. exitModalState (item != 0 ? item->itemId : 0);
  376. setVisible (false);
  377. if (item != 0
  378. && item->commandManager != 0
  379. && item->itemId != 0)
  380. {
  381. *managerOfChosenCommand = item->commandManager;
  382. }
  383. }
  384. }
  385. void dismissMenu (const MenuItemInfo* const item) throw()
  386. {
  387. if (owner != 0)
  388. {
  389. owner->dismissMenu (item);
  390. }
  391. else
  392. {
  393. if (item != 0)
  394. {
  395. // need a copy of this on the stack as the one passed in will get deleted during this call
  396. const MenuItemInfo mi (*item);
  397. hide (&mi);
  398. }
  399. else
  400. {
  401. hide (0);
  402. }
  403. }
  404. }
  405. //==============================================================================
  406. void mouseMove (const MouseEvent&)
  407. {
  408. timerCallback();
  409. }
  410. void mouseDown (const MouseEvent&)
  411. {
  412. timerCallback();
  413. }
  414. void mouseDrag (const MouseEvent&)
  415. {
  416. timerCallback();
  417. }
  418. void mouseUp (const MouseEvent&)
  419. {
  420. timerCallback();
  421. }
  422. void mouseWheelMove (const MouseEvent&, float /*amountX*/, float amountY)
  423. {
  424. alterChildYPos (roundFloatToInt (-10.0f * amountY * scrollZone));
  425. lastMouseX = -1;
  426. }
  427. bool keyPressed (const KeyPress& key)
  428. {
  429. if (key.isKeyCode (KeyPress::downKey))
  430. {
  431. selectNextItem (1);
  432. }
  433. else if (key.isKeyCode (KeyPress::upKey))
  434. {
  435. selectNextItem (-1);
  436. }
  437. else if (key.isKeyCode (KeyPress::leftKey))
  438. {
  439. PopupMenuWindow* parentWindow = owner;
  440. if (parentWindow != 0)
  441. {
  442. MenuItemComponent* currentChildOfParent
  443. = (parentWindow != 0) ? parentWindow->currentChild : 0;
  444. hide (0);
  445. if (parentWindow->isValidComponent())
  446. parentWindow->setCurrentlyHighlightedChild (currentChildOfParent);
  447. disableTimerUntilMouseMoves();
  448. }
  449. else if (menuBarComponent != 0)
  450. {
  451. menuBarComponent->keyPressed (key);
  452. }
  453. }
  454. else if (key.isKeyCode (KeyPress::rightKey))
  455. {
  456. disableTimerUntilMouseMoves();
  457. if (showSubMenuFor (currentChild))
  458. {
  459. jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent());
  460. if (activeSubMenu != 0 && activeSubMenu->isVisible())
  461. activeSubMenu->selectNextItem (1);
  462. }
  463. else if (menuBarComponent != 0)
  464. {
  465. menuBarComponent->keyPressed (key);
  466. }
  467. }
  468. else if (key.isKeyCode (KeyPress::returnKey))
  469. {
  470. triggerCurrentlyHighlightedItem();
  471. }
  472. else if (key.isKeyCode (KeyPress::escapeKey))
  473. {
  474. dismissMenu (0);
  475. }
  476. else
  477. {
  478. return false;
  479. }
  480. return true;
  481. }
  482. void inputAttemptWhenModal()
  483. {
  484. timerCallback();
  485. if (! isOverAnyMenu())
  486. {
  487. if (componentAttachedTo != 0 && ! attachedCompWatcher->hasBeenDeleted())
  488. {
  489. // we want to dismiss the menu, but if we do it synchronously, then
  490. // the mouse-click will be allowed to pass through. That's good, except
  491. // when the user clicks on the button that orginally popped the menu up,
  492. // as they'll expect the menu to go away, and in fact it'll just
  493. // come back. So only dismiss synchronously if they're not on the original
  494. // comp that we're attached to.
  495. int mx, my;
  496. componentAttachedTo->getMouseXYRelative (mx, my);
  497. if (componentAttachedTo->reallyContains (mx, my, true))
  498. {
  499. postCommandMessage (dismissCommandId); // dismiss asynchrounously
  500. return;
  501. }
  502. }
  503. dismissMenu (0);
  504. }
  505. }
  506. void handleCommandMessage (int commandId)
  507. {
  508. Component::handleCommandMessage (commandId);
  509. if (commandId == dismissCommandId)
  510. dismissMenu (0);
  511. }
  512. //==============================================================================
  513. void timerCallback()
  514. {
  515. if (! isVisible())
  516. return;
  517. if (attachedCompWatcher != 0 && attachedCompWatcher->hasBeenDeleted())
  518. {
  519. dismissMenu (0);
  520. return;
  521. }
  522. PopupMenuWindow* currentlyModalWindow = dynamic_cast <PopupMenuWindow*> (Component::getCurrentlyModalComponent());
  523. if (currentlyModalWindow != 0 && ! treeContains (currentlyModalWindow))
  524. return;
  525. startTimer (timerInterval); // do this in case it was called from a mouse
  526. // move rather than a real timer callback
  527. int mx, my;
  528. Desktop::getMousePosition (mx, my);
  529. int x = mx, y = my;
  530. globalPositionToRelative (x, y);
  531. const uint32 now = Time::getMillisecondCounter();
  532. if (now > timeEnteredCurrentChildComp + 100
  533. && reallyContains (x, y, true)
  534. && currentChild->isValidComponent()
  535. && (! disableMouseMoves)
  536. && ! (activeSubMenu != 0 && activeSubMenu->isVisible()))
  537. {
  538. showSubMenuFor (currentChild);
  539. }
  540. if (mx != lastMouseX || my != lastMouseY || now > lastMouseMoveTime + 350)
  541. {
  542. highlightItemUnderMouse (mx, my, x, y);
  543. }
  544. bool overScrollArea = false;
  545. if (isScrolling()
  546. && (isOver || (isDown && ((unsigned int) x) < (unsigned int) getWidth()))
  547. && ((isScrollZoneActive (false) && y < scrollZone)
  548. || (isScrollZoneActive (true) && y > getHeight() - scrollZone)))
  549. {
  550. if (now > lastScroll + 20)
  551. {
  552. scrollAcceleration = jmin (4.0, scrollAcceleration * 1.04);
  553. int amount = 0;
  554. for (int i = 0; i < getNumChildComponents() && amount == 0; ++i)
  555. amount = ((int) scrollAcceleration) * getChildComponent (i)->getHeight();
  556. alterChildYPos (y < scrollZone ? -amount : amount);
  557. lastScroll = now;
  558. }
  559. overScrollArea = true;
  560. lastMouseX = -1; // trigger a mouse-move
  561. }
  562. else
  563. {
  564. scrollAcceleration = 1.0;
  565. }
  566. const bool wasDown = isDown;
  567. bool isOverAny = isOverAnyMenu();
  568. if (hideOnExit && hasBeenOver && (! isOverAny) && activeSubMenu != 0)
  569. {
  570. activeSubMenu->updateMouseOverStatus (mx, my);
  571. isOverAny = isOverAnyMenu();
  572. }
  573. if (hideOnExit && hasBeenOver && ! isOverAny)
  574. {
  575. hide (0);
  576. }
  577. else
  578. {
  579. isDown = hasBeenOver
  580. && (ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()
  581. || ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown());
  582. bool anyFocused = Process::isForegroundProcess();
  583. if (anyFocused && Component::getCurrentlyFocusedComponent() == 0)
  584. {
  585. // because no component at all may have focus, our test here will
  586. // only be triggered when something has focus and then loses it.
  587. anyFocused = ! hasAnyJuceCompHadFocus;
  588. for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
  589. {
  590. if (ComponentPeer::getPeer (i)->isFocused())
  591. {
  592. anyFocused = true;
  593. hasAnyJuceCompHadFocus = true;
  594. break;
  595. }
  596. }
  597. }
  598. if (! anyFocused)
  599. {
  600. if (now > lastFocused + 10)
  601. {
  602. wasHiddenBecauseOfAppChange = true;
  603. dismissMenu (0);
  604. return; // may have been deleted by the previous call..
  605. }
  606. }
  607. else if (wasDown && now > menuCreationTime + 250
  608. && ! (isDown || overScrollArea))
  609. {
  610. isOver = reallyContains (x, y, true);
  611. if (isOver)
  612. {
  613. triggerCurrentlyHighlightedItem();
  614. }
  615. else if ((hasBeenOver || ! dismissOnMouseUp) && ! isOverAny)
  616. {
  617. dismissMenu (0);
  618. }
  619. return; // may have been deleted by the previous calls..
  620. }
  621. else
  622. {
  623. lastFocused = now;
  624. }
  625. }
  626. }
  627. //==============================================================================
  628. juce_UseDebuggingNewOperator
  629. private:
  630. PopupMenuWindow* owner;
  631. MenuItemComponent* currentChild;
  632. PopupMenuWindow* activeSubMenu;
  633. Component* menuBarComponent;
  634. ApplicationCommandManager** managerOfChosenCommand;
  635. Component* componentAttachedTo;
  636. ScopedPointer <ComponentDeletionWatcher> attachedCompWatcher;
  637. Rectangle windowPos;
  638. int lastMouseX, lastMouseY;
  639. int minimumWidth, maximumNumColumns, standardItemHeight;
  640. bool isOver, hasBeenOver, isDown, needsToScroll;
  641. bool dismissOnMouseUp, hideOnExit, disableMouseMoves, hasAnyJuceCompHadFocus;
  642. int numColumns, contentHeight, childYOffset;
  643. Array <int> columnWidths;
  644. uint32 menuCreationTime, lastFocused, lastScroll, lastMouseMoveTime, timeEnteredCurrentChildComp;
  645. double scrollAcceleration;
  646. //==============================================================================
  647. bool overlaps (const Rectangle& r) const throw()
  648. {
  649. return r.intersects (getBounds())
  650. || (owner != 0 && owner->overlaps (r));
  651. }
  652. bool isOverAnyMenu() const throw()
  653. {
  654. return (owner != 0) ? owner->isOverAnyMenu()
  655. : isOverChildren();
  656. }
  657. bool isOverChildren() const throw()
  658. {
  659. jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent());
  660. return isVisible()
  661. && (isOver || (activeSubMenu != 0 && activeSubMenu->isOverChildren()));
  662. }
  663. void updateMouseOverStatus (const int mx, const int my) throw()
  664. {
  665. int rx = mx, ry = my;
  666. globalPositionToRelative (rx, ry);
  667. isOver = reallyContains (rx, ry, true);
  668. if (activeSubMenu != 0)
  669. activeSubMenu->updateMouseOverStatus (mx, my);
  670. }
  671. bool treeContains (const PopupMenuWindow* const window) const throw()
  672. {
  673. const PopupMenuWindow* mw = this;
  674. while (mw->owner != 0)
  675. mw = mw->owner;
  676. while (mw != 0)
  677. {
  678. if (mw == window)
  679. return true;
  680. mw = mw->activeSubMenu;
  681. }
  682. return false;
  683. }
  684. //==============================================================================
  685. void calculateWindowPos (const int minX, const int maxX,
  686. const int minY, const int maxY,
  687. const bool alignToRectangle)
  688. {
  689. const Rectangle mon (Desktop::getInstance()
  690. .getMonitorAreaContaining ((minX + maxX) / 2,
  691. (minY + maxY) / 2,
  692. #if JUCE_MAC
  693. true));
  694. #else
  695. false)); // on windows, don't stop the menu overlapping the taskbar
  696. #endif
  697. int x, y, widthToUse, heightToUse;
  698. layoutMenuItems (mon.getWidth() - 24, widthToUse, heightToUse);
  699. if (alignToRectangle)
  700. {
  701. x = minX;
  702. const int spaceUnder = mon.getHeight() - (maxY - mon.getY());
  703. const int spaceOver = minY - mon.getY();
  704. if (heightToUse < spaceUnder - 30 || spaceUnder >= spaceOver)
  705. y = maxY;
  706. else
  707. y = minY - heightToUse;
  708. }
  709. else
  710. {
  711. bool tendTowardsRight = (minX + maxX) / 2 < mon.getCentreX();
  712. if (owner != 0)
  713. {
  714. if (owner->owner != 0)
  715. {
  716. const bool ownerGoingRight = (owner->getX() + owner->getWidth() / 2
  717. > owner->owner->getX() + owner->owner->getWidth() / 2);
  718. if (ownerGoingRight && maxX + widthToUse < mon.getRight() - 4)
  719. tendTowardsRight = true;
  720. else if ((! ownerGoingRight) && minX > widthToUse + 4)
  721. tendTowardsRight = false;
  722. }
  723. else if (maxX + widthToUse < mon.getRight() - 32)
  724. {
  725. tendTowardsRight = true;
  726. }
  727. }
  728. const int biggestSpace = jmax (mon.getRight() - maxX,
  729. minX - mon.getX()) - 32;
  730. if (biggestSpace < widthToUse)
  731. {
  732. layoutMenuItems (biggestSpace + (maxX - minX) / 3, widthToUse, heightToUse);
  733. if (numColumns > 1)
  734. layoutMenuItems (biggestSpace - 4, widthToUse, heightToUse);
  735. tendTowardsRight = (mon.getRight() - maxX) >= (minX - mon.getX());
  736. }
  737. if (tendTowardsRight)
  738. x = jmin (mon.getRight() - widthToUse - 4, maxX);
  739. else
  740. x = jmax (mon.getX() + 4, minX - widthToUse);
  741. y = minY;
  742. if ((minY + maxY) / 2 > mon.getCentreY())
  743. y = jmax (mon.getY(), maxY - heightToUse);
  744. }
  745. x = jmax (mon.getX() + 1, jmin (mon.getRight() - (widthToUse + 6), x));
  746. y = jmax (mon.getY() + 1, jmin (mon.getBottom() - (heightToUse + 6), y));
  747. windowPos.setBounds (x, y, widthToUse, heightToUse);
  748. // sets this flag if it's big enough to obscure any of its parent menus
  749. hideOnExit = (owner != 0)
  750. && owner->windowPos.intersects (windowPos.expanded (-4, -4));
  751. }
  752. void layoutMenuItems (const int maxMenuW, int& width, int& height)
  753. {
  754. numColumns = 0;
  755. contentHeight = 0;
  756. const int maxMenuH = getParentHeight() - 24;
  757. int totalW;
  758. do
  759. {
  760. ++numColumns;
  761. totalW = workOutBestSize (maxMenuW);
  762. if (totalW > maxMenuW)
  763. {
  764. numColumns = jmax (1, numColumns - 1);
  765. totalW = workOutBestSize (maxMenuW); // to update col widths
  766. break;
  767. }
  768. else if (totalW > maxMenuW / 2 || contentHeight < maxMenuH)
  769. {
  770. break;
  771. }
  772. } while (numColumns < maximumNumColumns);
  773. const int actualH = jmin (contentHeight, maxMenuH);
  774. needsToScroll = contentHeight > actualH;
  775. width = updateYPositions();
  776. height = actualH + borderSize * 2;
  777. }
  778. int workOutBestSize (const int maxMenuW)
  779. {
  780. int totalW = 0;
  781. contentHeight = 0;
  782. int childNum = 0;
  783. for (int col = 0; col < numColumns; ++col)
  784. {
  785. int i, colW = 50, colH = 0;
  786. const int numChildren = jmin (getNumChildComponents() - childNum,
  787. (getNumChildComponents() + numColumns - 1) / numColumns);
  788. for (i = numChildren; --i >= 0;)
  789. {
  790. colW = jmax (colW, getChildComponent (childNum + i)->getWidth());
  791. colH += getChildComponent (childNum + i)->getHeight();
  792. }
  793. colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + borderSize * 2);
  794. columnWidths.set (col, colW);
  795. totalW += colW;
  796. contentHeight = jmax (contentHeight, colH);
  797. childNum += numChildren;
  798. }
  799. if (totalW < minimumWidth)
  800. {
  801. totalW = minimumWidth;
  802. for (int col = 0; col < numColumns; ++col)
  803. columnWidths.set (0, totalW / numColumns);
  804. }
  805. return totalW;
  806. }
  807. void ensureItemIsVisible (const int itemId, int wantedY)
  808. {
  809. jassert (itemId != 0)
  810. for (int i = getNumChildComponents(); --i >= 0;)
  811. {
  812. MenuItemComponent* const m = (MenuItemComponent*) getChildComponent (i);
  813. if (m != 0
  814. && m->itemInfo.itemId == itemId
  815. && windowPos.getHeight() > scrollZone * 4)
  816. {
  817. const int currentY = m->getY();
  818. if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight())
  819. {
  820. if (wantedY < 0)
  821. wantedY = jlimit (scrollZone,
  822. jmax (scrollZone, windowPos.getHeight() - (scrollZone + m->getHeight())),
  823. currentY);
  824. const Rectangle mon (Desktop::getInstance()
  825. .getMonitorAreaContaining (windowPos.getX(),
  826. windowPos.getY(),
  827. true));
  828. int deltaY = wantedY - currentY;
  829. const int newY = jlimit (mon.getY(),
  830. mon.getBottom() - windowPos.getHeight(),
  831. windowPos.getY() + deltaY);
  832. deltaY -= newY - windowPos.getY();
  833. childYOffset -= deltaY;
  834. windowPos.setPosition (windowPos.getX(), newY);
  835. updateYPositions();
  836. }
  837. break;
  838. }
  839. }
  840. }
  841. void resizeToBestWindowPos()
  842. {
  843. Rectangle r (windowPos);
  844. if (childYOffset < 0)
  845. {
  846. r.setBounds (r.getX(), r.getY() - childYOffset,
  847. r.getWidth(), r.getHeight() + childYOffset);
  848. }
  849. else if (childYOffset > 0)
  850. {
  851. const int spaceAtBottom = r.getHeight() - (contentHeight - childYOffset);
  852. if (spaceAtBottom > 0)
  853. r.setSize (r.getWidth(), r.getHeight() - spaceAtBottom);
  854. }
  855. setBounds (r);
  856. updateYPositions();
  857. }
  858. void alterChildYPos (const int delta)
  859. {
  860. if (isScrolling())
  861. {
  862. childYOffset += delta;
  863. if (delta < 0)
  864. {
  865. childYOffset = jmax (childYOffset, 0);
  866. }
  867. else if (delta > 0)
  868. {
  869. childYOffset = jmin (childYOffset,
  870. contentHeight - windowPos.getHeight() + borderSize);
  871. }
  872. updateYPositions();
  873. }
  874. else
  875. {
  876. childYOffset = 0;
  877. }
  878. resizeToBestWindowPos();
  879. repaint();
  880. }
  881. int updateYPositions()
  882. {
  883. int x = 0;
  884. int childNum = 0;
  885. for (int col = 0; col < numColumns; ++col)
  886. {
  887. const int numChildren = jmin (getNumChildComponents() - childNum,
  888. (getNumChildComponents() + numColumns - 1) / numColumns);
  889. const int colW = columnWidths [col];
  890. int y = borderSize - (childYOffset + (getY() - windowPos.getY()));
  891. for (int i = 0; i < numChildren; ++i)
  892. {
  893. Component* const c = getChildComponent (childNum + i);
  894. c->setBounds (x, y, colW, c->getHeight());
  895. y += c->getHeight();
  896. }
  897. x += colW;
  898. childNum += numChildren;
  899. }
  900. return x;
  901. }
  902. bool isScrolling() const throw()
  903. {
  904. return childYOffset != 0 || needsToScroll;
  905. }
  906. void setCurrentlyHighlightedChild (MenuItemComponent* const child) throw()
  907. {
  908. if (currentChild->isValidComponent())
  909. currentChild->setHighlighted (false);
  910. currentChild = child;
  911. if (currentChild != 0)
  912. {
  913. currentChild->setHighlighted (true);
  914. timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter();
  915. }
  916. }
  917. bool showSubMenuFor (MenuItemComponent* const childComp)
  918. {
  919. jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent());
  920. deleteAndZero (activeSubMenu);
  921. if (childComp->isValidComponent() && childComp->itemInfo.hasActiveSubMenu())
  922. {
  923. int left = 0, top = 0;
  924. childComp->relativePositionToGlobal (left, top);
  925. int right = childComp->getWidth(), bottom = childComp->getHeight();
  926. childComp->relativePositionToGlobal (right, bottom);
  927. activeSubMenu = PopupMenuWindow::create (*(childComp->itemInfo.subMenu),
  928. dismissOnMouseUp,
  929. this,
  930. left, right, top, bottom,
  931. 0, maximumNumColumns,
  932. standardItemHeight,
  933. false, 0, menuBarComponent,
  934. managerOfChosenCommand,
  935. componentAttachedTo);
  936. if (activeSubMenu != 0)
  937. {
  938. activeSubMenu->setVisible (true);
  939. activeSubMenu->enterModalState (false);
  940. activeSubMenu->toFront (false);
  941. return true;
  942. }
  943. }
  944. return false;
  945. }
  946. void highlightItemUnderMouse (const int mx, const int my, const int x, const int y)
  947. {
  948. isOver = reallyContains (x, y, true);
  949. if (isOver)
  950. hasBeenOver = true;
  951. if (abs (lastMouseX - mx) > 2 || abs (lastMouseY - my) > 2)
  952. {
  953. lastMouseMoveTime = Time::getApproximateMillisecondCounter();
  954. if (disableMouseMoves && isOver)
  955. disableMouseMoves = false;
  956. }
  957. if (disableMouseMoves)
  958. return;
  959. bool isMovingTowardsMenu = false;
  960. jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent())
  961. if (isOver && (activeSubMenu != 0) && (mx != lastMouseX || my != lastMouseY))
  962. {
  963. // try to intelligently guess whether the user is moving the mouse towards a currently-open
  964. // submenu. To do this, look at whether the mouse stays inside a triangular region that
  965. // extends from the last mouse pos to the submenu's rectangle..
  966. float subX = (float) activeSubMenu->getScreenX();
  967. if (activeSubMenu->getX() > getX())
  968. {
  969. lastMouseX -= 2; // to enlarge the triangle a bit, in case the mouse only moves a couple of pixels
  970. }
  971. else
  972. {
  973. lastMouseX += 2;
  974. subX += activeSubMenu->getWidth();
  975. }
  976. Path areaTowardsSubMenu;
  977. areaTowardsSubMenu.addTriangle ((float) lastMouseX,
  978. (float) lastMouseY,
  979. subX,
  980. (float) activeSubMenu->getScreenY(),
  981. subX,
  982. (float) (activeSubMenu->getScreenY() + activeSubMenu->getHeight()));
  983. isMovingTowardsMenu = areaTowardsSubMenu.contains ((float) mx, (float) my);
  984. }
  985. lastMouseX = mx;
  986. lastMouseY = my;
  987. if (! isMovingTowardsMenu)
  988. {
  989. Component* c = getComponentAt (x, y);
  990. if (c == this)
  991. c = 0;
  992. MenuItemComponent* mic = dynamic_cast <MenuItemComponent*> (c);
  993. if (mic == 0 && c != 0)
  994. mic = c->findParentComponentOfClass ((MenuItemComponent*) 0);
  995. if (mic != currentChild
  996. && (isOver || (activeSubMenu == 0) || ! activeSubMenu->isVisible()))
  997. {
  998. if (isOver && (c != 0) && (activeSubMenu != 0))
  999. {
  1000. activeSubMenu->hide (0);
  1001. }
  1002. if (! isOver)
  1003. mic = 0;
  1004. setCurrentlyHighlightedChild (mic);
  1005. }
  1006. }
  1007. }
  1008. void triggerCurrentlyHighlightedItem()
  1009. {
  1010. if (currentChild->isValidComponent()
  1011. && currentChild->itemInfo.canBeTriggered()
  1012. && (currentChild->itemInfo.customComp == 0
  1013. || currentChild->itemInfo.customComp->isTriggeredAutomatically))
  1014. {
  1015. dismissMenu (&currentChild->itemInfo);
  1016. }
  1017. }
  1018. void selectNextItem (const int delta)
  1019. {
  1020. disableTimerUntilMouseMoves();
  1021. MenuItemComponent* mic = 0;
  1022. bool wasLastOne = (currentChild == 0);
  1023. const int numItems = getNumChildComponents();
  1024. for (int i = 0; i < numItems + 1; ++i)
  1025. {
  1026. int index = (delta > 0) ? i : (numItems - 1 - i);
  1027. index = (index + numItems) % numItems;
  1028. mic = dynamic_cast <MenuItemComponent*> (getChildComponent (index));
  1029. if (mic != 0 && (mic->itemInfo.canBeTriggered() || mic->itemInfo.hasActiveSubMenu())
  1030. && wasLastOne)
  1031. break;
  1032. if (mic == currentChild)
  1033. wasLastOne = true;
  1034. }
  1035. setCurrentlyHighlightedChild (mic);
  1036. }
  1037. void disableTimerUntilMouseMoves() throw()
  1038. {
  1039. disableMouseMoves = true;
  1040. if (owner != 0)
  1041. owner->disableTimerUntilMouseMoves();
  1042. }
  1043. PopupMenuWindow (const PopupMenuWindow&);
  1044. const PopupMenuWindow& operator= (const PopupMenuWindow&);
  1045. };
  1046. //==============================================================================
  1047. PopupMenu::PopupMenu() throw()
  1048. : items (8),
  1049. lookAndFeel (0),
  1050. separatorPending (false)
  1051. {
  1052. }
  1053. PopupMenu::PopupMenu (const PopupMenu& other) throw()
  1054. : items (8),
  1055. lookAndFeel (other.lookAndFeel),
  1056. separatorPending (false)
  1057. {
  1058. items.ensureStorageAllocated (other.items.size());
  1059. for (int i = 0; i < other.items.size(); ++i)
  1060. items.add (new MenuItemInfo (*(const MenuItemInfo*) other.items.getUnchecked(i)));
  1061. }
  1062. const PopupMenu& PopupMenu::operator= (const PopupMenu& other) throw()
  1063. {
  1064. if (this != &other)
  1065. {
  1066. lookAndFeel = other.lookAndFeel;
  1067. clear();
  1068. items.ensureStorageAllocated (other.items.size());
  1069. for (int i = 0; i < other.items.size(); ++i)
  1070. items.add (new MenuItemInfo (*(const MenuItemInfo*) other.items.getUnchecked(i)));
  1071. }
  1072. return *this;
  1073. }
  1074. PopupMenu::~PopupMenu() throw()
  1075. {
  1076. clear();
  1077. }
  1078. void PopupMenu::clear() throw()
  1079. {
  1080. for (int i = items.size(); --i >= 0;)
  1081. delete (MenuItemInfo*) items.getUnchecked(i);
  1082. items.clear();
  1083. separatorPending = false;
  1084. }
  1085. void PopupMenu::addSeparatorIfPending()
  1086. {
  1087. if (separatorPending)
  1088. {
  1089. separatorPending = false;
  1090. if (items.size() > 0)
  1091. items.add (new MenuItemInfo());
  1092. }
  1093. }
  1094. void PopupMenu::addItem (const int itemResultId,
  1095. const String& itemText,
  1096. const bool isActive,
  1097. const bool isTicked,
  1098. const Image* const iconToUse) throw()
  1099. {
  1100. jassert (itemResultId != 0); // 0 is used as a return value to indicate that the user
  1101. // didn't pick anything, so you shouldn't use it as the id
  1102. // for an item..
  1103. addSeparatorIfPending();
  1104. items.add (new MenuItemInfo (itemResultId,
  1105. itemText,
  1106. isActive,
  1107. isTicked,
  1108. iconToUse,
  1109. Colours::black,
  1110. false,
  1111. 0, 0, 0));
  1112. }
  1113. void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager,
  1114. const int commandID,
  1115. const String& displayName) throw()
  1116. {
  1117. jassert (commandManager != 0 && commandID != 0);
  1118. const ApplicationCommandInfo* const registeredInfo = commandManager->getCommandForID (commandID);
  1119. if (registeredInfo != 0)
  1120. {
  1121. ApplicationCommandInfo info (*registeredInfo);
  1122. ApplicationCommandTarget* const target = commandManager->getTargetForCommand (commandID, info);
  1123. addSeparatorIfPending();
  1124. items.add (new MenuItemInfo (commandID,
  1125. displayName.isNotEmpty() ? displayName
  1126. : info.shortName,
  1127. target != 0 && (info.flags & ApplicationCommandInfo::isDisabled) == 0,
  1128. (info.flags & ApplicationCommandInfo::isTicked) != 0,
  1129. 0,
  1130. Colours::black,
  1131. false,
  1132. 0, 0,
  1133. commandManager));
  1134. }
  1135. }
  1136. void PopupMenu::addColouredItem (const int itemResultId,
  1137. const String& itemText,
  1138. const Colour& itemTextColour,
  1139. const bool isActive,
  1140. const bool isTicked,
  1141. const Image* const iconToUse) throw()
  1142. {
  1143. jassert (itemResultId != 0); // 0 is used as a return value to indicate that the user
  1144. // didn't pick anything, so you shouldn't use it as the id
  1145. // for an item..
  1146. addSeparatorIfPending();
  1147. items.add (new MenuItemInfo (itemResultId,
  1148. itemText,
  1149. isActive,
  1150. isTicked,
  1151. iconToUse,
  1152. itemTextColour,
  1153. true,
  1154. 0, 0, 0));
  1155. }
  1156. //==============================================================================
  1157. void PopupMenu::addCustomItem (const int itemResultId,
  1158. PopupMenuCustomComponent* const customComponent) throw()
  1159. {
  1160. jassert (itemResultId != 0); // 0 is used as a return value to indicate that the user
  1161. // didn't pick anything, so you shouldn't use it as the id
  1162. // for an item..
  1163. addSeparatorIfPending();
  1164. items.add (new MenuItemInfo (itemResultId,
  1165. String::empty,
  1166. true,
  1167. false,
  1168. 0,
  1169. Colours::black,
  1170. false,
  1171. customComponent,
  1172. 0, 0));
  1173. }
  1174. class NormalComponentWrapper : public PopupMenuCustomComponent
  1175. {
  1176. public:
  1177. NormalComponentWrapper (Component* const comp,
  1178. const int w, const int h,
  1179. const bool triggerMenuItemAutomaticallyWhenClicked)
  1180. : PopupMenuCustomComponent (triggerMenuItemAutomaticallyWhenClicked),
  1181. width (w),
  1182. height (h)
  1183. {
  1184. addAndMakeVisible (comp);
  1185. }
  1186. ~NormalComponentWrapper() {}
  1187. void getIdealSize (int& idealWidth, int& idealHeight)
  1188. {
  1189. idealWidth = width;
  1190. idealHeight = height;
  1191. }
  1192. void resized()
  1193. {
  1194. if (getChildComponent(0) != 0)
  1195. getChildComponent(0)->setBounds (0, 0, getWidth(), getHeight());
  1196. }
  1197. juce_UseDebuggingNewOperator
  1198. private:
  1199. const int width, height;
  1200. NormalComponentWrapper (const NormalComponentWrapper&);
  1201. const NormalComponentWrapper& operator= (const NormalComponentWrapper&);
  1202. };
  1203. void PopupMenu::addCustomItem (const int itemResultId,
  1204. Component* customComponent,
  1205. int idealWidth, int idealHeight,
  1206. const bool triggerMenuItemAutomaticallyWhenClicked) throw()
  1207. {
  1208. addCustomItem (itemResultId,
  1209. new NormalComponentWrapper (customComponent,
  1210. idealWidth, idealHeight,
  1211. triggerMenuItemAutomaticallyWhenClicked));
  1212. }
  1213. //==============================================================================
  1214. void PopupMenu::addSubMenu (const String& subMenuName,
  1215. const PopupMenu& subMenu,
  1216. const bool isActive,
  1217. Image* const iconToUse,
  1218. const bool isTicked) throw()
  1219. {
  1220. addSeparatorIfPending();
  1221. items.add (new MenuItemInfo (0,
  1222. subMenuName,
  1223. isActive && (subMenu.getNumItems() > 0),
  1224. isTicked,
  1225. iconToUse,
  1226. Colours::black,
  1227. false,
  1228. 0,
  1229. &subMenu,
  1230. 0));
  1231. }
  1232. void PopupMenu::addSeparator() throw()
  1233. {
  1234. separatorPending = true;
  1235. }
  1236. //==============================================================================
  1237. class HeaderItemComponent : public PopupMenuCustomComponent
  1238. {
  1239. public:
  1240. HeaderItemComponent (const String& name)
  1241. : PopupMenuCustomComponent (false)
  1242. {
  1243. setName (name);
  1244. }
  1245. ~HeaderItemComponent()
  1246. {
  1247. }
  1248. void paint (Graphics& g)
  1249. {
  1250. Font f (getLookAndFeel().getPopupMenuFont());
  1251. f.setBold (true);
  1252. g.setFont (f);
  1253. g.setColour (findColour (PopupMenu::headerTextColourId));
  1254. g.drawFittedText (getName(),
  1255. 12, 0, getWidth() - 16, proportionOfHeight (0.8f),
  1256. Justification::bottomLeft, 1);
  1257. }
  1258. void getIdealSize (int& idealWidth,
  1259. int& idealHeight)
  1260. {
  1261. getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight);
  1262. idealHeight += idealHeight / 2;
  1263. idealWidth += idealWidth / 4;
  1264. }
  1265. juce_UseDebuggingNewOperator
  1266. };
  1267. void PopupMenu::addSectionHeader (const String& title) throw()
  1268. {
  1269. addCustomItem (0X4734a34f, new HeaderItemComponent (title));
  1270. }
  1271. //==============================================================================
  1272. Component* PopupMenu::createMenuComponent (const int x, const int y, const int w, const int h,
  1273. const int itemIdThatMustBeVisible,
  1274. const int minimumWidth,
  1275. const int maximumNumColumns,
  1276. const int standardItemHeight,
  1277. const bool alignToRectangle,
  1278. Component* menuBarComponent,
  1279. ApplicationCommandManager** managerOfChosenCommand,
  1280. Component* const componentAttachedTo) throw()
  1281. {
  1282. PopupMenuWindow* const pw
  1283. = PopupMenuWindow::create (*this,
  1284. ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(),
  1285. 0,
  1286. x, x + w,
  1287. y, y + h,
  1288. minimumWidth,
  1289. maximumNumColumns,
  1290. standardItemHeight,
  1291. alignToRectangle,
  1292. itemIdThatMustBeVisible,
  1293. menuBarComponent,
  1294. managerOfChosenCommand,
  1295. componentAttachedTo);
  1296. if (pw != 0)
  1297. pw->setVisible (true);
  1298. return pw;
  1299. }
  1300. int PopupMenu::showMenu (const int x, const int y, const int w, const int h,
  1301. const int itemIdThatMustBeVisible,
  1302. const int minimumWidth,
  1303. const int maximumNumColumns,
  1304. const int standardItemHeight,
  1305. const bool alignToRectangle,
  1306. Component* const componentAttachedTo) throw()
  1307. {
  1308. Component* const prevFocused = Component::getCurrentlyFocusedComponent();
  1309. ScopedPointer <ComponentDeletionWatcher> deletionChecker[2];
  1310. if (prevFocused != 0)
  1311. deletionChecker[0] = new ComponentDeletionWatcher (prevFocused);
  1312. Component* const prevTopLevel = (prevFocused != 0) ? prevFocused->getTopLevelComponent() : 0;
  1313. if (prevTopLevel != 0)
  1314. deletionChecker[1] = new ComponentDeletionWatcher (prevTopLevel);
  1315. wasHiddenBecauseOfAppChange = false;
  1316. int result = 0;
  1317. ApplicationCommandManager* managerOfChosenCommand = 0;
  1318. ScopedPointer <Component> popupComp (createMenuComponent (x, y, w, h,
  1319. itemIdThatMustBeVisible,
  1320. minimumWidth,
  1321. maximumNumColumns > 0 ? maximumNumColumns : 7,
  1322. standardItemHeight,
  1323. alignToRectangle, 0,
  1324. &managerOfChosenCommand,
  1325. componentAttachedTo));
  1326. if (popupComp != 0)
  1327. {
  1328. popupComp->enterModalState (false);
  1329. popupComp->toFront (false); // need to do this after making it modal, or it could
  1330. // be stuck behind other comps that are already modal..
  1331. result = popupComp->runModalLoop();
  1332. popupComp = 0;
  1333. if (! wasHiddenBecauseOfAppChange)
  1334. {
  1335. if (deletionChecker[1] != 0 && ! deletionChecker[1]->hasBeenDeleted())
  1336. prevTopLevel->toFront (true);
  1337. if (deletionChecker[0] != 0 && ! deletionChecker[0]->hasBeenDeleted())
  1338. prevFocused->grabKeyboardFocus();
  1339. }
  1340. }
  1341. if (managerOfChosenCommand != 0 && result != 0)
  1342. {
  1343. ApplicationCommandTarget::InvocationInfo info (result);
  1344. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  1345. managerOfChosenCommand->invoke (info, true);
  1346. }
  1347. return result;
  1348. }
  1349. int PopupMenu::show (const int itemIdThatMustBeVisible,
  1350. const int minimumWidth,
  1351. const int maximumNumColumns,
  1352. const int standardItemHeight)
  1353. {
  1354. int x, y;
  1355. Desktop::getMousePosition (x, y);
  1356. return showAt (x, y,
  1357. itemIdThatMustBeVisible,
  1358. minimumWidth,
  1359. maximumNumColumns,
  1360. standardItemHeight);
  1361. }
  1362. int PopupMenu::showAt (const int screenX,
  1363. const int screenY,
  1364. const int itemIdThatMustBeVisible,
  1365. const int minimumWidth,
  1366. const int maximumNumColumns,
  1367. const int standardItemHeight)
  1368. {
  1369. return showMenu (screenX, screenY, 1, 1,
  1370. itemIdThatMustBeVisible,
  1371. minimumWidth, maximumNumColumns,
  1372. standardItemHeight,
  1373. false, 0);
  1374. }
  1375. int PopupMenu::showAt (Component* componentToAttachTo,
  1376. const int itemIdThatMustBeVisible,
  1377. const int minimumWidth,
  1378. const int maximumNumColumns,
  1379. const int standardItemHeight)
  1380. {
  1381. if (componentToAttachTo != 0)
  1382. {
  1383. return showMenu (componentToAttachTo->getScreenX(),
  1384. componentToAttachTo->getScreenY(),
  1385. componentToAttachTo->getWidth(),
  1386. componentToAttachTo->getHeight(),
  1387. itemIdThatMustBeVisible,
  1388. minimumWidth,
  1389. maximumNumColumns,
  1390. standardItemHeight,
  1391. true, componentToAttachTo);
  1392. }
  1393. else
  1394. {
  1395. return show (itemIdThatMustBeVisible,
  1396. minimumWidth,
  1397. maximumNumColumns,
  1398. standardItemHeight);
  1399. }
  1400. }
  1401. void JUCE_CALLTYPE PopupMenu::dismissAllActiveMenus() throw()
  1402. {
  1403. for (int i = activeMenuWindows.size(); --i >= 0;)
  1404. {
  1405. PopupMenuWindow* const pmw = (PopupMenuWindow*) activeMenuWindows[i];
  1406. if (pmw != 0)
  1407. pmw->dismissMenu (0);
  1408. }
  1409. }
  1410. //==============================================================================
  1411. int PopupMenu::getNumItems() const throw()
  1412. {
  1413. int num = 0;
  1414. for (int i = items.size(); --i >= 0;)
  1415. if (! ((MenuItemInfo*) items.getUnchecked(i))->isSeparator)
  1416. ++num;
  1417. return num;
  1418. }
  1419. bool PopupMenu::containsCommandItem (const int commandID) const throw()
  1420. {
  1421. for (int i = items.size(); --i >= 0;)
  1422. {
  1423. const MenuItemInfo* mi = (const MenuItemInfo*) items.getUnchecked (i);
  1424. if ((mi->itemId == commandID && mi->commandManager != 0)
  1425. || (mi->subMenu != 0 && mi->subMenu->containsCommandItem (commandID)))
  1426. {
  1427. return true;
  1428. }
  1429. }
  1430. return false;
  1431. }
  1432. bool PopupMenu::containsAnyActiveItems() const throw()
  1433. {
  1434. for (int i = items.size(); --i >= 0;)
  1435. {
  1436. const MenuItemInfo* const mi = (const MenuItemInfo*) items.getUnchecked (i);
  1437. if (mi->subMenu != 0)
  1438. {
  1439. if (mi->subMenu->containsAnyActiveItems())
  1440. return true;
  1441. }
  1442. else if (mi->active)
  1443. {
  1444. return true;
  1445. }
  1446. }
  1447. return false;
  1448. }
  1449. void PopupMenu::setLookAndFeel (LookAndFeel* const newLookAndFeel) throw()
  1450. {
  1451. lookAndFeel = newLookAndFeel;
  1452. }
  1453. //==============================================================================
  1454. PopupMenuCustomComponent::PopupMenuCustomComponent (const bool isTriggeredAutomatically_)
  1455. : refCount_ (0),
  1456. isHighlighted (false),
  1457. isTriggeredAutomatically (isTriggeredAutomatically_)
  1458. {
  1459. }
  1460. PopupMenuCustomComponent::~PopupMenuCustomComponent()
  1461. {
  1462. jassert (refCount_ == 0); // should be deleted only by the menu component, as they keep a ref-count.
  1463. }
  1464. void PopupMenuCustomComponent::triggerMenuItem()
  1465. {
  1466. MenuItemComponent* const mic = dynamic_cast<MenuItemComponent*> (getParentComponent());
  1467. if (mic != 0)
  1468. {
  1469. PopupMenuWindow* const pmw = dynamic_cast<PopupMenuWindow*> (mic->getParentComponent());
  1470. if (pmw != 0)
  1471. {
  1472. pmw->dismissMenu (&mic->itemInfo);
  1473. }
  1474. else
  1475. {
  1476. // something must have gone wrong with the component hierarchy if this happens..
  1477. jassertfalse
  1478. }
  1479. }
  1480. else
  1481. {
  1482. // why isn't this component inside a menu? Not much point triggering the item if
  1483. // there's no menu.
  1484. jassertfalse
  1485. }
  1486. }
  1487. //==============================================================================
  1488. PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& menu_) throw()
  1489. : subMenu (0),
  1490. itemId (0),
  1491. isSeparator (false),
  1492. isTicked (false),
  1493. isEnabled (false),
  1494. isCustomComponent (false),
  1495. isSectionHeader (false),
  1496. customColour (0),
  1497. customImage (0),
  1498. menu (menu_),
  1499. index (0)
  1500. {
  1501. }
  1502. PopupMenu::MenuItemIterator::~MenuItemIterator() throw()
  1503. {
  1504. }
  1505. bool PopupMenu::MenuItemIterator::next() throw()
  1506. {
  1507. if (index >= menu.items.size())
  1508. return false;
  1509. const MenuItemInfo* const item = (const MenuItemInfo*) menu.items.getUnchecked (index);
  1510. ++index;
  1511. itemName = item->customComp != 0 ? item->customComp->getName() : item->text;
  1512. subMenu = item->subMenu;
  1513. itemId = item->itemId;
  1514. isSeparator = item->isSeparator;
  1515. isTicked = item->isTicked;
  1516. isEnabled = item->active;
  1517. isSectionHeader = dynamic_cast <HeaderItemComponent*> (item->customComp) != 0;
  1518. isCustomComponent = (! isSectionHeader) && item->customComp != 0;
  1519. customColour = item->usesColour ? &(item->textColour) : 0;
  1520. customImage = item->image;
  1521. commandManager = item->commandManager;
  1522. return true;
  1523. }
  1524. END_JUCE_NAMESPACE