From ac9973f185ab9a091f2cfe36a31cc5fbf2d4e718 Mon Sep 17 00:00:00 2001 From: hogliux Date: Fri, 20 May 2016 17:08:26 +0100 Subject: [PATCH] Add support for creating popup menus inside parent components --- .../lookandfeel/juce_LookAndFeel_V2.cpp | 5 + .../lookandfeel/juce_LookAndFeel_V2.h | 2 + .../juce_gui_basics/menus/juce_PopupMenu.cpp | 125 ++++++++++++------ .../juce_gui_basics/menus/juce_PopupMenu.h | 15 +++ 4 files changed, 107 insertions(+), 40 deletions(-) diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index a7d2d0bb1a..718496c699 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -1075,6 +1075,11 @@ void LookAndFeel_V2::drawMenuBarItem (Graphics& g, int width, int height, g.drawFittedText (itemText, 0, 0, width, height, Justification::centred, 1); } +Component* LookAndFeel_V2::getParentComponentForMenuOptions (const PopupMenu::Options& options) +{ + return options.getParentComponent(); +} + //============================================================================== void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) { diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h index aea453953a..1633bfd036 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h @@ -173,6 +173,8 @@ public: bool isMouseOverItem, bool isMenuOpen, bool isMouseOverBar, MenuBarComponent&) override; + Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) override; + //============================================================================== void drawComboBox (Graphics&, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 317ca49be5..b93960e4bf 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -198,6 +198,7 @@ public: options (opts), managerOfChosenCommand (manager), componentAttachedTo (options.targetComponent), + parentComponent (nullptr), hasBeenOver (false), needsToScroll (false), dismissOnMouseUp (shouldDismissOnMouseUp), @@ -218,6 +219,8 @@ public: setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel()) : menu.lookAndFeel.get()); + parentComponent = getLookAndFeel().getParentComponentForMenuOptions (options); + setOpaque (getLookAndFeel().findColour (PopupMenu::backgroundColourId).isOpaque() || ! Desktop::canUseSemiTransparentWindows()); @@ -235,18 +238,30 @@ public: if (options.visibleItemID != 0) { - const int y = options.targetArea.getY() - windowPos.getY(); + const Point targetPosition = + (parentComponent != nullptr ? parentComponent->getLocalPoint (nullptr, options.targetArea.getTopLeft()) + : options.targetArea.getTopLeft()); + + const int y = targetPosition.getY() - windowPos.getY(); ensureItemIsVisible (options.visibleItemID, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1); } resizeToBestWindowPos(); - addToDesktop (ComponentPeer::windowIsTemporary - | ComponentPeer::windowIgnoresKeyPresses - | getLookAndFeel().getMenuWindowFlags()); - getActiveWindows().add (this); - Desktop::getInstance().addGlobalMouseListener (this); + if (parentComponent != nullptr) + { + parentComponent->addChildComponent (this); + } + else + { + addToDesktop (ComponentPeer::windowIsTemporary + | ComponentPeer::windowIgnoresKeyPresses + | getLookAndFeel().getMenuWindowFlags()); + + getActiveWindows().add (this); + Desktop::getInstance().addGlobalMouseListener (this); + } } ~MenuWindow() @@ -268,10 +283,13 @@ public: void paintOverChildren (Graphics& g) override { + LookAndFeel& lf = getLookAndFeel(); + + if (parentComponent != nullptr) + lf.drawResizableFrame (g, getWidth(), getHeight(), BorderSize (PopupMenuSettings::borderSize)); + if (canScroll()) { - LookAndFeel& lf = getLookAndFeel(); - if (isTopScrollZoneActive()) lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, true); @@ -551,27 +569,43 @@ public: } //============================================================================== - void calculateWindowPos (const Rectangle& target, const bool alignToRectangle) + Rectangle getParentArea (Point targetPoint) { - const Rectangle mon (Desktop::getInstance().getDisplays() - .getDisplayContaining (target.getCentre()) - #if JUCE_MAC - .userArea); - #else - .totalArea); // on windows, don't stop the menu overlapping the taskbar - #endif + Rectangle parentArea (Desktop::getInstance().getDisplays() + .getDisplayContaining (targetPoint) + #if JUCE_MAC + .userArea); + #else + .totalArea); // on windows, don't stop the menu overlapping the taskbar + #endif - const int maxMenuHeight = mon.getHeight() - 24; + if (parentComponent == nullptr) + return parentArea; + + return parentComponent->getLocalArea (nullptr, + parentComponent->getScreenBounds() + .reduced (PopupMenuSettings::borderSize) + .getIntersection (parentArea)); + } + + void calculateWindowPos (Rectangle target, const bool alignToRectangle) + { + const Rectangle parentArea = getParentArea (target.getCentre()); + + if (parentComponent != nullptr) + target = parentComponent->getLocalArea (nullptr, target).getIntersection (parentArea); + + const int maxMenuHeight = parentArea.getHeight() - 24; int x, y, widthToUse, heightToUse; - layoutMenuItems (mon.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse); + layoutMenuItems (parentArea.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse); if (alignToRectangle) { x = target.getX(); - const int spaceUnder = mon.getHeight() - (target.getBottom() - mon.getY()); - const int spaceOver = target.getY() - mon.getY(); + const int spaceUnder = parentArea.getHeight() - (target.getBottom() - parentArea.getY()); + const int spaceOver = target.getY() - parentArea.getY(); if (heightToUse < spaceUnder - 30 || spaceUnder >= spaceOver) y = target.getBottom(); @@ -580,7 +614,7 @@ public: } else { - bool tendTowardsRight = target.getCentreX() < mon.getCentreX(); + bool tendTowardsRight = target.getCentreX() < parentArea.getCentreX(); if (parent != nullptr) { @@ -589,19 +623,19 @@ public: const bool parentGoingRight = (parent->getX() + parent->getWidth() / 2 > parent->parent->getX() + parent->parent->getWidth() / 2); - if (parentGoingRight && target.getRight() + widthToUse < mon.getRight() - 4) + if (parentGoingRight && target.getRight() + widthToUse < parentArea.getRight() - 4) tendTowardsRight = true; else if ((! parentGoingRight) && target.getX() > widthToUse + 4) tendTowardsRight = false; } - else if (target.getRight() + widthToUse < mon.getRight() - 32) + else if (target.getRight() + widthToUse < parentArea.getRight() - 32) { tendTowardsRight = true; } } - const int biggestSpace = jmax (mon.getRight() - target.getRight(), - target.getX() - mon.getX()) - 32; + const int biggestSpace = jmax (parentArea.getRight() - target.getRight(), + target.getX() - parentArea.getX()) - 32; if (biggestSpace < widthToUse) { @@ -610,21 +644,21 @@ public: if (numColumns > 1) layoutMenuItems (biggestSpace - 4, maxMenuHeight, widthToUse, heightToUse); - tendTowardsRight = (mon.getRight() - target.getRight()) >= (target.getX() - mon.getX()); + tendTowardsRight = (parentArea.getRight() - target.getRight()) >= (target.getX() - parentArea.getX()); } if (tendTowardsRight) - x = jmin (mon.getRight() - widthToUse - 4, target.getRight()); + x = jmin (parentArea.getRight() - widthToUse - 4, target.getRight()); else - x = jmax (mon.getX() + 4, target.getX() - widthToUse); + x = jmax (parentArea.getX() + 4, target.getX() - widthToUse); y = target.getY(); - if (target.getCentreY() > mon.getCentreY()) - y = jmax (mon.getY(), target.getBottom() - heightToUse); + if (target.getCentreY() > parentArea.getCentreY()) + y = jmax (parentArea.getY(), target.getBottom() - heightToUse); } - x = jmax (mon.getX() + 1, jmin (mon.getRight() - (widthToUse + 6), x)); - y = jmax (mon.getY() + 1, jmin (mon.getBottom() - (heightToUse + 6), y)); + x = jmax (parentArea.getX() + 1, jmin (parentArea.getRight() - (widthToUse + 6), x)); + y = jmax (parentArea.getY() + 1, jmin (parentArea.getBottom() - (heightToUse + 6), y)); windowPos.setBounds (x, y, widthToUse, heightToUse); @@ -695,9 +729,12 @@ public: childNum += numChildren; } - if (totalW < options.minWidth) + // width must never be larger than the screen + const int minWidth = jmin (maxMenuW, options.minWidth); + + if (totalW < minWidth) { - totalW = options.minWidth; + totalW = minWidth; for (int col = 0; col < numColumns; ++col) columnWidths.set (0, totalW / numColumns); @@ -727,16 +764,15 @@ public: windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())), currentY); - const Rectangle mon (Desktop::getInstance().getDisplays() - .getDisplayContaining (windowPos.getPosition()).userArea); + const Rectangle parantArea = getParentArea (windowPos.getPosition()); int deltaY = wantedY - currentY; - windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()), - jmin (windowPos.getHeight(), mon.getHeight())); + windowPos.setSize (jmin (windowPos.getWidth(), parantArea.getWidth()), + jmin (windowPos.getHeight(), parantArea.getHeight())); - const int newY = jlimit (mon.getY(), - mon.getBottom() - windowPos.getHeight(), + const int newY = jlimit (parantArea.getY(), + parantArea.getBottom() - windowPos.getHeight(), windowPos.getY() + deltaY); deltaY -= newY - windowPos.getY(); @@ -912,6 +948,7 @@ public: OwnedArray items; ApplicationCommandManager** managerOfChosenCommand; WeakReference componentAttachedTo; + Component* parentComponent; Rectangle windowPos; bool hasBeenOver, needsToScroll; bool dismissOnMouseUp, hideOnExit, disableMouseMoves, hasAnyJuceCompHadFocus; @@ -1428,6 +1465,7 @@ void PopupMenu::addSectionHeader (const String& title) //============================================================================== PopupMenu::Options::Options() : targetComponent (nullptr), + parentComponent (nullptr), visibleItemID (0), minWidth (0), maxColumns (0), @@ -1482,6 +1520,13 @@ PopupMenu::Options PopupMenu::Options::withItemThatMustBeVisible (int idOfItemTo return o; } +PopupMenu::Options PopupMenu::Options::withParentComponent (Component* parent) const noexcept +{ + Options o (*this); + o.parentComponent = parent; + return o; +} + Component* PopupMenu::createWindow (const Options& options, ApplicationCommandManager** managerOfChosenCommand) const { diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.h b/modules/juce_gui_basics/menus/juce_PopupMenu.h index ac0a4d7672..0a7f2778a6 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -380,18 +380,31 @@ public: public: Options(); + //============================================================================== Options withTargetComponent (Component* targetComponent) const noexcept; Options withTargetScreenArea (const Rectangle& targetArea) const noexcept; Options withMinimumWidth (int minWidth) const noexcept; Options withMaximumNumColumns (int maxNumColumns) const noexcept; Options withStandardItemHeight (int standardHeight) const noexcept; Options withItemThatMustBeVisible (int idOfItemToBeVisible) const noexcept; + Options withParentComponent (Component* parentComponent) const noexcept; + + //============================================================================== + Component* getParentComponent() const noexcept { return parentComponent; } + Component* getTargetComponent() const noexcept { return targetComponent; } + Rectangle getTargetScreenArea() const noexcept { return targetArea; } + int getMinimumWidth() const noexcept { return minWidth; } + int getMaximumNumColumns() const noexcept { return maxColumns; } + int getStandardItemHeight() const noexcept { return standardHeight; } + int getItemThatMustBeVisible() const noexcept { return visibleItemID; } private: + //============================================================================== friend class PopupMenu; friend class PopupMenu::Window; Rectangle targetArea; Component* targetComponent; + Component* parentComponent; int visibleItemID, minWidth, maxColumns, standardHeight; }; @@ -668,6 +681,8 @@ public: bool isMenuOpen, bool isMouseOverBar, MenuBarComponent&) = 0; + + virtual Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) = 0; }; private: