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.

1875 lines
61KB

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