diff --git a/Builds/Linux/Makefile b/Builds/Linux/Makefile index aeab61b682..6f8708174d 100644 --- a/Builds/Linux/Makefile +++ b/Builds/Linux/Makefile @@ -213,6 +213,7 @@ OBJECTS := \ $(OBJDIR)/juce_PreferencesPanel_dbc7d503.o \ $(OBJDIR)/juce_SystemTrayIconComponent_fa664512.o \ $(OBJDIR)/juce_AlertWindow_94d8d1f5.o \ + $(OBJDIR)/juce_CallOutBox_61f42d7c.o \ $(OBJDIR)/juce_ComponentPeer_1e4b4b08.o \ $(OBJDIR)/juce_DialogWindow_535b27b9.o \ $(OBJDIR)/juce_DocumentWindow_3cb9d4cc.o \ @@ -1222,6 +1223,11 @@ $(OBJDIR)/juce_AlertWindow_94d8d1f5.o: ../../src/gui/components/windows/juce_Ale @echo "Compiling juce_AlertWindow.cpp" @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" +$(OBJDIR)/juce_CallOutBox_61f42d7c.o: ../../src/gui/components/windows/juce_CallOutBox.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling juce_CallOutBox.cpp" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + $(OBJDIR)/juce_ComponentPeer_1e4b4b08.o: ../../src/gui/components/windows/juce_ComponentPeer.cpp -@mkdir -p $(OBJDIR) @echo "Compiling juce_ComponentPeer.cpp" diff --git a/Builds/MacOSX/Juce.xcodeproj/project.pbxproj b/Builds/MacOSX/Juce.xcodeproj/project.pbxproj index b78945adf6..2fd68d1228 100644 --- a/Builds/MacOSX/Juce.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/Juce.xcodeproj/project.pbxproj @@ -182,6 +182,7 @@ D9AAB4AE220010CD526C87D2 = { isa = PBXBuildFile; fileRef = A34C0E63D41CFF5E55FD1D9E; }; C732ADB05901619B14F1D6BB = { isa = PBXBuildFile; fileRef = CC04F253CB70B20B774801A9; }; 075D5995E41FDD670ED35E17 = { isa = PBXBuildFile; fileRef = A5AAF4475138358F33D4904A; }; + BBE02E8719411C8A7D43A401 = { isa = PBXBuildFile; fileRef = 8AEF18EE9B12D4677F96B709; }; 35E3B9684ED968BAC0BC8021 = { isa = PBXBuildFile; fileRef = 2FFDC7636EFC2D7F74590A31; }; 736AC4A9DA6515B92644FA02 = { isa = PBXBuildFile; fileRef = 929FEA5458430B7AE23BBB46; }; DFFBADCBC9C7E31B391BA560 = { isa = PBXBuildFile; fileRef = 090907E4FE95EE2B11C1A0E1; }; @@ -736,6 +737,8 @@ 740D1808DB934123F05A1598 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_WebBrowserComponent.h; path = ../../src/gui/components/special/juce_WebBrowserComponent.h; sourceTree = SOURCE_ROOT; }; A5AAF4475138358F33D4904A = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_AlertWindow.cpp; path = ../../src/gui/components/windows/juce_AlertWindow.cpp; sourceTree = SOURCE_ROOT; }; 72C4FDDDB8602591DD4F7B3B = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_AlertWindow.h; path = ../../src/gui/components/windows/juce_AlertWindow.h; sourceTree = SOURCE_ROOT; }; + 8AEF18EE9B12D4677F96B709 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_CallOutBox.cpp; path = ../../src/gui/components/windows/juce_CallOutBox.cpp; sourceTree = SOURCE_ROOT; }; + 8F54431CD3A672B1EB8335BE = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_CallOutBox.h; path = ../../src/gui/components/windows/juce_CallOutBox.h; sourceTree = SOURCE_ROOT; }; 2FFDC7636EFC2D7F74590A31 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_ComponentPeer.cpp; path = ../../src/gui/components/windows/juce_ComponentPeer.cpp; sourceTree = SOURCE_ROOT; }; 12C66C90F3192AFFD6BCEDB6 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ComponentPeer.h; path = ../../src/gui/components/windows/juce_ComponentPeer.h; sourceTree = SOURCE_ROOT; }; 929FEA5458430B7AE23BBB46 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_DialogWindow.cpp; path = ../../src/gui/components/windows/juce_DialogWindow.cpp; sourceTree = SOURCE_ROOT; }; @@ -1427,6 +1430,8 @@ 48D41BA310DED74E900A5AB0 = { isa = PBXGroup; children = ( A5AAF4475138358F33D4904A, 72C4FDDDB8602591DD4F7B3B, + 8AEF18EE9B12D4677F96B709, + 8F54431CD3A672B1EB8335BE, 2FFDC7636EFC2D7F74590A31, 12C66C90F3192AFFD6BCEDB6, 929FEA5458430B7AE23BBB46, @@ -2011,6 +2016,7 @@ D9AAB4AE220010CD526C87D2, C732ADB05901619B14F1D6BB, 075D5995E41FDD670ED35E17, + BBE02E8719411C8A7D43A401, 35E3B9684ED968BAC0BC8021, 736AC4A9DA6515B92644FA02, DFFBADCBC9C7E31B391BA560, diff --git a/Builds/VisualStudio2005/Juce.vcproj b/Builds/VisualStudio2005/Juce.vcproj index 116f57fc8e..8f5d225630 100644 --- a/Builds/VisualStudio2005/Juce.vcproj +++ b/Builds/VisualStudio2005/Juce.vcproj @@ -559,6 +559,8 @@ + + diff --git a/Builds/VisualStudio2008/Juce.vcproj b/Builds/VisualStudio2008/Juce.vcproj index db7dbe5c78..46954d1b5c 100644 --- a/Builds/VisualStudio2008/Juce.vcproj +++ b/Builds/VisualStudio2008/Juce.vcproj @@ -559,6 +559,8 @@ + + diff --git a/Builds/VisualStudio2008_DLL/Juce.vcproj b/Builds/VisualStudio2008_DLL/Juce.vcproj index 14698a8955..7c28e2eb21 100644 --- a/Builds/VisualStudio2008_DLL/Juce.vcproj +++ b/Builds/VisualStudio2008_DLL/Juce.vcproj @@ -561,6 +561,8 @@ + + diff --git a/Builds/iPhone/Juce.xcodeproj/project.pbxproj b/Builds/iPhone/Juce.xcodeproj/project.pbxproj index ffa7baf63d..f5f13d37b6 100644 --- a/Builds/iPhone/Juce.xcodeproj/project.pbxproj +++ b/Builds/iPhone/Juce.xcodeproj/project.pbxproj @@ -182,6 +182,7 @@ D9AAB4AE220010CD526C87D2 = { isa = PBXBuildFile; fileRef = A34C0E63D41CFF5E55FD1D9E; }; C732ADB05901619B14F1D6BB = { isa = PBXBuildFile; fileRef = CC04F253CB70B20B774801A9; }; 075D5995E41FDD670ED35E17 = { isa = PBXBuildFile; fileRef = A5AAF4475138358F33D4904A; }; + BBE02E8719411C8A7D43A401 = { isa = PBXBuildFile; fileRef = 8AEF18EE9B12D4677F96B709; }; 35E3B9684ED968BAC0BC8021 = { isa = PBXBuildFile; fileRef = 2FFDC7636EFC2D7F74590A31; }; 736AC4A9DA6515B92644FA02 = { isa = PBXBuildFile; fileRef = 929FEA5458430B7AE23BBB46; }; DFFBADCBC9C7E31B391BA560 = { isa = PBXBuildFile; fileRef = 090907E4FE95EE2B11C1A0E1; }; @@ -736,6 +737,8 @@ 740D1808DB934123F05A1598 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_WebBrowserComponent.h; path = ../../src/gui/components/special/juce_WebBrowserComponent.h; sourceTree = SOURCE_ROOT; }; A5AAF4475138358F33D4904A = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_AlertWindow.cpp; path = ../../src/gui/components/windows/juce_AlertWindow.cpp; sourceTree = SOURCE_ROOT; }; 72C4FDDDB8602591DD4F7B3B = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_AlertWindow.h; path = ../../src/gui/components/windows/juce_AlertWindow.h; sourceTree = SOURCE_ROOT; }; + 8AEF18EE9B12D4677F96B709 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_CallOutBox.cpp; path = ../../src/gui/components/windows/juce_CallOutBox.cpp; sourceTree = SOURCE_ROOT; }; + 8F54431CD3A672B1EB8335BE = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_CallOutBox.h; path = ../../src/gui/components/windows/juce_CallOutBox.h; sourceTree = SOURCE_ROOT; }; 2FFDC7636EFC2D7F74590A31 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_ComponentPeer.cpp; path = ../../src/gui/components/windows/juce_ComponentPeer.cpp; sourceTree = SOURCE_ROOT; }; 12C66C90F3192AFFD6BCEDB6 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ComponentPeer.h; path = ../../src/gui/components/windows/juce_ComponentPeer.h; sourceTree = SOURCE_ROOT; }; 929FEA5458430B7AE23BBB46 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_DialogWindow.cpp; path = ../../src/gui/components/windows/juce_DialogWindow.cpp; sourceTree = SOURCE_ROOT; }; @@ -1427,6 +1430,8 @@ 48D41BA310DED74E900A5AB0 = { isa = PBXGroup; children = ( A5AAF4475138358F33D4904A, 72C4FDDDB8602591DD4F7B3B, + 8AEF18EE9B12D4677F96B709, + 8F54431CD3A672B1EB8335BE, 2FFDC7636EFC2D7F74590A31, 12C66C90F3192AFFD6BCEDB6, 929FEA5458430B7AE23BBB46, @@ -2011,6 +2016,7 @@ D9AAB4AE220010CD526C87D2, C732ADB05901619B14F1D6BB, 075D5995E41FDD670ED35E17, + BBE02E8719411C8A7D43A401, 35E3B9684ED968BAC0BC8021, 736AC4A9DA6515B92644FA02, DFFBADCBC9C7E31B391BA560, diff --git a/Juce.jucer b/Juce.jucer index 0b04fbe394..68e95c82ea 100644 --- a/Juce.jucer +++ b/Juce.jucer @@ -887,6 +887,10 @@ file="src/gui/components/windows/juce_AlertWindow.cpp"/> + + getTopLevelComponent()*/); + CallOutBox c (colourSelector, *targetComp, 0 /*targetComp->getTopLevelComponent()*/); + c.runModalLoop(); } void resized() diff --git a/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h b/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h index 62d74ea382..ff2e6229eb 100644 --- a/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h +++ b/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h @@ -650,7 +650,9 @@ public: undoManager->beginNewTransaction(); PopupFillSelector popup (fillState, getDefaultGradient(), imageProvider, project, undoManager); - CallOutBox::showModal (popup, this, 0 /*getTopLevelComponent()*/); + + CallOutBox c (popup, *this, 0 /*getTopLevelComponent()*/); + c.runModalLoop(); } void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& property) { refresh(); } diff --git a/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.cpp b/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.cpp index e3fa77c432..4fb15c2c33 100644 --- a/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.cpp +++ b/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.cpp @@ -415,209 +415,3 @@ const ColourGradient FillTypeEditorComponent::getDefaultGradient() const jassert (p != 0); return p->getDefaultGradient(); } - - -//============================================================================== -CallOutBox::CallOutBox (Component& content_, const float arrowSize_) - : edge (jmax (20, (int) arrowSize_)), content (content_), arrowSize (arrowSize_) -{ - addAndMakeVisible (&content); -} - -CallOutBox::~CallOutBox() -{ -} - -//============================================================================== -void CallOutBox::showModal (Component& content, Component* targetComp, Component* parentComp, float arrowSize) -{ - showModal (content, - parentComp, - parentComp == 0 ? targetComp->getParentMonitorArea() - : parentComp->getLocalBounds(), - parentComp == 0 ? targetComp->getScreenBounds() - : (targetComp->getLocalBounds() + targetComp->relativePositionToOtherComponent (parentComp, Point())), - arrowSize); -} - -void CallOutBox::showModal (Component& content, Component* parent, - const Rectangle& availableAreaInParent, - const Rectangle& targetAreaInParent, float arrowSize) -{ - CallOutBox p (content, arrowSize); - p.updatePosition (targetAreaInParent, availableAreaInParent); - - if (parent != 0) - parent->addAndMakeVisible (&p); - else - p.addToDesktop (ComponentPeer::windowIsTemporary); - - p.runModalLoop(); -} - -//============================================================================== -void CallOutBox::paint (Graphics& g) -{ - if (background.isNull()) - { - DropShadowEffect shadow; - shadow.setShadowProperties (5.0f, 0.4f, 0, 2); - - Image im (Image::ARGB, getWidth(), getHeight(), true); - - { - Graphics g (im); - - g.setColour (Colour::greyLevel (0.23f).withAlpha (0.9f)); - g.fillPath (outline); - - g.setColour (Colours::white.withAlpha (0.8f)); - g.strokePath (outline, PathStrokeType (2.0f)); - } - - background = Image (Image::ARGB, getWidth(), getHeight(), true); - Graphics g (background); - shadow.applyEffect (im, g); - } - - g.setColour (Colours::black); - g.drawImageAt (background, 0, 0); -} - -void CallOutBox::resized() -{ - content.setTopLeftPosition (edge, edge); - refreshPath(); -} - -void CallOutBox::moved() -{ - refreshPath(); -} - -void CallOutBox::childBoundsChanged (Component*) -{ - updatePosition (targetArea, availableArea); -} - -bool CallOutBox::hitTest (int x, int y) -{ - return outline.contains ((float) x, (float) y); -} - -void CallOutBox::inputAttemptWhenModal() -{ - exitModalState (0); - setVisible (false); -} - -void CallOutBox::updatePosition (const Rectangle& newTargetArea, const Rectangle& newArea) -{ - targetArea = newTargetArea; - availableArea = newArea; - - Rectangle bounds (0, 0, - content.getWidth() + edge * 2, - content.getHeight() + edge * 2); - - const int hw = bounds.getWidth() / 2; - const int hh = bounds.getHeight() / 2; - const float hwReduced = (float) (hw - edge * 3); - const float hhReduced = (float) (hh - edge * 3); - const float arrowIndent = edge - arrowSize; - - Point targets[4] = { Point ((float) targetArea.getCentreX(), (float) targetArea.getBottom()), - Point ((float) targetArea.getRight(), (float) targetArea.getCentreY()), - Point ((float) targetArea.getX(), (float) targetArea.getCentreY()), - Point ((float) targetArea.getCentreX(), (float) targetArea.getY()) }; - - Line lines[4] = { Line (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)), - Line (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)), - Line (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)), - Line (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) }; - - const Rectangle reducedArea (newArea.reduced (hw, hh).toFloat()); - - float bestDist = 1.0e9f; - - for (int i = 0; i < 4; ++i) - { - Line constrainedLine (reducedArea.getConstrainedPoint (lines[i].getStart()), - reducedArea.getConstrainedPoint (lines[i].getEnd())); - - const Point centre (constrainedLine.findNearestPointTo (reducedArea.getCentre())); - float distanceFromCentre = centre.getDistanceFrom (reducedArea.getCentre()); - - if (! (reducedArea.contains (lines[i].getStart()) || reducedArea.contains (lines[i].getEnd()))) - distanceFromCentre *= 2.0f; - - if (distanceFromCentre < bestDist) - { - bestDist = distanceFromCentre; - - targetPoint = targets[i]; - bounds.setPosition ((int) (centre.getX() - hw), - (int) (centre.getY() - hh)); - } - } - - setBounds (bounds); -} - -void CallOutBox::refreshPath() -{ - repaint(); - background = Image(); - outline.clear(); - - const float gap = 4.5f; - const float cornerSize = 9.0f; - const float cornerSize2 = 2.0f * cornerSize; - const float arrowBaseWidth = arrowSize * 0.7f; - const float left = content.getX() - gap, top = content.getY() - gap, right = content.getRight() + gap, bottom = content.getBottom() + gap; - const float targetX = targetPoint.getX() - getX(), targetY = targetPoint.getY() - getY(); - - outline.startNewSubPath (left + cornerSize, top); - - if (targetY < edge) - { - outline.lineTo (targetX - arrowBaseWidth, top); - outline.lineTo (targetX, targetY); - outline.lineTo (targetX + arrowBaseWidth, top); - } - - outline.lineTo (right - cornerSize, top); - outline.addArc (right - cornerSize2, top, cornerSize2, cornerSize2, 0, float_Pi * 0.5f); - - if (targetX > right) - { - outline.lineTo (right, targetY - arrowBaseWidth); - outline.lineTo (targetX, targetY); - outline.lineTo (right, targetY + arrowBaseWidth); - } - - outline.lineTo (right, bottom - cornerSize); - outline.addArc (right - cornerSize2, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi * 0.5f, float_Pi); - - if (targetY > bottom) - { - outline.lineTo (targetX + arrowBaseWidth, bottom); - outline.lineTo (targetX, targetY); - outline.lineTo (targetX - arrowBaseWidth, bottom); - } - - outline.lineTo (left + cornerSize, bottom); - outline.addArc (left, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi, float_Pi * 1.5f); - - if (targetX < left) - { - outline.lineTo (left, targetY + arrowBaseWidth); - outline.lineTo (targetX, targetY); - outline.lineTo (left, targetY - arrowBaseWidth); - } - - outline.lineTo (left, top + cornerSize); - outline.addArc (left, top, cornerSize2, cornerSize2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f); - - outline.closeSubPath(); -} diff --git a/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.h b/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.h index 162c797583..36595058cd 100644 --- a/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.h +++ b/extras/Jucer (experimental)/Source/utility/jucer_MiscUtilities.h @@ -81,58 +81,6 @@ private: GlyphArrangement glyphs; }; -//============================================================================== -class CallOutBox : public Component -{ -public: - //============================================================================== - CallOutBox (Component& content, float arrowSize = 16.0f); - ~CallOutBox(); - - //============================================================================== - static void showModal (Component& contentComponent, Component* targetComp, - Component* parentComp, float arrowSize = 16.0f); - - static void showModal (Component& contentComponent, Component* parent, - const Rectangle& availableAreaInParent, - const Rectangle& targetAreaInParent, - float arrowSize = 16.0f); - - //============================================================================== - void updatePosition (const Rectangle& newTargetArea, const Rectangle& newArea); - - //============================================================================== - /** @internal */ - void paint (Graphics& g); - /** @internal */ - void resized(); - /** @internal */ - void moved(); - /** @internal */ - void childBoundsChanged (Component*); - /** @internal */ - bool hitTest (int x, int y); - /** @internal */ - void inputAttemptWhenModal(); - - juce_UseDebuggingNewOperator - -private: - const int edge; - const float arrowSize; - Component& content; - Path outline; - Point targetPoint; - Rectangle availableArea, targetArea; - Image background; - - void refreshPath(); - - CallOutBox (const CallOutBox&); - CallOutBox& operator= (const CallOutBox&); -}; - - //============================================================================== class JucerToolbarButton : public ToolbarItemComponent { diff --git a/extras/juce demo/Source/demos/WidgetsDemo.cpp b/extras/juce demo/Source/demos/WidgetsDemo.cpp index 6cc66b1f55..1f8468fd26 100644 --- a/extras/juce demo/Source/demos/WidgetsDemo.cpp +++ b/extras/juce demo/Source/demos/WidgetsDemo.cpp @@ -256,28 +256,15 @@ public: { // create two colour selector components for our background and // text colour.. - ColourSelector colourSelector1; - colourSelector1.setName ("background"); - colourSelector1.setCurrentColour (findColour (TextButton::buttonColourId)); - colourSelector1.addChangeListener (this); - - ColourSelector colourSelector2; - colourSelector2.setName ("text"); - colourSelector2.setCurrentColour (findColour (TextButton::textColourOffId)); - colourSelector2.addChangeListener (this); - - // and add the selectors as custom menu items to a PopupMenu, putting - // them in two different sub-menus.. - PopupMenu m, sub1, sub2; - - sub1.addCustomItem (1234, &colourSelector1, 300, 300, false); - m.addSubMenu ("background colour", sub1); - - sub2.addCustomItem (1234, &colourSelector2, 300, 300, false); - m.addSubMenu ("text colour", sub2); - - // and show the menu (modally).. - m.showAt (this); + ColourSelector colourSelector; + colourSelector.setName ("background"); + colourSelector.setCurrentColour (findColour (TextButton::buttonColourId)); + colourSelector.addChangeListener (this); + colourSelector.setColour (ColourSelector::backgroundColourId, Colours::transparentBlack); + colourSelector.setSize (300, 400); + + CallOutBox callOut (colourSelector, *this, 0); + callOut.runModalLoop(); } void changeListenerCallback (void* source) diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index c7b7de8d4a..854b175079 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -66032,6 +66032,25 @@ const Rectangle LookAndFeel::getPropertyComponentContentPosition (PropertyC component.getWidth() - component.getWidth() / 3 - 1, component.getHeight() - 3); } +void LookAndFeel::drawCallOutBoxBackground (CallOutBox& box, Graphics& g, const Path& path) +{ + Image content (Image::ARGB, box.getWidth(), box.getHeight(), true); + + { + Graphics g2 (content); + + g2.setColour (Colour::greyLevel (0.23f).withAlpha (0.9f)); + g2.fillPath (path); + + g2.setColour (Colours::white.withAlpha (0.8f)); + g2.strokePath (path, PathStrokeType (2.0f)); + } + + DropShadowEffect shadow; + shadow.setShadowProperties (5.0f, 0.4f, 0, 2); + shadow.applyEffect (content, g); +} + void LookAndFeel::createFileChooserHeaderText (const String& title, const String& instructions, GlyphArrangement& text, @@ -76241,6 +76260,210 @@ END_JUCE_NAMESPACE /*** End of inlined file: juce_AlertWindow.cpp ***/ +/*** Start of inlined file: juce_CallOutBox.cpp ***/ +BEGIN_JUCE_NAMESPACE + +CallOutBox::CallOutBox (Component& contentComponent, + Component& componentToPointTo, + Component* const parentComponent) + : borderSpace (20), arrowSize (16.0f), content (contentComponent) +{ + addAndMakeVisible (&content); + + if (parentComponent != 0) + { + updatePosition (parentComponent->getLocalBounds(), + componentToPointTo.getLocalBounds() + + componentToPointTo.relativePositionToOtherComponent (parentComponent, Point())); + + parentComponent->addAndMakeVisible (this); + } + else + { + updatePosition (componentToPointTo.getScreenBounds(), + componentToPointTo.getParentMonitorArea()); + + addToDesktop (ComponentPeer::windowIsTemporary); + } +} + +CallOutBox::~CallOutBox() +{ +} + +void CallOutBox::setArrowSize (const float newSize) +{ + arrowSize = newSize; + borderSpace = jmax (20, (int) arrowSize); + refreshPath(); +} + +void CallOutBox::paint (Graphics& g) +{ + if (background.isNull()) + { + background = Image (Image::ARGB, getWidth(), getHeight(), true); + Graphics g (background); + getLookAndFeel().drawCallOutBoxBackground (*this, g, outline); + } + + g.setColour (Colours::black); + g.drawImageAt (background, 0, 0); +} + +void CallOutBox::resized() +{ + content.setTopLeftPosition (borderSpace, borderSpace); + refreshPath(); +} + +void CallOutBox::moved() +{ + refreshPath(); +} + +void CallOutBox::childBoundsChanged (Component*) +{ + updatePosition (targetArea, availableArea); +} + +bool CallOutBox::hitTest (int x, int y) +{ + return outline.contains ((float) x, (float) y); +} + +void CallOutBox::inputAttemptWhenModal() +{ + exitModalState (0); + setVisible (false); +} + +bool CallOutBox::keyPressed (const KeyPress& key) +{ + if (key.isKeyCode (KeyPress::escapeKey)) + { + inputAttemptWhenModal(); + return true; + } + + return false; +} + +void CallOutBox::updatePosition (const Rectangle& newAreaToPointTo, const Rectangle& newAreaToFitIn) +{ + targetArea = newAreaToPointTo; + availableArea = newAreaToFitIn; + + Rectangle bounds (0, 0, + content.getWidth() + borderSpace * 2, + content.getHeight() + borderSpace * 2); + + const int hw = bounds.getWidth() / 2; + const int hh = bounds.getHeight() / 2; + const float hwReduced = (float) (hw - borderSpace * 3); + const float hhReduced = (float) (hh - borderSpace * 3); + const float arrowIndent = borderSpace - arrowSize; + + Point targets[4] = { Point ((float) targetArea.getCentreX(), (float) targetArea.getBottom()), + Point ((float) targetArea.getRight(), (float) targetArea.getCentreY()), + Point ((float) targetArea.getX(), (float) targetArea.getCentreY()), + Point ((float) targetArea.getCentreX(), (float) targetArea.getY()) }; + + Line lines[4] = { Line (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)), + Line (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)), + Line (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)), + Line (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) }; + + const Rectangle centrePointArea (newAreaToFitIn.reduced (hw, hh).toFloat()); + + float nearest = 1.0e9f; + + for (int i = 0; i < 4; ++i) + { + Line constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()), + centrePointArea.getConstrainedPoint (lines[i].getEnd())); + + const Point centre (constrainedLine.findNearestPointTo (centrePointArea.getCentre())); + float distanceFromCentre = centre.getDistanceFrom (centrePointArea.getCentre()); + + if (! (centrePointArea.contains (lines[i].getStart()) || centrePointArea.contains (lines[i].getEnd()))) + distanceFromCentre *= 2.0f; + + if (distanceFromCentre < nearest) + { + nearest = distanceFromCentre; + + targetPoint = targets[i]; + bounds.setPosition ((int) (centre.getX() - hw), + (int) (centre.getY() - hh)); + } + } + + setBounds (bounds); +} + +void CallOutBox::refreshPath() +{ + repaint(); + background = Image(); + outline.clear(); + + const float gap = 4.5f; + const float cornerSize = 9.0f; + const float cornerSize2 = 2.0f * cornerSize; + const float arrowBaseWidth = arrowSize * 0.7f; + const float left = content.getX() - gap, top = content.getY() - gap, right = content.getRight() + gap, bottom = content.getBottom() + gap; + const float targetX = targetPoint.getX() - getX(), targetY = targetPoint.getY() - getY(); + + outline.startNewSubPath (left + cornerSize, top); + + if (targetY <= top) + { + outline.lineTo (targetX - arrowBaseWidth, top); + outline.lineTo (targetX, targetY); + outline.lineTo (targetX + arrowBaseWidth, top); + } + + outline.lineTo (right - cornerSize, top); + outline.addArc (right - cornerSize2, top, cornerSize2, cornerSize2, 0, float_Pi * 0.5f); + + if (targetX >= right) + { + outline.lineTo (right, targetY - arrowBaseWidth); + outline.lineTo (targetX, targetY); + outline.lineTo (right, targetY + arrowBaseWidth); + } + + outline.lineTo (right, bottom - cornerSize); + outline.addArc (right - cornerSize2, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi * 0.5f, float_Pi); + + if (targetY >= bottom) + { + outline.lineTo (targetX + arrowBaseWidth, bottom); + outline.lineTo (targetX, targetY); + outline.lineTo (targetX - arrowBaseWidth, bottom); + } + + outline.lineTo (left + cornerSize, bottom); + outline.addArc (left, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi, float_Pi * 1.5f); + + if (targetX <= left) + { + outline.lineTo (left, targetY + arrowBaseWidth); + outline.lineTo (targetX, targetY); + outline.lineTo (left, targetY - arrowBaseWidth); + } + + outline.lineTo (left, top + cornerSize); + outline.addArc (left, top, cornerSize2, cornerSize2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f); + + outline.closeSubPath(); +} + +END_JUCE_NAMESPACE +/*** End of inlined file: juce_CallOutBox.cpp ***/ + + /*** Start of inlined file: juce_ComponentPeer.cpp ***/ BEGIN_JUCE_NAMESPACE @@ -84084,6 +84307,17 @@ void DrawableComposite::resetBoundingBoxToContentArea() RelativePoint (content.left, content.bottom))); } +void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() +{ + const Rectangle bounds (getUntransformedBounds (false)); + + setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX(), true), + RelativeCoordinate (bounds.getRight(), true), + RelativeCoordinate (bounds.getY(), false), + RelativeCoordinate (bounds.getBottom(), false))); + resetBoundingBoxToContentArea(); +} + int DrawableComposite::getNumMarkers (const bool xAxis) const throw() { return (xAxis ? markersX : markersY).size(); @@ -84205,7 +84439,7 @@ const RelativeCoordinate DrawableComposite::findNamedCoordinate (const String& o return RelativeCoordinate(); } -const Rectangle DrawableComposite::getUntransformedBounds() const +const Rectangle DrawableComposite::getUntransformedBounds (const bool includeMarkers) const { Rectangle bounds; @@ -84213,58 +84447,57 @@ const Rectangle DrawableComposite::getUntransformedBounds() const for (i = 0; i < drawables.size(); ++i) bounds = bounds.getUnion (drawables.getUnchecked(i)->getBounds()); - if (markersX.size() > 0) + if (includeMarkers) { - float minX = std::numeric_limits::max(); - float maxX = std::numeric_limits::min(); - - for (i = markersX.size(); --i >= 0;) + if (markersX.size() > 0) { - const Marker* m = markersX.getUnchecked(i); - const float pos = (float) m->position.resolve (parent); - minX = jmin (minX, pos); - maxX = jmax (maxX, pos); - } + float minX = std::numeric_limits::max(); + float maxX = std::numeric_limits::min(); - if (minX <= maxX) - { - if (bounds.getWidth() == 0) + for (i = markersX.size(); --i >= 0;) { - bounds.setLeft (minX); - bounds.setWidth (maxX - minX); + const Marker* m = markersX.getUnchecked(i); + const float pos = (float) m->position.resolve (parent); + minX = jmin (minX, pos); + maxX = jmax (maxX, pos); } - else + + if (minX <= maxX) { - bounds.setLeft (jmin (bounds.getX(), minX)); - bounds.setRight (jmax (bounds.getRight(), maxX)); + if (bounds.getHeight() > 0) + { + minX = jmin (minX, bounds.getX()); + maxX = jmax (maxX, bounds.getRight()); + } + + bounds.setLeft (minX); + bounds.setWidth (maxX - minX); } } - } - - if (markersY.size() > 0) - { - float minY = std::numeric_limits::max(); - float maxY = std::numeric_limits::min(); - for (i = markersY.size(); --i >= 0;) + if (markersY.size() > 0) { - const Marker* m = markersY.getUnchecked(i); - const float pos = (float) m->position.resolve (parent); - minY = jmin (minY, pos); - maxY = jmax (maxY, pos); - } + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::min(); - if (minY <= maxY) - { - if (bounds.getHeight() == 0) + for (i = markersY.size(); --i >= 0;) { - bounds.setTop (minY); - bounds.setHeight (maxY - minY); + const Marker* m = markersY.getUnchecked(i); + const float pos = (float) m->position.resolve (parent); + minY = jmin (minY, pos); + maxY = jmax (maxY, pos); } - else + + if (minY <= maxY) { - bounds.setTop (jmin (bounds.getY(), minY)); - bounds.setBottom (jmax (bounds.getBottom(), maxY)); + if (bounds.getHeight() > 0) + { + minY = jmin (minY, bounds.getY()); + maxY = jmax (maxY, bounds.getBottom()); + } + + bounds.setTop (minY); + bounds.setHeight (maxY - minY); } } } @@ -84274,7 +84507,7 @@ const Rectangle DrawableComposite::getUntransformedBounds() const const Rectangle DrawableComposite::getBounds() const { - return getUntransformedBounds().transformed (calculateTransform()); + return getUntransformedBounds (true).transformed (calculateTransform()); } bool DrawableComposite::hitTest (float x, float y) const @@ -85643,13 +85876,7 @@ public: newState.parseSubElements (xml, drawable); - const Rectangle bounds (drawable->getBounds()); - drawable->setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX(), true), - RelativeCoordinate (bounds.getRight(), true), - RelativeCoordinate (bounds.getY(), false), - RelativeCoordinate (bounds.getBottom(), false))); - drawable->resetBoundingBoxToContentArea(); - + drawable->resetContentAreaAndBoundingBoxToFitChildren(); return drawable; } @@ -85711,6 +85938,7 @@ private: parseSubElements (xml, drawable); } + drawable->resetContentAreaAndBoundingBoxToFitChildren(); return drawable; } diff --git a/juce_amalgamated.h b/juce_amalgamated.h index ac1168e295..bbe455c0d4 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -64,7 +64,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 13 +#define JUCE_BUILDNUMBER 14 /** Current Juce version number. @@ -22284,17 +22284,20 @@ public: /** Applies this stroke type to a path and returns the resultant stroke as another Path. - @param destPath the resultant stroked outline shape will be copied into this path. - Note that it's ok for the source and destination Paths to be - the same object, so you can easily turn a path into a stroked version - of itself. + @param destPath the resultant stroked outline shape will be copied into this path. + Note that it's ok for the source and destination Paths to be + the same object, so you can easily turn a path into a stroked version + of itself. @param sourcePath the path to use as the source - @param transform an optional transform to apply to the points from the source path - as they are being used + @param arrowheadStartWidth the width of the arrowhead at the start of the path + @param arrowheadStartLength the length of the arrowhead at the start of the path + @param arrowheadEndWidth the width of the arrowhead at the end of the path + @param arrowheadEndLength the length of the arrowhead at the end of the path + @param transform an optional transform to apply to the points from the source path + as they are being used @param extraAccuracy if this is greater than 1.0, it will subdivide the path to - a higher resolution, which improved the quality if you'll later want - to enlarge the stroked path - + a higher resolution, which improved the quality if you'll later want + to enlarge the stroked path @see createDashedStroke */ void createStrokeWithArrowheads (Path& destPath, @@ -53213,6 +53216,7 @@ class FileBrowserComponent; class DirectoryContentsDisplayComponent; class FilePreviewComponent; class ImageButton; +class CallOutBox; /** LookAndFeel objects define the appearance of all the JUCE widgets, and subclasses @@ -53719,6 +53723,8 @@ public: virtual const Rectangle getPropertyComponentContentPosition (PropertyComponent& component); + void drawCallOutBoxBackground (CallOutBox& box, Graphics& g, const Path& path); + virtual void drawLevelMeter (Graphics& g, int width, int height, float level); virtual void drawKeymapChangeButton (Graphics& g, int width, int height, Button& button, const String& keyDescription); @@ -57130,6 +57136,109 @@ private: #endif #ifndef __JUCE_ALERTWINDOW_JUCEHEADER__ +#endif +#ifndef __JUCE_CALLOUTBOX_JUCEHEADER__ + +/*** Start of inlined file: juce_CallOutBox.h ***/ +#ifndef __JUCE_CALLOUTBOX_JUCEHEADER__ +#define __JUCE_CALLOUTBOX_JUCEHEADER__ + +/** + A box with a small arrow that can be used as a temporary pop-up window to show + extra controls when a button or other component is clicked. + + Using one of these is similar to having a popup menu attached to a button or + other component - but it looks fancier, and has an arrow that can indicate the + object that it applies to. + + Normally, you'd create one of these on the stack and run it modally, e.g. + + @code + void mouseUp (const MouseEvent& e) + { + MyContentComponent content; + content.setSize (300, 300); + + CallOutBox callOut (content, *this, 0); + callOut.runModalLoop(); + } + @endcode + + The call-out will resize and position itself when the content changes size. +*/ +class JUCE_API CallOutBox : public Component +{ +public: + + /** Creates a CallOutBox. + + @param contentComponent the component to display inside the call-out. This should + already have a size set (although the call-out will also + update itself when the component's size is changed later). + Obviously this component must not be deleted until the + call-out box has been deleted. + @param componentToPointTo the component that the call-out's arrow should point towards + @param parentComponent if non-zero, this is the component to add the call-out to. If + this is zero, the call-out will be added to the desktop. + */ + CallOutBox (Component& contentComponent, + Component& componentToPointTo, + Component* parentComponent); + + /** Destructor. */ + ~CallOutBox(); + + /** Changes the length of the arrow. */ + void setArrowSize (float newSize); + + /** Updates the position and size of the box. + + You shouldn't normally need to call this, unless you need more precise control over the + layout. + + @param newAreaToPointTo the rectangle to make the box's arrow point to + @param newAreaToFitIn the area within which the box's position should be constrained + */ + void updatePosition (const Rectangle& newAreaToPointTo, + const Rectangle& newAreaToFitIn); + + /** @internal */ + void paint (Graphics& g); + /** @internal */ + void resized(); + /** @internal */ + void moved(); + /** @internal */ + void childBoundsChanged (Component*); + /** @internal */ + bool hitTest (int x, int y); + /** @internal */ + void inputAttemptWhenModal(); + /** @internal */ + bool keyPressed (const KeyPress& key); + + juce_UseDebuggingNewOperator + +private: + int borderSpace; + float arrowSize; + Component& content; + Path outline; + Point targetPoint; + Rectangle availableArea, targetArea; + Image background; + + void refreshPath(); + void drawCallOutBoxBackground (Graphics& g, const Path& outline, int width, int height); + + CallOutBox (const CallOutBox&); + CallOutBox& operator= (const CallOutBox&); +}; + +#endif // __JUCE_CALLOUTBOX_JUCEHEADER__ +/*** End of inlined file: juce_CallOutBox.h ***/ + + #endif #ifndef __JUCE_COMPONENTPEER_JUCEHEADER__ @@ -58558,6 +58667,11 @@ public: */ void resetBoundingBoxToContentArea(); + /** Resets the content area and the bounding transform to fit around the area occupied + by the child components (ignoring any markers). + */ + void resetContentAreaAndBoundingBoxToFitChildren(); + /** Represents a named marker position. @see DrawableComposite::getMarker */ @@ -58654,7 +58768,7 @@ private: RelativeParallelogram bounds; OwnedArray markersX, markersY; - const Rectangle getUntransformedBounds() const; + const Rectangle getUntransformedBounds (bool includeMarkers) const; const AffineTransform calculateTransform() const; DrawableComposite& operator= (const DrawableComposite&); diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index e19247b57d..a4593de283 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 13 +#define JUCE_BUILDNUMBER 14 /** Current Juce version number. diff --git a/src/gui/components/lookandfeel/juce_LookAndFeel.cpp b/src/gui/components/lookandfeel/juce_LookAndFeel.cpp index e423b77e9d..fdba8143f5 100644 --- a/src/gui/components/lookandfeel/juce_LookAndFeel.cpp +++ b/src/gui/components/lookandfeel/juce_LookAndFeel.cpp @@ -39,6 +39,7 @@ BEGIN_JUCE_NAMESPACE #include "../windows/juce_AlertWindow.h" #include "../windows/juce_DocumentWindow.h" #include "../windows/juce_ResizableWindow.h" +#include "../windows/juce_CallOutBox.h" #include "../menus/juce_MenuBarComponent.h" #include "../menus/juce_PopupMenu.h" #include "../layout/juce_ScrollBar.h" @@ -2528,6 +2529,27 @@ const Rectangle LookAndFeel::getPropertyComponentContentPosition (PropertyC component.getWidth() - component.getWidth() / 3 - 1, component.getHeight() - 3); } +//============================================================================== +void LookAndFeel::drawCallOutBoxBackground (CallOutBox& box, Graphics& g, const Path& path) +{ + Image content (Image::ARGB, box.getWidth(), box.getHeight(), true); + + { + Graphics g2 (content); + + g2.setColour (Colour::greyLevel (0.23f).withAlpha (0.9f)); + g2.fillPath (path); + + g2.setColour (Colours::white.withAlpha (0.8f)); + g2.strokePath (path, PathStrokeType (2.0f)); + } + + DropShadowEffect shadow; + shadow.setShadowProperties (5.0f, 0.4f, 0, 2); + shadow.applyEffect (content, g); +} + + //============================================================================== void LookAndFeel::createFileChooserHeaderText (const String& title, const String& instructions, diff --git a/src/gui/components/lookandfeel/juce_LookAndFeel.h b/src/gui/components/lookandfeel/juce_LookAndFeel.h index c11a29099d..718ae5d6cf 100644 --- a/src/gui/components/lookandfeel/juce_LookAndFeel.h +++ b/src/gui/components/lookandfeel/juce_LookAndFeel.h @@ -57,6 +57,7 @@ class FileBrowserComponent; class DirectoryContentsDisplayComponent; class FilePreviewComponent; class ImageButton; +class CallOutBox; //============================================================================== @@ -597,6 +598,9 @@ public: virtual const Rectangle getPropertyComponentContentPosition (PropertyComponent& component); + //============================================================================== + void drawCallOutBoxBackground (CallOutBox& box, Graphics& g, const Path& path); + //============================================================================== virtual void drawLevelMeter (Graphics& g, int width, int height, float level); diff --git a/src/gui/components/windows/juce_CallOutBox.cpp b/src/gui/components/windows/juce_CallOutBox.cpp new file mode 100644 index 0000000000..edd81ac92d --- /dev/null +++ b/src/gui/components/windows/juce_CallOutBox.cpp @@ -0,0 +1,234 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-10 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#include "../../../core/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_CallOutBox.h" +#include "juce_ComponentPeer.h" +#include "../lookandfeel/juce_LookAndFeel.h" + + +//============================================================================== +CallOutBox::CallOutBox (Component& contentComponent, + Component& componentToPointTo, + Component* const parentComponent) + : borderSpace (20), arrowSize (16.0f), content (contentComponent) +{ + addAndMakeVisible (&content); + + if (parentComponent != 0) + { + updatePosition (parentComponent->getLocalBounds(), + componentToPointTo.getLocalBounds() + + componentToPointTo.relativePositionToOtherComponent (parentComponent, Point())); + + parentComponent->addAndMakeVisible (this); + } + else + { + updatePosition (componentToPointTo.getScreenBounds(), + componentToPointTo.getParentMonitorArea()); + + addToDesktop (ComponentPeer::windowIsTemporary); + } +} + +CallOutBox::~CallOutBox() +{ +} + +//============================================================================== +void CallOutBox::setArrowSize (const float newSize) +{ + arrowSize = newSize; + borderSpace = jmax (20, (int) arrowSize); + refreshPath(); +} + +void CallOutBox::paint (Graphics& g) +{ + if (background.isNull()) + { + background = Image (Image::ARGB, getWidth(), getHeight(), true); + Graphics g (background); + getLookAndFeel().drawCallOutBoxBackground (*this, g, outline); + } + + g.setColour (Colours::black); + g.drawImageAt (background, 0, 0); +} + +void CallOutBox::resized() +{ + content.setTopLeftPosition (borderSpace, borderSpace); + refreshPath(); +} + +void CallOutBox::moved() +{ + refreshPath(); +} + +void CallOutBox::childBoundsChanged (Component*) +{ + updatePosition (targetArea, availableArea); +} + +bool CallOutBox::hitTest (int x, int y) +{ + return outline.contains ((float) x, (float) y); +} + +void CallOutBox::inputAttemptWhenModal() +{ + exitModalState (0); + setVisible (false); +} + +bool CallOutBox::keyPressed (const KeyPress& key) +{ + if (key.isKeyCode (KeyPress::escapeKey)) + { + inputAttemptWhenModal(); + return true; + } + + return false; +} + +void CallOutBox::updatePosition (const Rectangle& newAreaToPointTo, const Rectangle& newAreaToFitIn) +{ + targetArea = newAreaToPointTo; + availableArea = newAreaToFitIn; + + Rectangle bounds (0, 0, + content.getWidth() + borderSpace * 2, + content.getHeight() + borderSpace * 2); + + const int hw = bounds.getWidth() / 2; + const int hh = bounds.getHeight() / 2; + const float hwReduced = (float) (hw - borderSpace * 3); + const float hhReduced = (float) (hh - borderSpace * 3); + const float arrowIndent = borderSpace - arrowSize; + + Point targets[4] = { Point ((float) targetArea.getCentreX(), (float) targetArea.getBottom()), + Point ((float) targetArea.getRight(), (float) targetArea.getCentreY()), + Point ((float) targetArea.getX(), (float) targetArea.getCentreY()), + Point ((float) targetArea.getCentreX(), (float) targetArea.getY()) }; + + Line lines[4] = { Line (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)), + Line (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)), + Line (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)), + Line (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) }; + + const Rectangle centrePointArea (newAreaToFitIn.reduced (hw, hh).toFloat()); + + float nearest = 1.0e9f; + + for (int i = 0; i < 4; ++i) + { + Line constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()), + centrePointArea.getConstrainedPoint (lines[i].getEnd())); + + const Point centre (constrainedLine.findNearestPointTo (centrePointArea.getCentre())); + float distanceFromCentre = centre.getDistanceFrom (centrePointArea.getCentre()); + + if (! (centrePointArea.contains (lines[i].getStart()) || centrePointArea.contains (lines[i].getEnd()))) + distanceFromCentre *= 2.0f; + + if (distanceFromCentre < nearest) + { + nearest = distanceFromCentre; + + targetPoint = targets[i]; + bounds.setPosition ((int) (centre.getX() - hw), + (int) (centre.getY() - hh)); + } + } + + setBounds (bounds); +} + +void CallOutBox::refreshPath() +{ + repaint(); + background = Image(); + outline.clear(); + + const float gap = 4.5f; + const float cornerSize = 9.0f; + const float cornerSize2 = 2.0f * cornerSize; + const float arrowBaseWidth = arrowSize * 0.7f; + const float left = content.getX() - gap, top = content.getY() - gap, right = content.getRight() + gap, bottom = content.getBottom() + gap; + const float targetX = targetPoint.getX() - getX(), targetY = targetPoint.getY() - getY(); + + outline.startNewSubPath (left + cornerSize, top); + + if (targetY <= top) + { + outline.lineTo (targetX - arrowBaseWidth, top); + outline.lineTo (targetX, targetY); + outline.lineTo (targetX + arrowBaseWidth, top); + } + + outline.lineTo (right - cornerSize, top); + outline.addArc (right - cornerSize2, top, cornerSize2, cornerSize2, 0, float_Pi * 0.5f); + + if (targetX >= right) + { + outline.lineTo (right, targetY - arrowBaseWidth); + outline.lineTo (targetX, targetY); + outline.lineTo (right, targetY + arrowBaseWidth); + } + + outline.lineTo (right, bottom - cornerSize); + outline.addArc (right - cornerSize2, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi * 0.5f, float_Pi); + + if (targetY >= bottom) + { + outline.lineTo (targetX + arrowBaseWidth, bottom); + outline.lineTo (targetX, targetY); + outline.lineTo (targetX - arrowBaseWidth, bottom); + } + + outline.lineTo (left + cornerSize, bottom); + outline.addArc (left, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi, float_Pi * 1.5f); + + if (targetX <= left) + { + outline.lineTo (left, targetY + arrowBaseWidth); + outline.lineTo (targetX, targetY); + outline.lineTo (left, targetY - arrowBaseWidth); + } + + outline.lineTo (left, top + cornerSize); + outline.addArc (left, top, cornerSize2, cornerSize2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f); + + outline.closeSubPath(); +} + +END_JUCE_NAMESPACE diff --git a/src/gui/components/windows/juce_CallOutBox.h b/src/gui/components/windows/juce_CallOutBox.h new file mode 100644 index 0000000000..718eebd841 --- /dev/null +++ b/src/gui/components/windows/juce_CallOutBox.h @@ -0,0 +1,128 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-10 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_CALLOUTBOX_JUCEHEADER__ +#define __JUCE_CALLOUTBOX_JUCEHEADER__ + +#include "../juce_Component.h" + + +//============================================================================== +/** + A box with a small arrow that can be used as a temporary pop-up window to show + extra controls when a button or other component is clicked. + + Using one of these is similar to having a popup menu attached to a button or + other component - but it looks fancier, and has an arrow that can indicate the + object that it applies to. + + Normally, you'd create one of these on the stack and run it modally, e.g. + + @code + void mouseUp (const MouseEvent& e) + { + MyContentComponent content; + content.setSize (300, 300); + + CallOutBox callOut (content, *this, 0); + callOut.runModalLoop(); + } + @endcode + + The call-out will resize and position itself when the content changes size. +*/ +class JUCE_API CallOutBox : public Component +{ +public: + //============================================================================== + /** Creates a CallOutBox. + + @param contentComponent the component to display inside the call-out. This should + already have a size set (although the call-out will also + update itself when the component's size is changed later). + Obviously this component must not be deleted until the + call-out box has been deleted. + @param componentToPointTo the component that the call-out's arrow should point towards + @param parentComponent if non-zero, this is the component to add the call-out to. If + this is zero, the call-out will be added to the desktop. + */ + CallOutBox (Component& contentComponent, + Component& componentToPointTo, + Component* parentComponent); + + /** Destructor. */ + ~CallOutBox(); + + //============================================================================== + /** Changes the length of the arrow. */ + void setArrowSize (float newSize); + + /** Updates the position and size of the box. + + You shouldn't normally need to call this, unless you need more precise control over the + layout. + + @param newAreaToPointTo the rectangle to make the box's arrow point to + @param newAreaToFitIn the area within which the box's position should be constrained + */ + void updatePosition (const Rectangle& newAreaToPointTo, + const Rectangle& newAreaToFitIn); + + //============================================================================== + /** @internal */ + void paint (Graphics& g); + /** @internal */ + void resized(); + /** @internal */ + void moved(); + /** @internal */ + void childBoundsChanged (Component*); + /** @internal */ + bool hitTest (int x, int y); + /** @internal */ + void inputAttemptWhenModal(); + /** @internal */ + bool keyPressed (const KeyPress& key); + + juce_UseDebuggingNewOperator + +private: + int borderSpace; + float arrowSize; + Component& content; + Path outline; + Point targetPoint; + Rectangle availableArea, targetArea; + Image background; + + void refreshPath(); + void drawCallOutBoxBackground (Graphics& g, const Path& outline, int width, int height); + + CallOutBox (const CallOutBox&); + CallOutBox& operator= (const CallOutBox&); +}; + + +#endif // __JUCE_CALLOUTBOX_JUCEHEADER__ diff --git a/src/gui/graphics/drawables/juce_DrawableComposite.cpp b/src/gui/graphics/drawables/juce_DrawableComposite.cpp index b2e1dd9235..8f79015297 100644 --- a/src/gui/graphics/drawables/juce_DrawableComposite.cpp +++ b/src/gui/graphics/drawables/juce_DrawableComposite.cpp @@ -149,6 +149,17 @@ void DrawableComposite::resetBoundingBoxToContentArea() RelativePoint (content.left, content.bottom))); } +void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() +{ + const Rectangle bounds (getUntransformedBounds (false)); + + setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX(), true), + RelativeCoordinate (bounds.getRight(), true), + RelativeCoordinate (bounds.getY(), false), + RelativeCoordinate (bounds.getBottom(), false))); + resetBoundingBoxToContentArea(); +} + int DrawableComposite::getNumMarkers (const bool xAxis) const throw() { return (xAxis ? markersX : markersY).size(); @@ -271,7 +282,7 @@ const RelativeCoordinate DrawableComposite::findNamedCoordinate (const String& o return RelativeCoordinate(); } -const Rectangle DrawableComposite::getUntransformedBounds() const +const Rectangle DrawableComposite::getUntransformedBounds (const bool includeMarkers) const { Rectangle bounds; @@ -279,58 +290,57 @@ const Rectangle DrawableComposite::getUntransformedBounds() const for (i = 0; i < drawables.size(); ++i) bounds = bounds.getUnion (drawables.getUnchecked(i)->getBounds()); - if (markersX.size() > 0) + if (includeMarkers) { - float minX = std::numeric_limits::max(); - float maxX = std::numeric_limits::min(); - - for (i = markersX.size(); --i >= 0;) + if (markersX.size() > 0) { - const Marker* m = markersX.getUnchecked(i); - const float pos = (float) m->position.resolve (parent); - minX = jmin (minX, pos); - maxX = jmax (maxX, pos); - } + float minX = std::numeric_limits::max(); + float maxX = std::numeric_limits::min(); - if (minX <= maxX) - { - if (bounds.getWidth() == 0) + for (i = markersX.size(); --i >= 0;) { - bounds.setLeft (minX); - bounds.setWidth (maxX - minX); + const Marker* m = markersX.getUnchecked(i); + const float pos = (float) m->position.resolve (parent); + minX = jmin (minX, pos); + maxX = jmax (maxX, pos); } - else + + if (minX <= maxX) { - bounds.setLeft (jmin (bounds.getX(), minX)); - bounds.setRight (jmax (bounds.getRight(), maxX)); + if (bounds.getHeight() > 0) + { + minX = jmin (minX, bounds.getX()); + maxX = jmax (maxX, bounds.getRight()); + } + + bounds.setLeft (minX); + bounds.setWidth (maxX - minX); } } - } - - if (markersY.size() > 0) - { - float minY = std::numeric_limits::max(); - float maxY = std::numeric_limits::min(); - for (i = markersY.size(); --i >= 0;) + if (markersY.size() > 0) { - const Marker* m = markersY.getUnchecked(i); - const float pos = (float) m->position.resolve (parent); - minY = jmin (minY, pos); - maxY = jmax (maxY, pos); - } + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::min(); - if (minY <= maxY) - { - if (bounds.getHeight() == 0) + for (i = markersY.size(); --i >= 0;) { - bounds.setTop (minY); - bounds.setHeight (maxY - minY); + const Marker* m = markersY.getUnchecked(i); + const float pos = (float) m->position.resolve (parent); + minY = jmin (minY, pos); + maxY = jmax (maxY, pos); } - else + + if (minY <= maxY) { - bounds.setTop (jmin (bounds.getY(), minY)); - bounds.setBottom (jmax (bounds.getBottom(), maxY)); + if (bounds.getHeight() > 0) + { + minY = jmin (minY, bounds.getY()); + maxY = jmax (maxY, bounds.getBottom()); + } + + bounds.setTop (minY); + bounds.setHeight (maxY - minY); } } } @@ -340,7 +350,7 @@ const Rectangle DrawableComposite::getUntransformedBounds() const const Rectangle DrawableComposite::getBounds() const { - return getUntransformedBounds().transformed (calculateTransform()); + return getUntransformedBounds (true).transformed (calculateTransform()); } bool DrawableComposite::hitTest (float x, float y) const diff --git a/src/gui/graphics/drawables/juce_DrawableComposite.h b/src/gui/graphics/drawables/juce_DrawableComposite.h index cf6203eeb9..305a9359da 100644 --- a/src/gui/graphics/drawables/juce_DrawableComposite.h +++ b/src/gui/graphics/drawables/juce_DrawableComposite.h @@ -148,6 +148,11 @@ public: */ void resetBoundingBoxToContentArea(); + /** Resets the content area and the bounding transform to fit around the area occupied + by the child components (ignoring any markers). + */ + void resetContentAreaAndBoundingBoxToFitChildren(); + //============================================================================== /** Represents a named marker position. @see DrawableComposite::getMarker @@ -248,7 +253,7 @@ private: RelativeParallelogram bounds; OwnedArray markersX, markersY; - const Rectangle getUntransformedBounds() const; + const Rectangle getUntransformedBounds (bool includeMarkers) const; const AffineTransform calculateTransform() const; DrawableComposite& operator= (const DrawableComposite&); diff --git a/src/gui/graphics/drawables/juce_SVGParser.cpp b/src/gui/graphics/drawables/juce_SVGParser.cpp index 98eb18c2e9..c7dc1bbf8b 100644 --- a/src/gui/graphics/drawables/juce_SVGParser.cpp +++ b/src/gui/graphics/drawables/juce_SVGParser.cpp @@ -130,13 +130,7 @@ public: newState.parseSubElements (xml, drawable); - const Rectangle bounds (drawable->getBounds()); - drawable->setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX(), true), - RelativeCoordinate (bounds.getRight(), true), - RelativeCoordinate (bounds.getY(), false), - RelativeCoordinate (bounds.getBottom(), false))); - drawable->resetBoundingBoxToContentArea(); - + drawable->resetContentAreaAndBoundingBoxToFitChildren(); return drawable; } @@ -199,6 +193,7 @@ private: parseSubElements (xml, drawable); } + drawable->resetContentAreaAndBoundingBoxToFitChildren(); return drawable; } diff --git a/src/gui/graphics/geometry/juce_PathStrokeType.h b/src/gui/graphics/geometry/juce_PathStrokeType.h index c488082928..cd8c21edf1 100644 --- a/src/gui/graphics/geometry/juce_PathStrokeType.h +++ b/src/gui/graphics/geometry/juce_PathStrokeType.h @@ -140,17 +140,20 @@ public: //============================================================================== /** Applies this stroke type to a path and returns the resultant stroke as another Path. - @param destPath the resultant stroked outline shape will be copied into this path. - Note that it's ok for the source and destination Paths to be - the same object, so you can easily turn a path into a stroked version - of itself. - @param sourcePath the path to use as the source - @param transform an optional transform to apply to the points from the source path - as they are being used - @param extraAccuracy if this is greater than 1.0, it will subdivide the path to - a higher resolution, which improved the quality if you'll later want - to enlarge the stroked path - + @param destPath the resultant stroked outline shape will be copied into this path. + Note that it's ok for the source and destination Paths to be + the same object, so you can easily turn a path into a stroked version + of itself. + @param sourcePath the path to use as the source + @param arrowheadStartWidth the width of the arrowhead at the start of the path + @param arrowheadStartLength the length of the arrowhead at the start of the path + @param arrowheadEndWidth the width of the arrowhead at the end of the path + @param arrowheadEndLength the length of the arrowhead at the end of the path + @param transform an optional transform to apply to the points from the source path + as they are being used + @param extraAccuracy if this is greater than 1.0, it will subdivide the path to + a higher resolution, which improved the quality if you'll later want + to enlarge the stroked path @see createDashedStroke */ void createStrokeWithArrowheads (Path& destPath, diff --git a/src/juce_app_includes.h b/src/juce_app_includes.h index 6282d9a13b..efd137f590 100644 --- a/src/juce_app_includes.h +++ b/src/juce_app_includes.h @@ -584,6 +584,9 @@ #ifndef __JUCE_ALERTWINDOW_JUCEHEADER__ #include "gui/components/windows/juce_AlertWindow.h" #endif +#ifndef __JUCE_CALLOUTBOX_JUCEHEADER__ + #include "gui/components/windows/juce_CallOutBox.h" +#endif #ifndef __JUCE_COMPONENTPEER_JUCEHEADER__ #include "gui/components/windows/juce_ComponentPeer.h" #endif