diff --git a/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm b/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm index 2bd976b83c..5cb3d1a4d0 100644 --- a/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm +++ b/build/macosx/platform_specific_code/juce_mac_MiscUtilities.mm @@ -52,6 +52,8 @@ void PlatformUtilities::beep() } //============================================================================== +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + bool AlertWindow::showNativeDialogBox (const String& title, const String& bodyText, bool isOkCancel) @@ -242,3 +244,6 @@ void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool c } #endif + +#endif + diff --git a/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h b/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h index 5e61bdbb7a..99097873de 100644 --- a/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h +++ b/build/macosx/platform_specific_code/juce_mac_NativeIncludes.h @@ -67,6 +67,8 @@ #if MACOS_10_4_OR_EARLIER #include + typedef int NSInteger; + typedef unsigned int NSUInteger; #endif #endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ diff --git a/build/macosx/platform_specific_code/juce_mac_QuickTimeMovieComponent.mm b/build/macosx/platform_specific_code/juce_mac_QuickTimeMovieComponent.mm index 78259b2c4e..8138081349 100644 --- a/build/macosx/platform_specific_code/juce_mac_QuickTimeMovieComponent.mm +++ b/build/macosx/platform_specific_code/juce_mac_QuickTimeMovieComponent.mm @@ -59,6 +59,9 @@ bool QuickTimeMovieComponent::isQuickTimeAvailable() throw() static QTMovie* openMovieFromStream (InputStream* movieStream, File& movieFile) { + // unfortunately, QTMovie objects can only be created on the main thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + QTMovie* movie = 0; FileInputStream* const fin = dynamic_cast (movieStream); diff --git a/build/macosx/platform_specific_code/juce_mac_Strings.mm b/build/macosx/platform_specific_code/juce_mac_Strings.mm index 83f8f2a45f..7374e1d00c 100644 --- a/build/macosx/platform_specific_code/juce_mac_Strings.mm +++ b/build/macosx/platform_specific_code/juce_mac_Strings.mm @@ -165,6 +165,8 @@ const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) } //============================================================================== +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + void SystemClipboard::copyTextToClipboard (const String& text) throw() { [[NSPasteboard generalPasteboard] declareTypes: [NSArray arrayWithObject: NSStringPboardType] @@ -182,5 +184,6 @@ const String SystemClipboard::getTextFromClipboard() throw() : nsStringToJuce (text); } +#endif #endif diff --git a/build/macosx/platform_specific_code/juce_posix_SharedCode.h b/build/macosx/platform_specific_code/juce_posix_SharedCode.h index e2a61f6d60..3666d88eed 100644 --- a/build/macosx/platform_specific_code/juce_posix_SharedCode.h +++ b/build/macosx/platform_specific_code/juce_posix_SharedCode.h @@ -224,12 +224,10 @@ bool juce_canWriteToFile (const String& fileName) throw() bool juce_deleteFile (const String& fileName) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); - if (juce_isDirectory (fileName)) - return rmdir (fileNameUTF8) == 0; + return rmdir ((const char*) fileName.toUTF8()) == 0; else - return remove (fileNameUTF8) == 0; + return remove ((const char*) fileName.toUTF8()) == 0; } bool juce_moveFile (const String& source, const String& dest) throw() @@ -256,14 +254,13 @@ void juce_createDirectory (const String& fileName) throw() void* juce_fileOpen (const String& fileName, bool forWriting) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); int flags = O_RDONLY; if (forWriting) { if (juce_fileExists (fileName, false)) { - const int f = open (fileNameUTF8, O_RDWR, 00644); + const int f = open ((const char*) fileName.toUTF8(), O_RDWR, 00644); if (f != -1) lseek (f, 0, SEEK_END); @@ -276,7 +273,7 @@ void* juce_fileOpen (const String& fileName, bool forWriting) throw() } } - return (void*) open (fileNameUTF8, flags, 00644); + return (void*) open ((const char*) fileName.toUTF8(), flags, 00644); } void juce_fileClose (void* handle) throw() diff --git a/extras/audio plugins/How to use this framework.txt b/extras/audio plugins/How to use this framework.txt index 3f9c0340db..37f39ff0af 100644 --- a/extras/audio plugins/How to use this framework.txt +++ b/extras/audio plugins/How to use this framework.txt @@ -267,7 +267,9 @@ If you also want to build an RTAS, then carry on reading... the "libPluginLibrary.a" item inside it. Drag this subitem down to your Target/"Link Binary With Libraries" build stage and drop it there to add it to the link process. - In your Info.plist, change the "Bundle OS Type Code" to "TDMw", and the "Bundle Creator OS Type Code" to - "PTul". + "PTul". +- You may need to remove the "OTHER_CFLAGS = -x c++" from the RTAS settings file to stop it complaining about + obj-C code You should now be able to build an RTAS! Again, just renaming the finished bundle to ".dpm" and putting it in your RTAS folder should be do the trick. diff --git a/extras/the jucer/src/ui/jucer_ComponentLayoutEditor.cpp b/extras/the jucer/src/ui/jucer_ComponentLayoutEditor.cpp index b373337ec3..85d68eddb8 100644 --- a/extras/the jucer/src/ui/jucer_ComponentLayoutEditor.cpp +++ b/extras/the jucer/src/ui/jucer_ComponentLayoutEditor.cpp @@ -385,7 +385,7 @@ void ComponentLayoutEditor::filesDropped (const StringArray& filenames, int x, i if (newOne != 0) { - JucerComponentHandler::setJucerComponentFile (*layout.getDocument(), newOne, f.getFullPathName()); + JucerComponentHandler::setJucerComponentFile (*layout.getDocument(), newOne, f.getRelativePathFrom (document.getFile().getParentDirectory())); layout.getSelectedSet().selectOnly (newOne); } diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index c3bf356524..0985f2ae40 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -612,6 +612,8 @@ #if MACOS_10_4_OR_EARLIER #include + typedef int NSInteger; + typedef unsigned int NSUInteger; #endif #endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ @@ -630,7 +632,7 @@ #pragma warning (disable: 4309 4305) #endif -#if JUCE_MAC && JUCE_32BIT && JUCE_SUPPORT_CARBON +#if JUCE_MAC && JUCE_32BIT && JUCE_SUPPORT_CARBON && ! JUCE_ONLY_BUILD_CORE_LIBRARY BEGIN_JUCE_NAMESPACE /********* Start of inlined file: juce_mac_CarbonViewWrapperComponent.h *********/ @@ -2778,7 +2780,7 @@ void BitArray::ensureSize (const int numVals) throw() } } -const String BitArray::toString (const int base) const throw() +const String BitArray::toString (const int base, const int minimumNumCharacters) const throw() { String s; BitArray v (*this); @@ -2821,8 +2823,10 @@ const String BitArray::toString (const int base) const throw() return String::empty; } - if (s.isEmpty()) - return T("0"); + const int length = s.length(); + + if (length < minimumNumCharacters) + s = String::repeatedString (T("0"), minimumNumCharacters - length); return isNegative() ? T("-") + s : s; } @@ -12606,6 +12610,20 @@ void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) throw() ignoreCase = shouldIgnoreCase; } +const String StringPairArray::getDescription() const +{ + String s; + + for (int i = 0; i < keys.size(); ++i) + { + s << keys[i] << T(" = ") << values[i]; + if (i < keys.size()) + s << T(", "); + } + + return s; +} + void StringPairArray::minimiseStorageOverheads() throw() { keys.minimiseStorageOverheads(); @@ -19415,8 +19433,6 @@ public: #ifdef WIN32 if (InitializeQTML (0) != noErr) return; -#elif JUCE_MAC - EnterMoviesOnThread (0); #endif if (EnterMovies() != noErr) return; @@ -42497,7 +42513,7 @@ void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, Image* ImageButton::getCurrentImage() const { - if (isDown()) + if (isDown() || getToggleState()) return getDownImage(); if (isOver()) @@ -45312,7 +45328,7 @@ double Slider::getMaxValue() const throw() return valueMax; } -void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) +void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) { // The minimum value only applies to sliders that are in two- or three-value mode. jassert (style == TwoValueHorizontal || style == TwoValueVertical @@ -45321,9 +45337,19 @@ void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const b newValue = constrainedValue (newValue); if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (allowNudgingOfOtherValues && newValue > valueMax) + setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously); + newValue = jmin (valueMax, newValue); + } else + { + if (allowNudgingOfOtherValues && newValue > currentValue) + setValue (newValue, sendUpdateMessage, sendMessageSynchronously); + newValue = jmin (currentValue, newValue); + } if (valueMin != newValue) { @@ -45341,7 +45367,7 @@ void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const b } } -void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) +void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) { // The maximum value only applies to sliders that are in two- or three-value mode. jassert (style == TwoValueHorizontal || style == TwoValueVertical @@ -45350,9 +45376,19 @@ void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const b newValue = constrainedValue (newValue); if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (allowNudgingOfOtherValues && newValue < valueMin) + setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously); + newValue = jmax (valueMin, newValue); + } else + { + if (allowNudgingOfOtherValues && newValue < currentValue) + setValue (newValue, sendUpdateMessage, sendMessageSynchronously); + newValue = jmax (currentValue, newValue); + } if (valueMax != newValue) { @@ -45748,6 +45784,10 @@ void Slider::mouseDown (const MouseEvent& e) { mouseWasHidden = false; incDecDragged = false; + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + mouseDragStartX = e.getMouseDownX(); + mouseDragStartY = e.getMouseDownY(); if (isEnabled()) { @@ -45826,8 +45866,6 @@ void Slider::mouseDown (const MouseEvent& e) minMaxDiff = valueMax - valueMin; - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; lastAngle = rotaryStart + (rotaryEnd - rotaryStart) * valueToProportionOfLength (currentValue); @@ -46016,13 +46054,14 @@ void Slider::mouseDrag (const MouseEvent& e) && valueBox != 0 && valueBox->isEditable()) return; - if (style == IncDecButtons) + if (style == IncDecButtons && ! incDecDragged) { - if (! incDecDragged) - incDecDragged = e.getDistanceFromDragStart() > 10 && ! e.mouseWasClicked(); - - if (! incDecDragged) + if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) return; + + incDecDragged = true; + mouseDragStartX = e.x; + mouseDragStartY = e.y; } if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) @@ -46043,8 +46082,8 @@ void Slider::mouseDrag (const MouseEvent& e) || style == LinearHorizontal || style == LinearBar || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.getDistanceFromDragStartX() - : -e.getDistanceFromDragStartY(); + ? e.x - mouseDragStartX + : mouseDragStartY - e.y; double newPos = valueToProportionOfLength (valueOnMouseDown) + mouseDiff * (1.0 / pixelsForFullDragExtent); @@ -46109,10 +46148,10 @@ void Slider::mouseDrag (const MouseEvent& e) else if (sliderBeingDragged == 1) { setMinValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false); + ! sendChangeOnlyOnRelease, false, true); if (e.mods.isShiftDown()) - setMaxValue (getMinValue() + minMaxDiff, false); + setMaxValue (getMinValue() + minMaxDiff, false, false, true); else minMaxDiff = valueMax - valueMin; } @@ -46121,10 +46160,10 @@ void Slider::mouseDrag (const MouseEvent& e) jassert (sliderBeingDragged == 2); setMaxValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false); + ! sendChangeOnlyOnRelease, false, true); if (e.mods.isShiftDown()) - setMinValue (getMaxValue() - minMaxDiff, false); + setMinValue (getMaxValue() - minMaxDiff, false, false, true); else minMaxDiff = valueMax - valueMin; } @@ -52151,6 +52190,7 @@ TreeViewItem::TreeViewItem() selected (false), redrawNeeded (true), drawLinesInside (true), + drawsInLeftMargin (false), openness (opennessDefault) { static int nextUID = 0; @@ -52425,6 +52465,11 @@ int TreeViewItem::getIndentX() const throw() return x; } +void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) throw() +{ + drawsInLeftMargin = canDrawInLeftMargin; +} + void TreeViewItem::paintRecursively (Graphics& g, int width) { jassert (ownerView != 0); @@ -52499,7 +52544,8 @@ void TreeViewItem::paintRecursively (Graphics& g, int width) g.saveState(); g.setOrigin (indent, 0); - if (g.reduceClipRegion (0, 0, itemW, itemHeight)) + if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0, + drawsInLeftMargin ? itemW + indent : itemW, itemHeight)) paintItem (g, itemW, itemHeight); g.restoreState(); @@ -56669,6 +56715,14 @@ void ComponentBoundsConstrainer::setBoundsForComponent (Component* const compone applyBoundsToComponent (component, x, y, w, h); } +void ComponentBoundsConstrainer::checkComponentBounds (Component* component) +{ + setBoundsForComponent (component, + component->getX(), component->getY(), + component->getWidth(), component->getHeight(), + false, false, false, false); +} + void ComponentBoundsConstrainer::applyBoundsToComponent (Component* component, int x, int y, int w, int h) { @@ -58996,7 +59050,7 @@ const StringArray TabbedButtonBar::getTabNames() const return tabs; } -void TabbedButtonBar::setCurrentTabIndex (int newIndex) +void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_) { if (currentTabIndex != newIndex) { @@ -59014,7 +59068,9 @@ void TabbedButtonBar::setCurrentTabIndex (int newIndex) } resized(); - sendChangeMessage (this); + + if (sendChangeMessage_) + sendChangeMessage (this); currentTabChanged (newIndex, newIndex >= 0 ? tabs [newIndex] : String::empty); } @@ -59396,9 +59452,9 @@ void TabbedComponent::setTabBackgroundColour (const int tabIndex, const Colour& repaint(); } -void TabbedComponent::setCurrentTabIndex (const int newTabIndex) +void TabbedComponent::setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage) { - tabs->setCurrentTabIndex (newTabIndex); + tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage); } int TabbedComponent::getCurrentTabIndex() const @@ -61369,8 +61425,11 @@ void LookAndFeel::drawImageButton (Graphics& g, Image* image, int imageX, int imageY, int imageW, int imageH, const Colour& overlayColour, float imageOpacity, - ImageButton& /*button*/) + ImageButton& button) { + if (! button.isEnabled()) + imageOpacity *= 0.3f; + if (! overlayColour.isOpaque()) { g.setOpacity (imageOpacity); @@ -71847,6 +71906,8 @@ ComponentPeer::~ComponentPeer() { heavyweightPeers.removeValue (this); delete dragAndDropTargetComponent; + + Desktop::getInstance().triggerFocusCallback(); } int ComponentPeer::getNumPeers() throw() @@ -76119,7 +76180,8 @@ void Graphics::drawBevel (const int x, const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour, - const bool useGradient) const throw() + const bool useGradient, + const bool sharpEdgeOnOutside) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); @@ -76131,7 +76193,7 @@ void Graphics::drawBevel (const int x, for (int i = bevelThickness; --i >= 0;) { - const float op = useGradient ? ramp * (bevelThickness - i) + const float op = useGradient ? ramp * (sharpEdgeOnOutside ? bevelThickness - i : i) : oldOpacity; context->fillRectWithColour (x + i, y + i, width - i * 2, 1, topLeftColour.withMultipliedAlpha (op), false); @@ -213882,14 +213944,14 @@ Image* juce_loadJPEGImageFromStream (InputStream& in) throw() } jpeg_finish_decompress (&jpegDecompStruct); + + in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); } jpeg_destroy_decompress (&jpegDecompStruct); } catch (...) {} - - in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); } return image; @@ -254840,12 +254902,10 @@ bool juce_canWriteToFile (const String& fileName) throw() bool juce_deleteFile (const String& fileName) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); - if (juce_isDirectory (fileName)) - return rmdir (fileNameUTF8) == 0; + return rmdir ((const char*) fileName.toUTF8()) == 0; else - return remove (fileNameUTF8) == 0; + return remove ((const char*) fileName.toUTF8()) == 0; } bool juce_moveFile (const String& source, const String& dest) throw() @@ -254872,14 +254932,13 @@ void juce_createDirectory (const String& fileName) throw() void* juce_fileOpen (const String& fileName, bool forWriting) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); int flags = O_RDONLY; if (forWriting) { if (juce_fileExists (fileName, false)) { - const int f = open (fileNameUTF8, O_RDWR, 00644); + const int f = open ((const char*) fileName.toUTF8(), O_RDWR, 00644); if (f != -1) lseek (f, 0, SEEK_END); @@ -254892,7 +254951,7 @@ void* juce_fileOpen (const String& fileName, bool forWriting) throw() } } - return (void*) open (fileNameUTF8, flags, 00644); + return (void*) open ((const char*) fileName.toUTF8(), flags, 00644); } void juce_fileClose (void* handle) throw() @@ -263836,6 +263895,8 @@ const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) return result; } +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + void SystemClipboard::copyTextToClipboard (const String& text) throw() { [[NSPasteboard generalPasteboard] declareTypes: [NSArray arrayWithObject: NSStringPboardType] @@ -263853,6 +263914,8 @@ const String SystemClipboard::getTextFromClipboard() throw() : nsStringToJuce (text); } +#endif + #endif /********* End of inlined file: juce_mac_Strings.mm *********/ @@ -264844,12 +264907,10 @@ bool juce_canWriteToFile (const String& fileName) throw() bool juce_deleteFile (const String& fileName) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); - if (juce_isDirectory (fileName)) - return rmdir (fileNameUTF8) == 0; + return rmdir ((const char*) fileName.toUTF8()) == 0; else - return remove (fileNameUTF8) == 0; + return remove ((const char*) fileName.toUTF8()) == 0; } bool juce_moveFile (const String& source, const String& dest) throw() @@ -264876,14 +264937,13 @@ void juce_createDirectory (const String& fileName) throw() void* juce_fileOpen (const String& fileName, bool forWriting) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); int flags = O_RDONLY; if (forWriting) { if (juce_fileExists (fileName, false)) { - const int f = open (fileNameUTF8, O_RDWR, 00644); + const int f = open ((const char*) fileName.toUTF8(), O_RDWR, 00644); if (f != -1) lseek (f, 0, SEEK_END); @@ -264896,7 +264956,7 @@ void* juce_fileOpen (const String& fileName, bool forWriting) throw() } } - return (void*) open (fileNameUTF8, flags, 00644); + return (void*) open ((const char*) fileName.toUTF8(), flags, 00644); } void juce_fileClose (void* handle) throw() @@ -265665,6 +265725,8 @@ void PlatformUtilities::beep() NSBeep(); } +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + bool AlertWindow::showNativeDialogBox (const String& title, const String& bodyText, bool isOkCancel) @@ -265851,6 +265913,9 @@ void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool c } #endif + +#endif + /********* End of inlined file: juce_mac_MiscUtilities.mm *********/ /********* Start of inlined file: juce_mac_Debugging.mm *********/ @@ -268784,6 +268849,9 @@ bool QuickTimeMovieComponent::isQuickTimeAvailable() throw() static QTMovie* openMovieFromStream (InputStream* movieStream, File& movieFile) { + // unfortunately, QTMovie objects can only be created on the main thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + QTMovie* movie = 0; FileInputStream* const fin = dynamic_cast (movieStream); diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 1cf3ce1229..23211948e5 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -7309,8 +7309,11 @@ public: /** Converts the array to a number string. Specify a base such as 2 (binary), 8 (octal), 10 (decimal), 16 (hex). + + If minuimumNumCharacters is greater than 0, the returned string will be + padded with leading zeros to reach at least that length. */ - const String toString (const int base) const throw(); + const String toString (const int base, const int minimumNumCharacters = 1) const throw(); /** Converts a number string to an array. @@ -8107,6 +8110,12 @@ public: */ void setIgnoresCase (const bool shouldIgnoreCase) throw(); + /** Returns a descriptive string containing the items. + + This is handy for dumping the contents of an array. + */ + const String getDescription() const; + /** Reduces the amount of storage being used by the array. Arrays typically allocate slightly more storage than they need, and after @@ -19582,6 +19591,11 @@ public: The top-left colour is used for the top- and left-hand edges of the bevel; the bottom-right colour is used for the bottom- and right-hand edges. + + If useGradient is true, then the bevel fades out to make it look more curved + and less angular. If sharpEdgeOnOutside is true, the outside of the bevel is + sharp, and it fades towards the centre; if sharpEdgeOnOutside is false, then + the centre edges are sharp and it fades towards the outside. */ void drawBevel (const int x, const int y, @@ -19590,7 +19604,8 @@ public: const int bevelThickness, const Colour& topLeftColour = Colours::white, const Colour& bottomRightColour = Colours::black, - const bool useGradient = true) const throw(); + const bool useGradient = true, + const bool sharpEdgeOnOutside = true) const throw(); /** Draws a pixel using the current colour or brush. */ @@ -37968,7 +37983,7 @@ public: { MessageManagerLock mml (Thread::getCurrentThread()); - if (! mml.lockWasGained) + if (! mml.lockWasGained()) return; // another thread is trying to kill us! ..do some locked stuff here.. @@ -43250,6 +43265,21 @@ public: */ virtual const String getDragSourceDescription(); + /** Sets a flag to indicate that the item wants to be allowed + to draw all the way across to the left edge of the treeview. + + By default this is false, which means that when the paintItem() + method is called, its graphics context is clipped to only allow + drawing within the item's rectangle. If this flag is set to true, + then the graphics context isn't clipped on its left side, so it + can draw all the way across to the left margin. Note that the + context will still have its origin in the same place though, so + the coordinates of anything to its left will be negative. It's + mostly useful if you want to draw a wider bar behind the + highlighted item. + */ + void setDrawsInLeftMargin (bool canDrawInLeftMargin) throw(); + juce_UseDebuggingNewOperator private: @@ -43261,6 +43291,7 @@ private: bool selected : 1; bool redrawNeeded : 1; bool drawLinesInside : 1; + bool drawsInLeftMargin : 1; unsigned int openness : 2; friend class TreeView; @@ -44025,6 +44056,11 @@ public: const bool isStretchingBottom, const bool isStretchingRight); + /** Performs a check on the current size of a component, and moves or resizes + it if it fails the constraints. + */ + void checkComponentBounds (Component* component); + /** Called by setBoundsForComponent() to apply a new constrained size to a component. @@ -45311,18 +45347,22 @@ public: want to handle it. @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and the max value (in a two-value - slider) or the mid value (in a three-value slider), and - will be snapped to the nearest interval if one has been set. + minimum and maximum range, and will be snapped to the nearest + interval if one has been set. @param sendUpdateMessage if false, a change to the value will not trigger a call to any SliderListeners or the valueChanged() method @param sendMessageSynchronously if true, then a call to the SliderListeners will be made synchronously; if false, it will be asynchronous + @param allowNudgingOfOtherValues if false, this value will be restricted to being below the + max value (in a two-value slider) or the mid value (in a three-value + slider). If false, then if this value goes beyond those values, + it will push them along with it. @see getMinValue, setMaxValue, setValue */ void setMinValue (double newValue, const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false); + const bool sendMessageSynchronously = false, + const bool allowNudgingOfOtherValues = false); /** For a slider with two or three thumbs, this returns the higher of its values. @@ -45341,18 +45381,22 @@ public: want to handle it. @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and the max value (in a two-value - slider) or the mid value (in a three-value slider), and - will be snapped to the nearest interval if one has been set. + minimum and maximum range, and will be snapped to the nearest + interval if one has been set. @param sendUpdateMessage if false, a change to the value will not trigger a call to any SliderListeners or the valueChanged() method @param sendMessageSynchronously if true, then a call to the SliderListeners will be made synchronously; if false, it will be asynchronous + @param allowNudgingOfOtherValues if false, this value will be restricted to being above the + min value (in a two-value slider) or the mid value (in a three-value + slider). If false, then if this value goes beyond those values, + it will push them along with it. @see getMaxValue, setMinValue, setValue */ void setMaxValue (double newValue, const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false); + const bool sendMessageSynchronously = false, + const bool allowNudgingOfOtherValues = false); /** Adds a listener to be called when this slider's value changes. */ void addListener (SliderListener* const listener) throw(); @@ -45625,6 +45669,7 @@ private: int velocityModeThreshold; float rotaryStart, rotaryEnd; int numDecimalPlaces, mouseXWhenLastDragged, mouseYWhenLastDragged; + int mouseDragStartX, mouseDragStartY; int sliderRegionStart, sliderRegionSize; int sliderBeingDragged; int pixelsForFullDragExtent; @@ -47134,7 +47179,7 @@ public: To deselect all the tabs, use an index of -1. */ - void setCurrentTabIndex (int newTabIndex); + void setCurrentTabIndex (int newTabIndex, const bool sendChangeMessage = true); /** Returns the name of the currently selected tab. @@ -47353,7 +47398,7 @@ public: @see TabbedButtonBar::setCurrentTabIndex */ - void setCurrentTabIndex (const int newTabIndex); + void setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage = true); /** Returns the index of the currently selected tab. diff --git a/src/juce_amalgamated_template.cpp b/src/juce_amalgamated_template.cpp index ac35c6060f..e9e588756f 100644 --- a/src/juce_amalgamated_template.cpp +++ b/src/juce_amalgamated_template.cpp @@ -66,7 +66,7 @@ #pragma warning (disable: 4309 4305) #endif -#if JUCE_MAC && JUCE_32BIT && JUCE_SUPPORT_CARBON +#if JUCE_MAC && JUCE_32BIT && JUCE_SUPPORT_CARBON && ! JUCE_ONLY_BUILD_CORE_LIBRARY BEGIN_JUCE_NAMESPACE #include "../build/macosx/platform_specific_code/juce_mac_CarbonViewWrapperComponent.h" END_JUCE_NAMESPACE diff --git a/src/juce_appframework/audio/audio_file_formats/juce_QuickTimeAudioFormat.cpp b/src/juce_appframework/audio/audio_file_formats/juce_QuickTimeAudioFormat.cpp index 95e3372d5f..498212872d 100644 --- a/src/juce_appframework/audio/audio_file_formats/juce_QuickTimeAudioFormat.cpp +++ b/src/juce_appframework/audio/audio_file_formats/juce_QuickTimeAudioFormat.cpp @@ -97,8 +97,6 @@ public: #ifdef WIN32 if (InitializeQTML (0) != noErr) return; -#elif JUCE_MAC - EnterMoviesOnThread (0); #endif if (EnterMovies() != noErr) return; diff --git a/src/juce_appframework/events/juce_MessageManager.h b/src/juce_appframework/events/juce_MessageManager.h index ef3bf458bc..088930c02b 100644 --- a/src/juce_appframework/events/juce_MessageManager.h +++ b/src/juce_appframework/events/juce_MessageManager.h @@ -279,7 +279,7 @@ public: { MessageManagerLock mml (Thread::getCurrentThread()); - if (! mml.lockWasGained) + if (! mml.lockWasGained()) return; // another thread is trying to kill us! ..do some locked stuff here.. diff --git a/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp b/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp index 4ba123f337..fc4e1256aa 100644 --- a/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp +++ b/src/juce_appframework/gui/components/buttons/juce_ImageButton.cpp @@ -1,242 +1,242 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_ImageButton.h" -#include "../../graphics/imaging/juce_ImageCache.h" -#include "../lookandfeel/juce_LookAndFeel.h" - - -//============================================================================== -ImageButton::ImageButton (const String& text) - : Button (text), - scaleImageToFit (true), - preserveProportions (true), - alphaThreshold (0), - imageX (0), - imageY (0), - imageW (0), - imageH (0), - normalImage (0), - overImage (0), - downImage (0) -{ -} - -ImageButton::~ImageButton() -{ - deleteImages(); -} - -void ImageButton::deleteImages() -{ - if (normalImage != 0) - { - if (ImageCache::isImageInCache (normalImage)) - ImageCache::release (normalImage); - else - delete normalImage; - } - - if (overImage != 0) - { - if (ImageCache::isImageInCache (overImage)) - ImageCache::release (overImage); - else - delete overImage; - } - - if (downImage != 0) - { - if (ImageCache::isImageInCache (downImage)) - ImageCache::release (downImage); - else - delete downImage; - } -} - -void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, - const bool rescaleImagesWhenButtonSizeChanges, - const bool preserveImageProportions, - Image* const normalImage_, - const float imageOpacityWhenNormal, - const Colour& overlayColourWhenNormal, - Image* const overImage_, - const float imageOpacityWhenOver, - const Colour& overlayColourWhenOver, - Image* const downImage_, - const float imageOpacityWhenDown, - const Colour& overlayColourWhenDown, - const float hitTestAlphaThreshold) -{ - deleteImages(); - - normalImage = normalImage_; - overImage = overImage_; - downImage = downImage_; - - if (resizeButtonNowToFitThisImage && normalImage != 0) - { - imageW = normalImage->getWidth(); - imageH = normalImage->getHeight(); - - setSize (imageW, imageH); - } - - scaleImageToFit = rescaleImagesWhenButtonSizeChanges; - preserveProportions = preserveImageProportions; - - normalOpacity = imageOpacityWhenNormal; - normalOverlay = overlayColourWhenNormal; - overOpacity = imageOpacityWhenOver; - overOverlay = overlayColourWhenOver; - downOpacity = imageOpacityWhenDown; - downOverlay = overlayColourWhenDown; - - alphaThreshold = (unsigned char) jlimit (0, 0xff, roundFloatToInt (255.0f * hitTestAlphaThreshold)); - - repaint(); -} - -Image* ImageButton::getCurrentImage() const -{ - if (isDown()) - return getDownImage(); - - if (isOver()) - return getOverImage(); - - return getNormalImage(); -} - -Image* ImageButton::getNormalImage() const throw() -{ - return normalImage; -} - -Image* ImageButton::getOverImage() const throw() -{ - return (overImage != 0) ? overImage - : normalImage; -} - -Image* ImageButton::getDownImage() const throw() -{ - return (downImage != 0) ? downImage - : getOverImage(); -} - -void ImageButton::paintButton (Graphics& g, - bool isMouseOverButton, - bool isButtonDown) -{ - if (! isEnabled()) - { - isMouseOverButton = false; - isButtonDown = false; - } - - Image* const im = getCurrentImage(); - - if (im != 0) - { - const int iw = im->getWidth(); - const int ih = im->getHeight(); - imageW = getWidth(); - imageH = getHeight(); - imageX = (imageW - iw) >> 1; - imageY = (imageH - ih) >> 1; - - if (scaleImageToFit) - { - if (preserveProportions) - { - int newW, newH; - const float imRatio = ih / (float)iw; - const float destRatio = imageH / (float)imageW; - - if (imRatio > destRatio) - { - newW = roundFloatToInt (imageH / imRatio); - newH = imageH; - } - else - { - newW = imageW; - newH = roundFloatToInt (imageW * imRatio); - } - - imageX = (imageW - newW) / 2; - imageY = (imageH - newH) / 2; - imageW = newW; - imageH = newH; - } - else - { - imageX = 0; - imageY = 0; - } - } - - if (! scaleImageToFit) - { - imageW = iw; - imageH = ih; - } - - getLookAndFeel().drawImageButton (g, im, imageX, imageY, imageW, imageH, - isButtonDown ? downOverlay - : (isMouseOverButton ? overOverlay - : normalOverlay), - isButtonDown ? downOpacity - : (isMouseOverButton ? overOpacity - : normalOpacity), - *this); - } -} - -bool ImageButton::hitTest (int x, int y) -{ - if (alphaThreshold == 0) - return true; - - Image* const im = getCurrentImage(); - - return im == 0 - || (imageW > 0 && imageH > 0 - && alphaThreshold < im->getPixelAt (((x - imageX) * im->getWidth()) / imageW, - ((y - imageY) * im->getHeight()) / imageH).getAlpha()); -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_ImageButton.h" +#include "../../graphics/imaging/juce_ImageCache.h" +#include "../lookandfeel/juce_LookAndFeel.h" + + +//============================================================================== +ImageButton::ImageButton (const String& text) + : Button (text), + scaleImageToFit (true), + preserveProportions (true), + alphaThreshold (0), + imageX (0), + imageY (0), + imageW (0), + imageH (0), + normalImage (0), + overImage (0), + downImage (0) +{ +} + +ImageButton::~ImageButton() +{ + deleteImages(); +} + +void ImageButton::deleteImages() +{ + if (normalImage != 0) + { + if (ImageCache::isImageInCache (normalImage)) + ImageCache::release (normalImage); + else + delete normalImage; + } + + if (overImage != 0) + { + if (ImageCache::isImageInCache (overImage)) + ImageCache::release (overImage); + else + delete overImage; + } + + if (downImage != 0) + { + if (ImageCache::isImageInCache (downImage)) + ImageCache::release (downImage); + else + delete downImage; + } +} + +void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, + const bool rescaleImagesWhenButtonSizeChanges, + const bool preserveImageProportions, + Image* const normalImage_, + const float imageOpacityWhenNormal, + const Colour& overlayColourWhenNormal, + Image* const overImage_, + const float imageOpacityWhenOver, + const Colour& overlayColourWhenOver, + Image* const downImage_, + const float imageOpacityWhenDown, + const Colour& overlayColourWhenDown, + const float hitTestAlphaThreshold) +{ + deleteImages(); + + normalImage = normalImage_; + overImage = overImage_; + downImage = downImage_; + + if (resizeButtonNowToFitThisImage && normalImage != 0) + { + imageW = normalImage->getWidth(); + imageH = normalImage->getHeight(); + + setSize (imageW, imageH); + } + + scaleImageToFit = rescaleImagesWhenButtonSizeChanges; + preserveProportions = preserveImageProportions; + + normalOpacity = imageOpacityWhenNormal; + normalOverlay = overlayColourWhenNormal; + overOpacity = imageOpacityWhenOver; + overOverlay = overlayColourWhenOver; + downOpacity = imageOpacityWhenDown; + downOverlay = overlayColourWhenDown; + + alphaThreshold = (unsigned char) jlimit (0, 0xff, roundFloatToInt (255.0f * hitTestAlphaThreshold)); + + repaint(); +} + +Image* ImageButton::getCurrentImage() const +{ + if (isDown() || getToggleState()) + return getDownImage(); + + if (isOver()) + return getOverImage(); + + return getNormalImage(); +} + +Image* ImageButton::getNormalImage() const throw() +{ + return normalImage; +} + +Image* ImageButton::getOverImage() const throw() +{ + return (overImage != 0) ? overImage + : normalImage; +} + +Image* ImageButton::getDownImage() const throw() +{ + return (downImage != 0) ? downImage + : getOverImage(); +} + +void ImageButton::paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) +{ + if (! isEnabled()) + { + isMouseOverButton = false; + isButtonDown = false; + } + + Image* const im = getCurrentImage(); + + if (im != 0) + { + const int iw = im->getWidth(); + const int ih = im->getHeight(); + imageW = getWidth(); + imageH = getHeight(); + imageX = (imageW - iw) >> 1; + imageY = (imageH - ih) >> 1; + + if (scaleImageToFit) + { + if (preserveProportions) + { + int newW, newH; + const float imRatio = ih / (float)iw; + const float destRatio = imageH / (float)imageW; + + if (imRatio > destRatio) + { + newW = roundFloatToInt (imageH / imRatio); + newH = imageH; + } + else + { + newW = imageW; + newH = roundFloatToInt (imageW * imRatio); + } + + imageX = (imageW - newW) / 2; + imageY = (imageH - newH) / 2; + imageW = newW; + imageH = newH; + } + else + { + imageX = 0; + imageY = 0; + } + } + + if (! scaleImageToFit) + { + imageW = iw; + imageH = ih; + } + + getLookAndFeel().drawImageButton (g, im, imageX, imageY, imageW, imageH, + isButtonDown ? downOverlay + : (isMouseOverButton ? overOverlay + : normalOverlay), + isButtonDown ? downOpacity + : (isMouseOverButton ? overOpacity + : normalOpacity), + *this); + } +} + +bool ImageButton::hitTest (int x, int y) +{ + if (alphaThreshold == 0) + return true; + + Image* const im = getCurrentImage(); + + return im == 0 + || (imageW > 0 && imageH > 0 + && alphaThreshold < im->getPixelAt (((x - imageX) * im->getWidth()) / imageW, + ((y - imageY) * im->getHeight()) / imageH).getAlpha()); +} + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.cpp b/src/juce_appframework/gui/components/controls/juce_Slider.cpp index 28c8db80cd..8d2b466131 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.cpp +++ b/src/juce_appframework/gui/components/controls/juce_Slider.cpp @@ -1,1394 +1,1416 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_Slider.h" -#include "../lookandfeel/juce_LookAndFeel.h" -#include "../menus/juce_PopupMenu.h" -#include "../juce_Desktop.h" -#include "../special/juce_BubbleComponent.h" -#include "../../../../juce_core/text/juce_LocalisedStrings.h" - - -//============================================================================== -class SliderPopupDisplayComponent : public BubbleComponent -{ -public: - //============================================================================== - SliderPopupDisplayComponent (Slider* const owner_) - : owner (owner_), - font (15.0f, Font::bold) - { - setAlwaysOnTop (true); - } - - ~SliderPopupDisplayComponent() - { - } - - void paintContent (Graphics& g, int w, int h) - { - g.setFont (font); - g.setColour (Colours::black); - - g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1); - } - - void getContentSize (int& w, int& h) - { - w = font.getStringWidth (text) + 18; - h = (int) (font.getHeight() * 1.6f); - } - - void updatePosition (const String& newText) - { - if (text != newText) - { - text = newText; - repaint(); - } - - BubbleComponent::setPosition (owner); - } - - //============================================================================== - juce_UseDebuggingNewOperator - -private: - Slider* owner; - Font font; - String text; - - SliderPopupDisplayComponent (const SliderPopupDisplayComponent&); - const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); -}; - -//============================================================================== -Slider::Slider (const String& name) - : Component (name), - listeners (2), - currentValue (0.0), - valueMin (0.0), - valueMax (0.0), - minimum (0), - maximum (10), - interval (0), - skewFactor (1.0), - velocityModeSensitivity (1.0), - velocityModeOffset (0.0), - velocityModeThreshold (1), - rotaryStart (float_Pi * 1.2f), - rotaryEnd (float_Pi * 2.8f), - numDecimalPlaces (7), - sliderRegionStart (0), - sliderRegionSize (1), - sliderBeingDragged (-1), - pixelsForFullDragExtent (250), - style (LinearHorizontal), - textBoxPos (TextBoxLeft), - textBoxWidth (80), - textBoxHeight (20), - incDecButtonMode (incDecButtonsNotDraggable), - editableText (true), - doubleClickToValue (false), - isVelocityBased (false), - userKeyOverridesVelocity (true), - rotaryStop (true), - incDecButtonsSideBySide (false), - sendChangeOnlyOnRelease (false), - popupDisplayEnabled (false), - menuEnabled (false), - menuShown (false), - scrollWheelEnabled (true), - snapsToMousePos (true), - valueBox (0), - incButton (0), - decButton (0), - popupDisplay (0), - parentForPopupDisplay (0) -{ - setWantsKeyboardFocus (false); - setRepaintsOnMouseActivity (true); - - lookAndFeelChanged(); - updateText(); -} - -Slider::~Slider() -{ - deleteAndZero (popupDisplay); - deleteAllChildren(); -} - - -//============================================================================== -void Slider::handleAsyncUpdate() -{ - cancelPendingUpdate(); - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderValueChanged (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::sendDragStart() -{ - startedDragging(); - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderDragStarted (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::sendDragEnd() -{ - stoppedDragging(); - - sliderBeingDragged = -1; - - for (int i = listeners.size(); --i >= 0;) - { - ((SliderListener*) listeners.getUnchecked (i))->sliderDragEnded (this); - i = jmin (i, listeners.size()); - } -} - -void Slider::addListener (SliderListener* const listener) throw() -{ - jassert (listener != 0); - if (listener != 0) - listeners.add (listener); -} - -void Slider::removeListener (SliderListener* const listener) throw() -{ - listeners.removeValue (listener); -} - -//============================================================================== -void Slider::setSliderStyle (const SliderStyle newStyle) -{ - if (style != newStyle) - { - style = newStyle; - repaint(); - lookAndFeelChanged(); - } -} - -void Slider::setRotaryParameters (const float startAngleRadians, - const float endAngleRadians, - const bool stopAtEnd) -{ - // make sure the values are sensible.. - jassert (rotaryStart >= 0 && rotaryEnd >= 0); - jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); - jassert (rotaryStart < rotaryEnd); - - rotaryStart = startAngleRadians; - rotaryEnd = endAngleRadians; - rotaryStop = stopAtEnd; -} - -void Slider::setVelocityBasedMode (const bool velBased) throw() -{ - isVelocityBased = velBased; -} - -void Slider::setVelocityModeParameters (const double sensitivity, - const int threshold, - const double offset, - const bool userCanPressKeyToSwapMode) throw() -{ - jassert (threshold >= 0); - jassert (sensitivity > 0); - jassert (offset >= 0); - - velocityModeSensitivity = sensitivity; - velocityModeOffset = offset; - velocityModeThreshold = threshold; - userKeyOverridesVelocity = userCanPressKeyToSwapMode; -} - -void Slider::setSkewFactor (const double factor) throw() -{ - skewFactor = factor; -} - -void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw() -{ - if (maximum > minimum) - skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum) - / (maximum - minimum)); -} - -void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag) -{ - jassert (distanceForFullScaleDrag > 0); - - pixelsForFullDragExtent = distanceForFullScaleDrag; -} - -void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) -{ - if (incDecButtonMode != mode) - { - incDecButtonMode = mode; - lookAndFeelChanged(); - } -} - -void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, - const bool isReadOnly, - const int textEntryBoxWidth, - const int textEntryBoxHeight) -{ - textBoxPos = newPosition; - editableText = ! isReadOnly; - textBoxWidth = textEntryBoxWidth; - textBoxHeight = textEntryBoxHeight; - - repaint(); - lookAndFeelChanged(); -} - -void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw() -{ - editableText = shouldBeEditable; - - if (valueBox != 0) - valueBox->setEditable (shouldBeEditable && isEnabled()); -} - -void Slider::showTextBox() -{ - jassert (editableText); // this should probably be avoided in read-only sliders. - - if (valueBox != 0) - valueBox->showEditor(); -} - -void Slider::hideTextBox (const bool discardCurrentEditorContents) -{ - if (valueBox != 0) - { - valueBox->hideEditor (discardCurrentEditorContents); - - if (discardCurrentEditorContents) - updateText(); - } -} - -void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() -{ - sendChangeOnlyOnRelease = onlyNotifyOnRelease; -} - -void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw() -{ - snapsToMousePos = shouldSnapToMouse; -} - -void Slider::setPopupDisplayEnabled (const bool enabled, - Component* const parentComponentToUse) throw() -{ - popupDisplayEnabled = enabled; - parentForPopupDisplay = parentComponentToUse; -} - -//============================================================================== -void Slider::colourChanged() -{ - lookAndFeelChanged(); -} - -void Slider::lookAndFeelChanged() -{ - const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() - : getTextFromValue (currentValue)); - - deleteAllChildren(); - valueBox = 0; - - LookAndFeel& lf = getLookAndFeel(); - - if (textBoxPos != NoTextBox) - { - addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this)); - - valueBox->setWantsKeyboardFocus (false); - valueBox->setText (previousTextBoxContent, false); - - valueBox->setEditable (editableText && isEnabled()); - valueBox->addListener (this); - - if (style == LinearBar) - valueBox->addMouseListener (this, false); - - valueBox->setTooltip (getTooltip()); - } - - if (style == IncDecButtons) - { - addAndMakeVisible (incButton = lf.createSliderButton (true)); - incButton->addButtonListener (this); - - addAndMakeVisible (decButton = lf.createSliderButton (false)); - decButton->addButtonListener (this); - - if (incDecButtonMode != incDecButtonsNotDraggable) - { - incButton->addMouseListener (this, false); - decButton->addMouseListener (this, false); - } - else - { - incButton->setRepeatSpeed (300, 100, 20); - incButton->addMouseListener (decButton, false); - - decButton->setRepeatSpeed (300, 100, 20); - decButton->addMouseListener (incButton, false); - } - } - - setComponentEffect (lf.getSliderEffect()); - - resized(); - repaint(); -} - -//============================================================================== -void Slider::setRange (const double newMin, - const double newMax, - const double newInt) -{ - if (minimum != newMin - || maximum != newMax - || interval != newInt) - { - minimum = newMin; - maximum = newMax; - interval = newInt; - - // figure out the number of DPs needed to display all values at this - // interval setting. - numDecimalPlaces = 7; - - if (newInt != 0) - { - int v = abs ((int) (newInt * 10000000)); - - while ((v % 10) == 0) - { - --numDecimalPlaces; - v /= 10; - } - } - - // keep the current values inside the new range.. - if (style != TwoValueHorizontal && style != TwoValueVertical) - { - setValue (currentValue, false, false); - } - else - { - setMinValue (getMinValue(), false, false); - setMaxValue (getMaxValue(), false, false); - } - - updateText(); - } -} - -void Slider::triggerChangeMessage (const bool synchronous) -{ - if (synchronous) - handleAsyncUpdate(); - else - triggerAsyncUpdate(); - - valueChanged(); -} - -double Slider::getValue() const throw() -{ - // for a two-value style slider, you should use the getMinValue() and getMaxValue() - // methods to get the two values. - jassert (style != TwoValueHorizontal && style != TwoValueVertical); - - return currentValue; -} - -void Slider::setValue (double newValue, - const bool sendUpdateMessage, - const bool sendMessageSynchronously) -{ - // for a two-value style slider, you should use the setMinValue() and setMaxValue() - // methods to set the two values. - jassert (style != TwoValueHorizontal && style != TwoValueVertical); - - newValue = constrainedValue (newValue); - - if (style == ThreeValueHorizontal || style == ThreeValueVertical) - { - jassert (valueMin <= valueMax); - newValue = jlimit (valueMin, valueMax, newValue); - } - - if (currentValue != newValue) - { - if (valueBox != 0) - valueBox->hideEditor (true); - - currentValue = newValue; - updateText(); - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (currentValue)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -double Slider::getMinValue() const throw() -{ - // The minimum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - return valueMin; -} - -double Slider::getMaxValue() const throw() -{ - // The maximum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - return valueMax; -} - -void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) -{ - // The minimum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - newValue = constrainedValue (newValue); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - newValue = jmin (valueMax, newValue); - else - newValue = jmin (currentValue, newValue); - - if (valueMin != newValue) - { - valueMin = newValue; - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMin)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) -{ - // The maximum value only applies to sliders that are in two- or three-value mode. - jassert (style == TwoValueHorizontal || style == TwoValueVertical - || style == ThreeValueHorizontal || style == ThreeValueVertical); - - newValue = constrainedValue (newValue); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - newValue = jmax (valueMin, newValue); - else - newValue = jmax (currentValue, newValue); - - if (valueMax != newValue) - { - valueMax = newValue; - repaint(); - - if (popupDisplay != 0) - { - ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMax)); - popupDisplay->repaint(); - } - - if (sendUpdateMessage) - triggerChangeMessage (sendMessageSynchronously); - } -} - -void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick) throw() -{ - doubleClickToValue = isDoubleClickEnabled; - doubleClickReturnValue = valueToSetOnDoubleClick; -} - -double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() -{ - isEnabled_ = doubleClickToValue; - return doubleClickReturnValue; -} - -void Slider::updateText() -{ - if (valueBox != 0) - valueBox->setText (getTextFromValue (currentValue), false); -} - -void Slider::setTextValueSuffix (const String& suffix) -{ - if (textSuffix != suffix) - { - textSuffix = suffix; - updateText(); - } -} - -const String Slider::getTextFromValue (double v) -{ - if (numDecimalPlaces > 0) - return String (v, numDecimalPlaces) + textSuffix; - else - return String (roundDoubleToInt (v)) + textSuffix; -} - -double Slider::getValueFromText (const String& text) -{ - String t (text.trimStart()); - - if (t.endsWith (textSuffix)) - t = t.substring (0, t.length() - textSuffix.length()); - - while (t.startsWithChar (T('+'))) - t = t.substring (1).trimStart(); - - return t.initialSectionContainingOnly (T("0123456789.,-")) - .getDoubleValue(); -} - -double Slider::proportionOfLengthToValue (double proportion) -{ - if (skewFactor != 1.0 && proportion > 0.0) - proportion = exp (log (proportion) / skewFactor); - - return minimum + (maximum - minimum) * proportion; -} - -double Slider::valueToProportionOfLength (double value) -{ - const double n = (value - minimum) / (maximum - minimum); - - return skewFactor == 1.0 ? n : pow (n, skewFactor); -} - -double Slider::snapValue (double attemptedValue, const bool) -{ - return attemptedValue; -} - -//============================================================================== -void Slider::startedDragging() -{ -} - -void Slider::stoppedDragging() -{ -} - -void Slider::valueChanged() -{ -} - -//============================================================================== -void Slider::enablementChanged() -{ - repaint(); -} - -void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() -{ - menuEnabled = menuEnabled_; -} - -void Slider::setScrollWheelEnabled (const bool enabled) throw() -{ - scrollWheelEnabled = enabled; -} - -//============================================================================== -void Slider::labelTextChanged (Label* label) -{ - const double newValue = snapValue (getValueFromText (label->getText()), false); - - if (getValue() != newValue) - { - sendDragStart(); - setValue (newValue, true, true); - sendDragEnd(); - } - - updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this. -} - -void Slider::buttonClicked (Button* button) -{ - if (style == IncDecButtons) - { - sendDragStart(); - - if (button == incButton) - setValue (snapValue (getValue() + interval, false), true, true); - else if (button == decButton) - setValue (snapValue (getValue() - interval, false), true, true); - - sendDragEnd(); - } -} - -//============================================================================== -double Slider::constrainedValue (double value) const throw() -{ - if (interval > 0) - value = minimum + interval * floor ((value - minimum) / interval + 0.5); - - if (value <= minimum || maximum <= minimum) - value = minimum; - else if (value >= maximum) - value = maximum; - - return value; -} - -float Slider::getLinearSliderPos (const double value) -{ - double sliderPosProportional; - - if (maximum > minimum) - { - if (value < minimum) - { - sliderPosProportional = 0.0; - } - else if (value > maximum) - { - sliderPosProportional = 1.0; - } - else - { - sliderPosProportional = valueToProportionOfLength (value); - jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0); - } - } - else - { - sliderPosProportional = 0.5; - } - - if (isVertical() || style == IncDecButtons) - sliderPosProportional = 1.0 - sliderPosProportional; - - return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize); -} - -bool Slider::isHorizontal() const throw() -{ - return style == LinearHorizontal - || style == LinearBar - || style == TwoValueHorizontal - || style == ThreeValueHorizontal; -} - -bool Slider::isVertical() const throw() -{ - return style == LinearVertical - || style == TwoValueVertical - || style == ThreeValueVertical; -} - -bool Slider::incDecDragDirectionIsHorizontal() const throw() -{ - return incDecButtonMode == incDecButtonsDraggable_Horizontal - || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide); -} - -float Slider::getPositionOfValue (const double value) -{ - if (isHorizontal() || isVertical()) - { - return getLinearSliderPos (value); - } - else - { - jassertfalse // not a valid call on a slider that doesn't work linearly! - return 0.0f; - } -} - -//============================================================================== -void Slider::paint (Graphics& g) -{ - if (style != IncDecButtons) - { - if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - const float sliderPos = (float) valueToProportionOfLength (currentValue); - jassert (sliderPos >= 0 && sliderPos <= 1.0f); - - getLookAndFeel().drawRotarySlider (g, - sliderRect.getX(), - sliderRect.getY(), - sliderRect.getWidth(), - sliderRect.getHeight(), - sliderPos, - rotaryStart, rotaryEnd, - *this); - } - else - { - getLookAndFeel().drawLinearSlider (g, - sliderRect.getX(), - sliderRect.getY(), - sliderRect.getWidth(), - sliderRect.getHeight(), - getLinearSliderPos (currentValue), - getLinearSliderPos (valueMin), - getLinearSliderPos (valueMax), - style, - *this); - } - - if (style == LinearBar && valueBox == 0) - { - g.setColour (findColour (Slider::textBoxOutlineColourId)); - g.drawRect (0, 0, getWidth(), getHeight(), 1); - } - } -} - -void Slider::resized() -{ - int minXSpace = 0; - int minYSpace = 0; - - if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) - minXSpace = 30; - else - minYSpace = 15; - - const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace)); - const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace)); - - if (style == LinearBar) - { - if (valueBox != 0) - valueBox->setBounds (0, 0, getWidth(), getHeight()); - } - else - { - if (textBoxPos == NoTextBox) - { - sliderRect.setBounds (0, 0, getWidth(), getHeight()); - } - else if (textBoxPos == TextBoxLeft) - { - valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh); - sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight()); - } - else if (textBoxPos == TextBoxRight) - { - valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh); - sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight()); - } - else if (textBoxPos == TextBoxAbove) - { - valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh); - sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh); - } - else if (textBoxPos == TextBoxBelow) - { - valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh); - sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh); - } - } - - const int indent = getLookAndFeel().getSliderThumbRadius (*this); - - if (style == LinearBar) - { - const int barIndent = 1; - sliderRegionStart = barIndent; - sliderRegionSize = getWidth() - barIndent * 2; - - sliderRect.setBounds (sliderRegionStart, barIndent, - sliderRegionSize, getHeight() - barIndent * 2); - } - else if (isHorizontal()) - { - sliderRegionStart = sliderRect.getX() + indent; - sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2); - - sliderRect.setBounds (sliderRegionStart, sliderRect.getY(), - sliderRegionSize, sliderRect.getHeight()); - } - else if (isVertical()) - { - sliderRegionStart = sliderRect.getY() + indent; - sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2); - - sliderRect.setBounds (sliderRect.getX(), sliderRegionStart, - sliderRect.getWidth(), sliderRegionSize); - } - else - { - sliderRegionStart = 0; - sliderRegionSize = 100; - } - - if (style == IncDecButtons) - { - Rectangle buttonRect (sliderRect); - - if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) - buttonRect.expand (-2, 0); - else - buttonRect.expand (0, -2); - - incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight(); - - if (incDecButtonsSideBySide) - { - decButton->setBounds (buttonRect.getX(), - buttonRect.getY(), - buttonRect.getWidth() / 2, - buttonRect.getHeight()); - - decButton->setConnectedEdges (Button::ConnectedOnRight); - - incButton->setBounds (buttonRect.getCentreX(), - buttonRect.getY(), - buttonRect.getWidth() / 2, - buttonRect.getHeight()); - - incButton->setConnectedEdges (Button::ConnectedOnLeft); - } - else - { - incButton->setBounds (buttonRect.getX(), - buttonRect.getY(), - buttonRect.getWidth(), - buttonRect.getHeight() / 2); - - incButton->setConnectedEdges (Button::ConnectedOnBottom); - - decButton->setBounds (buttonRect.getX(), - buttonRect.getCentreY(), - buttonRect.getWidth(), - buttonRect.getHeight() / 2); - - decButton->setConnectedEdges (Button::ConnectedOnTop); - } - } -} - -void Slider::focusOfChildComponentChanged (FocusChangeType) -{ - repaint(); -} - -void Slider::mouseDown (const MouseEvent& e) -{ - mouseWasHidden = false; - incDecDragged = false; - - if (isEnabled()) - { - if (e.mods.isPopupMenu() && menuEnabled) - { - menuShown = true; - - PopupMenu m; - m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); - m.addSeparator(); - - if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - PopupMenu rotaryMenu; - rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); - rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); - rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); - - m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); - } - - const int r = m.show(); - - if (r == 1) - { - setVelocityBasedMode (! isVelocityBased); - } - else if (r == 2) - { - setSliderStyle (Rotary); - } - else if (r == 3) - { - setSliderStyle (RotaryHorizontalDrag); - } - else if (r == 4) - { - setSliderStyle (RotaryVerticalDrag); - } - } - else if (maximum > minimum) - { - menuShown = false; - - if (valueBox != 0) - valueBox->hideEditor (true); - - sliderBeingDragged = 0; - - if (style == TwoValueHorizontal - || style == TwoValueVertical - || style == ThreeValueHorizontal - || style == ThreeValueVertical) - { - const float mousePos = (float) (isVertical() ? e.y : e.x); - - const float normalPosDistance = fabsf (getLinearSliderPos (currentValue) - mousePos); - const float minPosDistance = fabsf (getLinearSliderPos (valueMin) - 0.1f - mousePos); - const float maxPosDistance = fabsf (getLinearSliderPos (valueMax) + 0.1f - mousePos); - - if (style == TwoValueHorizontal || style == TwoValueVertical) - { - if (maxPosDistance <= minPosDistance) - sliderBeingDragged = 2; - else - sliderBeingDragged = 1; - } - else if (style == ThreeValueHorizontal || style == ThreeValueVertical) - { - if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) - sliderBeingDragged = 1; - else if (normalPosDistance >= maxPosDistance) - sliderBeingDragged = 2; - } - } - - minMaxDiff = valueMax - valueMin; - - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; - lastAngle = rotaryStart + (rotaryEnd - rotaryStart) - * valueToProportionOfLength (currentValue); - - if (sliderBeingDragged == 2) - valueWhenLastDragged = valueMax; - else if (sliderBeingDragged == 1) - valueWhenLastDragged = valueMin; - else - valueWhenLastDragged = currentValue; - - valueOnMouseDown = valueWhenLastDragged; - - if (popupDisplayEnabled) - { - SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this); - popupDisplay = popup; - - if (parentForPopupDisplay != 0) - { - parentForPopupDisplay->addChildComponent (popup); - } - else - { - popup->addToDesktop (0); - } - - popup->setVisible (true); - } - - sendDragStart(); - - mouseDrag (e); - } - } -} - -void Slider::mouseUp (const MouseEvent&) -{ - if (isEnabled() - && (! menuShown) - && (maximum > minimum) - && (style != IncDecButtons || incDecDragged)) - { - restoreMouseIfHidden(); - - if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue) - triggerChangeMessage (false); - - sendDragEnd(); - - deleteAndZero (popupDisplay); - - if (style == IncDecButtons) - { - incButton->setState (Button::buttonNormal); - decButton->setState (Button::buttonNormal); - } - } -} - -void Slider::restoreMouseIfHidden() -{ - if (mouseWasHidden) - { - mouseWasHidden = false; - - Component* c = Component::getComponentUnderMouse(); - - if (c == 0) - c = this; - - c->enableUnboundedMouseMovement (false); - - const double pos = (sliderBeingDragged == 2) ? getMaxValue() - : ((sliderBeingDragged == 1) ? getMinValue() - : currentValue); - - if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) - { - int x, y, downX, downY; - Desktop::getMousePosition (x, y); - Desktop::getLastMouseDownPosition (downX, downY); - - if (style == RotaryHorizontalDrag) - { - const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown); - x = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downX); - y = downY; - } - else - { - const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos); - x = downX; - y = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downY); - } - - Desktop::setMousePosition (x, y); - } - else - { - const int pixelPos = (int) getLinearSliderPos (pos); - - int x = isHorizontal() ? pixelPos : (getWidth() / 2); - int y = isVertical() ? pixelPos : (getHeight() / 2); - - relativePositionToGlobal (x, y); - Desktop::setMousePosition (x, y); - } - } -} - -void Slider::modifierKeysChanged (const ModifierKeys& modifiers) -{ - if (isEnabled() - && style != IncDecButtons - && style != Rotary - && isVelocityBased == modifiers.isAnyModifierKeyDown()) - { - restoreMouseIfHidden(); - } -} - -static double smallestAngleBetween (double a1, double a2) -{ - return jmin (fabs (a1 - a2), - fabs (a1 + double_Pi * 2.0 - a2), - fabs (a2 + double_Pi * 2.0 - a1)); -} - -void Slider::mouseDrag (const MouseEvent& e) -{ - if (isEnabled() - && (! menuShown) - && (maximum > minimum)) - { - if (style == Rotary) - { - int dx = e.x - sliderRect.getCentreX(); - int dy = e.y - sliderRect.getCentreY(); - - if (dx * dx + dy * dy > 25) - { - double angle = atan2 ((double) dx, (double) -dy); - while (angle < 0.0) - angle += double_Pi * 2.0; - - if (rotaryStop && ! e.mouseWasClicked()) - { - if (fabs (angle - lastAngle) > double_Pi) - { - if (angle >= lastAngle) - angle -= double_Pi * 2.0; - else - angle += double_Pi * 2.0; - } - - if (angle >= lastAngle) - angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); - else - angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); - } - else - { - while (angle < rotaryStart) - angle += double_Pi * 2.0; - - if (angle > rotaryEnd) - { - if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd)) - angle = rotaryStart; - else - angle = rotaryEnd; - } - } - - const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); - - lastAngle = angle; - } - } - else - { - if (style == LinearBar && e.mouseWasClicked() - && valueBox != 0 && valueBox->isEditable()) - return; - - if (style == IncDecButtons) - { - if (! incDecDragged) - incDecDragged = e.getDistanceFromDragStart() > 10 && ! e.mouseWasClicked(); - - if (! incDecDragged) - return; - } - - - if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) - : false)) - || ((maximum - minimum) / sliderRegionSize < interval)) - { - const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; - - double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; - - if (style == RotaryHorizontalDrag - || style == RotaryVerticalDrag - || style == IncDecButtons - || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) - && ! snapsToMousePos)) - { - const int mouseDiff = (style == RotaryHorizontalDrag - || style == LinearHorizontal - || style == LinearBar - || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.getDistanceFromDragStartX() - : -e.getDistanceFromDragStartY(); - - double newPos = valueToProportionOfLength (valueOnMouseDown) - + mouseDiff * (1.0 / pixelsForFullDragExtent); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); - - if (style == IncDecButtons) - { - incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); - decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); - } - } - else - { - if (isVertical()) - scaledMousePos = 1.0 - scaledMousePos; - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); - } - } - else - { - const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag - || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) - ? e.x - mouseXWhenLastDragged - : e.y - mouseYWhenLastDragged; - - const double maxSpeed = jmax (200, sliderRegionSize); - double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); - - if (speed != 0) - { - speed = 0.2 * velocityModeSensitivity - * (1.0 + sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset - + jmax (0.0, (double) (speed - velocityModeThreshold)) - / maxSpeed)))); - - if (mouseDiff < 0) - speed = -speed; - - if (isVertical() || style == RotaryVerticalDrag - || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) - speed = -speed; - - const double currentPos = valueToProportionOfLength (valueWhenLastDragged); - - valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); - - e.originalComponent->enableUnboundedMouseMovement (true, false); - mouseWasHidden = true; - } - } - } - - valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); - - if (sliderBeingDragged == 0) - { - setValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, true); - } - else if (sliderBeingDragged == 1) - { - setMinValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false); - - if (e.mods.isShiftDown()) - setMaxValue (getMinValue() + minMaxDiff, false); - else - minMaxDiff = valueMax - valueMin; - } - else - { - jassert (sliderBeingDragged == 2); - - setMaxValue (snapValue (valueWhenLastDragged, true), - ! sendChangeOnlyOnRelease, false); - - if (e.mods.isShiftDown()) - setMinValue (getMaxValue() - minMaxDiff, false); - else - minMaxDiff = valueMax - valueMin; - } - - mouseXWhenLastDragged = e.x; - mouseYWhenLastDragged = e.y; - } -} - -void Slider::mouseDoubleClick (const MouseEvent&) -{ - if (doubleClickToValue - && isEnabled() - && style != IncDecButtons - && minimum <= doubleClickReturnValue - && maximum >= doubleClickReturnValue) - { - sendDragStart(); - setValue (doubleClickReturnValue, true, true); - sendDragEnd(); - } -} - -void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) -{ - if (scrollWheelEnabled && isEnabled() - && style != TwoValueHorizontal - && style != TwoValueVertical) - { - if (maximum > minimum && ! isMouseButtonDownAnywhere()) - { - if (valueBox != 0) - valueBox->hideEditor (false); - - const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; - const double currentPos = valueToProportionOfLength (currentValue); - const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); - - double delta = (newValue != currentValue) - ? jmax (fabs (newValue - currentValue), interval) : 0; - - if (currentValue > newValue) - delta = -delta; - - sendDragStart(); - setValue (snapValue (currentValue + delta, false), true, true); - sendDragEnd(); - } - } - else - { - Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); - } -} - -void SliderListener::sliderDragStarted (Slider*) -{ -} - -void SliderListener::sliderDragEnded (Slider*) -{ -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_Slider.h" +#include "../lookandfeel/juce_LookAndFeel.h" +#include "../menus/juce_PopupMenu.h" +#include "../juce_Desktop.h" +#include "../special/juce_BubbleComponent.h" +#include "../../../../juce_core/text/juce_LocalisedStrings.h" + + +//============================================================================== +class SliderPopupDisplayComponent : public BubbleComponent +{ +public: + //============================================================================== + SliderPopupDisplayComponent (Slider* const owner_) + : owner (owner_), + font (15.0f, Font::bold) + { + setAlwaysOnTop (true); + } + + ~SliderPopupDisplayComponent() + { + } + + void paintContent (Graphics& g, int w, int h) + { + g.setFont (font); + g.setColour (Colours::black); + + g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1); + } + + void getContentSize (int& w, int& h) + { + w = font.getStringWidth (text) + 18; + h = (int) (font.getHeight() * 1.6f); + } + + void updatePosition (const String& newText) + { + if (text != newText) + { + text = newText; + repaint(); + } + + BubbleComponent::setPosition (owner); + } + + //============================================================================== + juce_UseDebuggingNewOperator + +private: + Slider* owner; + Font font; + String text; + + SliderPopupDisplayComponent (const SliderPopupDisplayComponent&); + const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&); +}; + +//============================================================================== +Slider::Slider (const String& name) + : Component (name), + listeners (2), + currentValue (0.0), + valueMin (0.0), + valueMax (0.0), + minimum (0), + maximum (10), + interval (0), + skewFactor (1.0), + velocityModeSensitivity (1.0), + velocityModeOffset (0.0), + velocityModeThreshold (1), + rotaryStart (float_Pi * 1.2f), + rotaryEnd (float_Pi * 2.8f), + numDecimalPlaces (7), + sliderRegionStart (0), + sliderRegionSize (1), + sliderBeingDragged (-1), + pixelsForFullDragExtent (250), + style (LinearHorizontal), + textBoxPos (TextBoxLeft), + textBoxWidth (80), + textBoxHeight (20), + incDecButtonMode (incDecButtonsNotDraggable), + editableText (true), + doubleClickToValue (false), + isVelocityBased (false), + userKeyOverridesVelocity (true), + rotaryStop (true), + incDecButtonsSideBySide (false), + sendChangeOnlyOnRelease (false), + popupDisplayEnabled (false), + menuEnabled (false), + menuShown (false), + scrollWheelEnabled (true), + snapsToMousePos (true), + valueBox (0), + incButton (0), + decButton (0), + popupDisplay (0), + parentForPopupDisplay (0) +{ + setWantsKeyboardFocus (false); + setRepaintsOnMouseActivity (true); + + lookAndFeelChanged(); + updateText(); +} + +Slider::~Slider() +{ + deleteAndZero (popupDisplay); + deleteAllChildren(); +} + + +//============================================================================== +void Slider::handleAsyncUpdate() +{ + cancelPendingUpdate(); + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderValueChanged (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::sendDragStart() +{ + startedDragging(); + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderDragStarted (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::sendDragEnd() +{ + stoppedDragging(); + + sliderBeingDragged = -1; + + for (int i = listeners.size(); --i >= 0;) + { + ((SliderListener*) listeners.getUnchecked (i))->sliderDragEnded (this); + i = jmin (i, listeners.size()); + } +} + +void Slider::addListener (SliderListener* const listener) throw() +{ + jassert (listener != 0); + if (listener != 0) + listeners.add (listener); +} + +void Slider::removeListener (SliderListener* const listener) throw() +{ + listeners.removeValue (listener); +} + +//============================================================================== +void Slider::setSliderStyle (const SliderStyle newStyle) +{ + if (style != newStyle) + { + style = newStyle; + repaint(); + lookAndFeelChanged(); + } +} + +void Slider::setRotaryParameters (const float startAngleRadians, + const float endAngleRadians, + const bool stopAtEnd) +{ + // make sure the values are sensible.. + jassert (rotaryStart >= 0 && rotaryEnd >= 0); + jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); + jassert (rotaryStart < rotaryEnd); + + rotaryStart = startAngleRadians; + rotaryEnd = endAngleRadians; + rotaryStop = stopAtEnd; +} + +void Slider::setVelocityBasedMode (const bool velBased) throw() +{ + isVelocityBased = velBased; +} + +void Slider::setVelocityModeParameters (const double sensitivity, + const int threshold, + const double offset, + const bool userCanPressKeyToSwapMode) throw() +{ + jassert (threshold >= 0); + jassert (sensitivity > 0); + jassert (offset >= 0); + + velocityModeSensitivity = sensitivity; + velocityModeOffset = offset; + velocityModeThreshold = threshold; + userKeyOverridesVelocity = userCanPressKeyToSwapMode; +} + +void Slider::setSkewFactor (const double factor) throw() +{ + skewFactor = factor; +} + +void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw() +{ + if (maximum > minimum) + skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum) + / (maximum - minimum)); +} + +void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag) +{ + jassert (distanceForFullScaleDrag > 0); + + pixelsForFullDragExtent = distanceForFullScaleDrag; +} + +void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) +{ + if (incDecButtonMode != mode) + { + incDecButtonMode = mode; + lookAndFeelChanged(); + } +} + +void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, + const bool isReadOnly, + const int textEntryBoxWidth, + const int textEntryBoxHeight) +{ + textBoxPos = newPosition; + editableText = ! isReadOnly; + textBoxWidth = textEntryBoxWidth; + textBoxHeight = textEntryBoxHeight; + + repaint(); + lookAndFeelChanged(); +} + +void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw() +{ + editableText = shouldBeEditable; + + if (valueBox != 0) + valueBox->setEditable (shouldBeEditable && isEnabled()); +} + +void Slider::showTextBox() +{ + jassert (editableText); // this should probably be avoided in read-only sliders. + + if (valueBox != 0) + valueBox->showEditor(); +} + +void Slider::hideTextBox (const bool discardCurrentEditorContents) +{ + if (valueBox != 0) + { + valueBox->hideEditor (discardCurrentEditorContents); + + if (discardCurrentEditorContents) + updateText(); + } +} + +void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() +{ + sendChangeOnlyOnRelease = onlyNotifyOnRelease; +} + +void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw() +{ + snapsToMousePos = shouldSnapToMouse; +} + +void Slider::setPopupDisplayEnabled (const bool enabled, + Component* const parentComponentToUse) throw() +{ + popupDisplayEnabled = enabled; + parentForPopupDisplay = parentComponentToUse; +} + +//============================================================================== +void Slider::colourChanged() +{ + lookAndFeelChanged(); +} + +void Slider::lookAndFeelChanged() +{ + const String previousTextBoxContent (valueBox != 0 ? valueBox->getText() + : getTextFromValue (currentValue)); + + deleteAllChildren(); + valueBox = 0; + + LookAndFeel& lf = getLookAndFeel(); + + if (textBoxPos != NoTextBox) + { + addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this)); + + valueBox->setWantsKeyboardFocus (false); + valueBox->setText (previousTextBoxContent, false); + + valueBox->setEditable (editableText && isEnabled()); + valueBox->addListener (this); + + if (style == LinearBar) + valueBox->addMouseListener (this, false); + + valueBox->setTooltip (getTooltip()); + } + + if (style == IncDecButtons) + { + addAndMakeVisible (incButton = lf.createSliderButton (true)); + incButton->addButtonListener (this); + + addAndMakeVisible (decButton = lf.createSliderButton (false)); + decButton->addButtonListener (this); + + if (incDecButtonMode != incDecButtonsNotDraggable) + { + incButton->addMouseListener (this, false); + decButton->addMouseListener (this, false); + } + else + { + incButton->setRepeatSpeed (300, 100, 20); + incButton->addMouseListener (decButton, false); + + decButton->setRepeatSpeed (300, 100, 20); + decButton->addMouseListener (incButton, false); + } + } + + setComponentEffect (lf.getSliderEffect()); + + resized(); + repaint(); +} + +//============================================================================== +void Slider::setRange (const double newMin, + const double newMax, + const double newInt) +{ + if (minimum != newMin + || maximum != newMax + || interval != newInt) + { + minimum = newMin; + maximum = newMax; + interval = newInt; + + // figure out the number of DPs needed to display all values at this + // interval setting. + numDecimalPlaces = 7; + + if (newInt != 0) + { + int v = abs ((int) (newInt * 10000000)); + + while ((v % 10) == 0) + { + --numDecimalPlaces; + v /= 10; + } + } + + // keep the current values inside the new range.. + if (style != TwoValueHorizontal && style != TwoValueVertical) + { + setValue (currentValue, false, false); + } + else + { + setMinValue (getMinValue(), false, false); + setMaxValue (getMaxValue(), false, false); + } + + updateText(); + } +} + +void Slider::triggerChangeMessage (const bool synchronous) +{ + if (synchronous) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + + valueChanged(); +} + +double Slider::getValue() const throw() +{ + // for a two-value style slider, you should use the getMinValue() and getMaxValue() + // methods to get the two values. + jassert (style != TwoValueHorizontal && style != TwoValueVertical); + + return currentValue; +} + +void Slider::setValue (double newValue, + const bool sendUpdateMessage, + const bool sendMessageSynchronously) +{ + // for a two-value style slider, you should use the setMinValue() and setMaxValue() + // methods to set the two values. + jassert (style != TwoValueHorizontal && style != TwoValueVertical); + + newValue = constrainedValue (newValue); + + if (style == ThreeValueHorizontal || style == ThreeValueVertical) + { + jassert (valueMin <= valueMax); + newValue = jlimit (valueMin, valueMax, newValue); + } + + if (currentValue != newValue) + { + if (valueBox != 0) + valueBox->hideEditor (true); + + currentValue = newValue; + updateText(); + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (currentValue)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +double Slider::getMinValue() const throw() +{ + // The minimum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + return valueMin; +} + +double Slider::getMaxValue() const throw() +{ + // The maximum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + return valueMax; +} + +void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) +{ + // The minimum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + newValue = constrainedValue (newValue); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (allowNudgingOfOtherValues && newValue > valueMax) + setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmin (valueMax, newValue); + } + else + { + if (allowNudgingOfOtherValues && newValue > currentValue) + setValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmin (currentValue, newValue); + } + + if (valueMin != newValue) + { + valueMin = newValue; + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMin)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues) +{ + // The maximum value only applies to sliders that are in two- or three-value mode. + jassert (style == TwoValueHorizontal || style == TwoValueVertical + || style == ThreeValueHorizontal || style == ThreeValueVertical); + + newValue = constrainedValue (newValue); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (allowNudgingOfOtherValues && newValue < valueMin) + setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmax (valueMin, newValue); + } + else + { + if (allowNudgingOfOtherValues && newValue < currentValue) + setValue (newValue, sendUpdateMessage, sendMessageSynchronously); + + newValue = jmax (currentValue, newValue); + } + + if (valueMax != newValue) + { + valueMax = newValue; + repaint(); + + if (popupDisplay != 0) + { + ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition (getTextFromValue (valueMax)); + popupDisplay->repaint(); + } + + if (sendUpdateMessage) + triggerChangeMessage (sendMessageSynchronously); + } +} + +void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, + const double valueToSetOnDoubleClick) throw() +{ + doubleClickToValue = isDoubleClickEnabled; + doubleClickReturnValue = valueToSetOnDoubleClick; +} + +double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() +{ + isEnabled_ = doubleClickToValue; + return doubleClickReturnValue; +} + +void Slider::updateText() +{ + if (valueBox != 0) + valueBox->setText (getTextFromValue (currentValue), false); +} + +void Slider::setTextValueSuffix (const String& suffix) +{ + if (textSuffix != suffix) + { + textSuffix = suffix; + updateText(); + } +} + +const String Slider::getTextFromValue (double v) +{ + if (numDecimalPlaces > 0) + return String (v, numDecimalPlaces) + textSuffix; + else + return String (roundDoubleToInt (v)) + textSuffix; +} + +double Slider::getValueFromText (const String& text) +{ + String t (text.trimStart()); + + if (t.endsWith (textSuffix)) + t = t.substring (0, t.length() - textSuffix.length()); + + while (t.startsWithChar (T('+'))) + t = t.substring (1).trimStart(); + + return t.initialSectionContainingOnly (T("0123456789.,-")) + .getDoubleValue(); +} + +double Slider::proportionOfLengthToValue (double proportion) +{ + if (skewFactor != 1.0 && proportion > 0.0) + proportion = exp (log (proportion) / skewFactor); + + return minimum + (maximum - minimum) * proportion; +} + +double Slider::valueToProportionOfLength (double value) +{ + const double n = (value - minimum) / (maximum - minimum); + + return skewFactor == 1.0 ? n : pow (n, skewFactor); +} + +double Slider::snapValue (double attemptedValue, const bool) +{ + return attemptedValue; +} + +//============================================================================== +void Slider::startedDragging() +{ +} + +void Slider::stoppedDragging() +{ +} + +void Slider::valueChanged() +{ +} + +//============================================================================== +void Slider::enablementChanged() +{ + repaint(); +} + +void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() +{ + menuEnabled = menuEnabled_; +} + +void Slider::setScrollWheelEnabled (const bool enabled) throw() +{ + scrollWheelEnabled = enabled; +} + +//============================================================================== +void Slider::labelTextChanged (Label* label) +{ + const double newValue = snapValue (getValueFromText (label->getText()), false); + + if (getValue() != newValue) + { + sendDragStart(); + setValue (newValue, true, true); + sendDragEnd(); + } + + updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this. +} + +void Slider::buttonClicked (Button* button) +{ + if (style == IncDecButtons) + { + sendDragStart(); + + if (button == incButton) + setValue (snapValue (getValue() + interval, false), true, true); + else if (button == decButton) + setValue (snapValue (getValue() - interval, false), true, true); + + sendDragEnd(); + } +} + +//============================================================================== +double Slider::constrainedValue (double value) const throw() +{ + if (interval > 0) + value = minimum + interval * floor ((value - minimum) / interval + 0.5); + + if (value <= minimum || maximum <= minimum) + value = minimum; + else if (value >= maximum) + value = maximum; + + return value; +} + +float Slider::getLinearSliderPos (const double value) +{ + double sliderPosProportional; + + if (maximum > minimum) + { + if (value < minimum) + { + sliderPosProportional = 0.0; + } + else if (value > maximum) + { + sliderPosProportional = 1.0; + } + else + { + sliderPosProportional = valueToProportionOfLength (value); + jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0); + } + } + else + { + sliderPosProportional = 0.5; + } + + if (isVertical() || style == IncDecButtons) + sliderPosProportional = 1.0 - sliderPosProportional; + + return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize); +} + +bool Slider::isHorizontal() const throw() +{ + return style == LinearHorizontal + || style == LinearBar + || style == TwoValueHorizontal + || style == ThreeValueHorizontal; +} + +bool Slider::isVertical() const throw() +{ + return style == LinearVertical + || style == TwoValueVertical + || style == ThreeValueVertical; +} + +bool Slider::incDecDragDirectionIsHorizontal() const throw() +{ + return incDecButtonMode == incDecButtonsDraggable_Horizontal + || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide); +} + +float Slider::getPositionOfValue (const double value) +{ + if (isHorizontal() || isVertical()) + { + return getLinearSliderPos (value); + } + else + { + jassertfalse // not a valid call on a slider that doesn't work linearly! + return 0.0f; + } +} + +//============================================================================== +void Slider::paint (Graphics& g) +{ + if (style != IncDecButtons) + { + if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + const float sliderPos = (float) valueToProportionOfLength (currentValue); + jassert (sliderPos >= 0 && sliderPos <= 1.0f); + + getLookAndFeel().drawRotarySlider (g, + sliderRect.getX(), + sliderRect.getY(), + sliderRect.getWidth(), + sliderRect.getHeight(), + sliderPos, + rotaryStart, rotaryEnd, + *this); + } + else + { + getLookAndFeel().drawLinearSlider (g, + sliderRect.getX(), + sliderRect.getY(), + sliderRect.getWidth(), + sliderRect.getHeight(), + getLinearSliderPos (currentValue), + getLinearSliderPos (valueMin), + getLinearSliderPos (valueMax), + style, + *this); + } + + if (style == LinearBar && valueBox == 0) + { + g.setColour (findColour (Slider::textBoxOutlineColourId)); + g.drawRect (0, 0, getWidth(), getHeight(), 1); + } + } +} + +void Slider::resized() +{ + int minXSpace = 0; + int minYSpace = 0; + + if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) + minXSpace = 30; + else + minYSpace = 15; + + const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace)); + const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace)); + + if (style == LinearBar) + { + if (valueBox != 0) + valueBox->setBounds (0, 0, getWidth(), getHeight()); + } + else + { + if (textBoxPos == NoTextBox) + { + sliderRect.setBounds (0, 0, getWidth(), getHeight()); + } + else if (textBoxPos == TextBoxLeft) + { + valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh); + sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight()); + } + else if (textBoxPos == TextBoxRight) + { + valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh); + sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight()); + } + else if (textBoxPos == TextBoxAbove) + { + valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh); + sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh); + } + else if (textBoxPos == TextBoxBelow) + { + valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh); + sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh); + } + } + + const int indent = getLookAndFeel().getSliderThumbRadius (*this); + + if (style == LinearBar) + { + const int barIndent = 1; + sliderRegionStart = barIndent; + sliderRegionSize = getWidth() - barIndent * 2; + + sliderRect.setBounds (sliderRegionStart, barIndent, + sliderRegionSize, getHeight() - barIndent * 2); + } + else if (isHorizontal()) + { + sliderRegionStart = sliderRect.getX() + indent; + sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2); + + sliderRect.setBounds (sliderRegionStart, sliderRect.getY(), + sliderRegionSize, sliderRect.getHeight()); + } + else if (isVertical()) + { + sliderRegionStart = sliderRect.getY() + indent; + sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2); + + sliderRect.setBounds (sliderRect.getX(), sliderRegionStart, + sliderRect.getWidth(), sliderRegionSize); + } + else + { + sliderRegionStart = 0; + sliderRegionSize = 100; + } + + if (style == IncDecButtons) + { + Rectangle buttonRect (sliderRect); + + if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight) + buttonRect.expand (-2, 0); + else + buttonRect.expand (0, -2); + + incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight(); + + if (incDecButtonsSideBySide) + { + decButton->setBounds (buttonRect.getX(), + buttonRect.getY(), + buttonRect.getWidth() / 2, + buttonRect.getHeight()); + + decButton->setConnectedEdges (Button::ConnectedOnRight); + + incButton->setBounds (buttonRect.getCentreX(), + buttonRect.getY(), + buttonRect.getWidth() / 2, + buttonRect.getHeight()); + + incButton->setConnectedEdges (Button::ConnectedOnLeft); + } + else + { + incButton->setBounds (buttonRect.getX(), + buttonRect.getY(), + buttonRect.getWidth(), + buttonRect.getHeight() / 2); + + incButton->setConnectedEdges (Button::ConnectedOnBottom); + + decButton->setBounds (buttonRect.getX(), + buttonRect.getCentreY(), + buttonRect.getWidth(), + buttonRect.getHeight() / 2); + + decButton->setConnectedEdges (Button::ConnectedOnTop); + } + } +} + +void Slider::focusOfChildComponentChanged (FocusChangeType) +{ + repaint(); +} + +void Slider::mouseDown (const MouseEvent& e) +{ + mouseWasHidden = false; + incDecDragged = false; + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + mouseDragStartX = e.getMouseDownX(); + mouseDragStartY = e.getMouseDownY(); + + if (isEnabled()) + { + if (e.mods.isPopupMenu() && menuEnabled) + { + menuShown = true; + + PopupMenu m; + m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased); + m.addSeparator(); + + if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + PopupMenu rotaryMenu; + rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary); + rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag); + rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag); + + m.addSubMenu (TRANS ("rotary mode"), rotaryMenu); + } + + const int r = m.show(); + + if (r == 1) + { + setVelocityBasedMode (! isVelocityBased); + } + else if (r == 2) + { + setSliderStyle (Rotary); + } + else if (r == 3) + { + setSliderStyle (RotaryHorizontalDrag); + } + else if (r == 4) + { + setSliderStyle (RotaryVerticalDrag); + } + } + else if (maximum > minimum) + { + menuShown = false; + + if (valueBox != 0) + valueBox->hideEditor (true); + + sliderBeingDragged = 0; + + if (style == TwoValueHorizontal + || style == TwoValueVertical + || style == ThreeValueHorizontal + || style == ThreeValueVertical) + { + const float mousePos = (float) (isVertical() ? e.y : e.x); + + const float normalPosDistance = fabsf (getLinearSliderPos (currentValue) - mousePos); + const float minPosDistance = fabsf (getLinearSliderPos (valueMin) - 0.1f - mousePos); + const float maxPosDistance = fabsf (getLinearSliderPos (valueMax) + 0.1f - mousePos); + + if (style == TwoValueHorizontal || style == TwoValueVertical) + { + if (maxPosDistance <= minPosDistance) + sliderBeingDragged = 2; + else + sliderBeingDragged = 1; + } + else if (style == ThreeValueHorizontal || style == ThreeValueVertical) + { + if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance) + sliderBeingDragged = 1; + else if (normalPosDistance >= maxPosDistance) + sliderBeingDragged = 2; + } + } + + minMaxDiff = valueMax - valueMin; + + lastAngle = rotaryStart + (rotaryEnd - rotaryStart) + * valueToProportionOfLength (currentValue); + + if (sliderBeingDragged == 2) + valueWhenLastDragged = valueMax; + else if (sliderBeingDragged == 1) + valueWhenLastDragged = valueMin; + else + valueWhenLastDragged = currentValue; + + valueOnMouseDown = valueWhenLastDragged; + + if (popupDisplayEnabled) + { + SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this); + popupDisplay = popup; + + if (parentForPopupDisplay != 0) + { + parentForPopupDisplay->addChildComponent (popup); + } + else + { + popup->addToDesktop (0); + } + + popup->setVisible (true); + } + + sendDragStart(); + + mouseDrag (e); + } + } +} + +void Slider::mouseUp (const MouseEvent&) +{ + if (isEnabled() + && (! menuShown) + && (maximum > minimum) + && (style != IncDecButtons || incDecDragged)) + { + restoreMouseIfHidden(); + + if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue) + triggerChangeMessage (false); + + sendDragEnd(); + + deleteAndZero (popupDisplay); + + if (style == IncDecButtons) + { + incButton->setState (Button::buttonNormal); + decButton->setState (Button::buttonNormal); + } + } +} + +void Slider::restoreMouseIfHidden() +{ + if (mouseWasHidden) + { + mouseWasHidden = false; + + Component* c = Component::getComponentUnderMouse(); + + if (c == 0) + c = this; + + c->enableUnboundedMouseMovement (false); + + const double pos = (sliderBeingDragged == 2) ? getMaxValue() + : ((sliderBeingDragged == 1) ? getMinValue() + : currentValue); + + if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag) + { + int x, y, downX, downY; + Desktop::getMousePosition (x, y); + Desktop::getLastMouseDownPosition (downX, downY); + + if (style == RotaryHorizontalDrag) + { + const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown); + x = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downX); + y = downY; + } + else + { + const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos); + x = downX; + y = roundDoubleToInt (pixelsForFullDragExtent * posDiff + downY); + } + + Desktop::setMousePosition (x, y); + } + else + { + const int pixelPos = (int) getLinearSliderPos (pos); + + int x = isHorizontal() ? pixelPos : (getWidth() / 2); + int y = isVertical() ? pixelPos : (getHeight() / 2); + + relativePositionToGlobal (x, y); + Desktop::setMousePosition (x, y); + } + } +} + +void Slider::modifierKeysChanged (const ModifierKeys& modifiers) +{ + if (isEnabled() + && style != IncDecButtons + && style != Rotary + && isVelocityBased == modifiers.isAnyModifierKeyDown()) + { + restoreMouseIfHidden(); + } +} + +static double smallestAngleBetween (double a1, double a2) +{ + return jmin (fabs (a1 - a2), + fabs (a1 + double_Pi * 2.0 - a2), + fabs (a2 + double_Pi * 2.0 - a1)); +} + +void Slider::mouseDrag (const MouseEvent& e) +{ + if (isEnabled() + && (! menuShown) + && (maximum > minimum)) + { + if (style == Rotary) + { + int dx = e.x - sliderRect.getCentreX(); + int dy = e.y - sliderRect.getCentreY(); + + if (dx * dx + dy * dy > 25) + { + double angle = atan2 ((double) dx, (double) -dy); + while (angle < 0.0) + angle += double_Pi * 2.0; + + if (rotaryStop && ! e.mouseWasClicked()) + { + if (fabs (angle - lastAngle) > double_Pi) + { + if (angle >= lastAngle) + angle -= double_Pi * 2.0; + else + angle += double_Pi * 2.0; + } + + if (angle >= lastAngle) + angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd)); + else + angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd)); + } + else + { + while (angle < rotaryStart) + angle += double_Pi * 2.0; + + if (angle > rotaryEnd) + { + if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd)) + angle = rotaryStart; + else + angle = rotaryEnd; + } + } + + const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion)); + + lastAngle = angle; + } + } + else + { + if (style == LinearBar && e.mouseWasClicked() + && valueBox != 0 && valueBox->isEditable()) + return; + + if (style == IncDecButtons && ! incDecDragged) + { + if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked()) + return; + + incDecDragged = true; + mouseDragStartX = e.x; + mouseDragStartY = e.y; + } + + if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier) + : false)) + || ((maximum - minimum) / sliderRegionSize < interval)) + { + const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y; + + double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize; + + if (style == RotaryHorizontalDrag + || style == RotaryVerticalDrag + || style == IncDecButtons + || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar) + && ! snapsToMousePos)) + { + const int mouseDiff = (style == RotaryHorizontalDrag + || style == LinearHorizontal + || style == LinearBar + || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) + ? e.x - mouseDragStartX + : mouseDragStartY - e.y; + + double newPos = valueToProportionOfLength (valueOnMouseDown) + + mouseDiff * (1.0 / pixelsForFullDragExtent); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos)); + + if (style == IncDecButtons) + { + incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown); + decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown); + } + } + else + { + if (isVertical()) + scaledMousePos = 1.0 - scaledMousePos; + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos)); + } + } + else + { + const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag + || (style == IncDecButtons && incDecDragDirectionIsHorizontal())) + ? e.x - mouseXWhenLastDragged + : e.y - mouseYWhenLastDragged; + + const double maxSpeed = jmax (200, sliderRegionSize); + double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff)); + + if (speed != 0) + { + speed = 0.2 * velocityModeSensitivity + * (1.0 + sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset + + jmax (0.0, (double) (speed - velocityModeThreshold)) + / maxSpeed)))); + + if (mouseDiff < 0) + speed = -speed; + + if (isVertical() || style == RotaryVerticalDrag + || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal())) + speed = -speed; + + const double currentPos = valueToProportionOfLength (valueWhenLastDragged); + + valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed)); + + e.originalComponent->enableUnboundedMouseMovement (true, false); + mouseWasHidden = true; + } + } + } + + valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); + + if (sliderBeingDragged == 0) + { + setValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, true); + } + else if (sliderBeingDragged == 1) + { + setMinValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, false, true); + + if (e.mods.isShiftDown()) + setMaxValue (getMinValue() + minMaxDiff, false, false, true); + else + minMaxDiff = valueMax - valueMin; + } + else + { + jassert (sliderBeingDragged == 2); + + setMaxValue (snapValue (valueWhenLastDragged, true), + ! sendChangeOnlyOnRelease, false, true); + + if (e.mods.isShiftDown()) + setMinValue (getMaxValue() - minMaxDiff, false, false, true); + else + minMaxDiff = valueMax - valueMin; + } + + mouseXWhenLastDragged = e.x; + mouseYWhenLastDragged = e.y; + } +} + +void Slider::mouseDoubleClick (const MouseEvent&) +{ + if (doubleClickToValue + && isEnabled() + && style != IncDecButtons + && minimum <= doubleClickReturnValue + && maximum >= doubleClickReturnValue) + { + sendDragStart(); + setValue (doubleClickReturnValue, true, true); + sendDragEnd(); + } +} + +void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) +{ + if (scrollWheelEnabled && isEnabled() + && style != TwoValueHorizontal + && style != TwoValueVertical) + { + if (maximum > minimum && ! isMouseButtonDownAnywhere()) + { + if (valueBox != 0) + valueBox->hideEditor (false); + + const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; + const double currentPos = valueToProportionOfLength (currentValue); + const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); + + double delta = (newValue != currentValue) + ? jmax (fabs (newValue - currentValue), interval) : 0; + + if (currentValue > newValue) + delta = -delta; + + sendDragStart(); + setValue (snapValue (currentValue + delta, false), true, true); + sendDragEnd(); + } + } + else + { + Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); + } +} + +void SliderListener::sliderDragStarted (Slider*) +{ +} + +void SliderListener::sliderDragEnded (Slider*) +{ +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.h b/src/juce_appframework/gui/components/controls/juce_Slider.h index d8f8d77b66..ccba2120e9 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.h +++ b/src/juce_appframework/gui/components/controls/juce_Slider.h @@ -1,742 +1,751 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#ifndef __JUCE_SLIDER_JUCEHEADER__ -#define __JUCE_SLIDER_JUCEHEADER__ - -#include "juce_SliderListener.h" -#include "juce_Label.h" -#include "../buttons/juce_Button.h" -#include "../../../events/juce_AsyncUpdater.h" -#include "../../../../juce_core/containers/juce_SortedSet.h" - - -//============================================================================== -/** - A slider control for changing a value. - - The slider can be horizontal, vertical, or rotary, and can optionally have - a text-box inside it to show an editable display of the current value. - - To use it, create a Slider object and use the setSliderStyle() method - to set up the type you want. To set up the text-entry box, use setTextBoxStyle(). - - To define the values that it can be set to, see the setRange() and setValue() methods. - - There are also lots of custom tweaks you can do by subclassing and overriding - some of the virtual methods, such as changing the scaling, changing the format of - the text display, custom ways of limiting the values, etc. - - You can register SliderListeners with a slider, which will be informed when the value - changes, or a subclass can override valueChanged() to be informed synchronously. - - @see SliderListener -*/ -class JUCE_API Slider : public Component, - public SettableTooltipClient, - private AsyncUpdater, - private ButtonListener, - private LabelListener -{ -public: - //============================================================================== - /** Creates a slider. - - When created, you'll need to set up the slider's style and range with setSliderStyle(), - setRange(), etc. - */ - Slider (const String& componentName); - - /** Destructor. */ - ~Slider(); - - //============================================================================== - /** The types of slider available. - - @see setSliderStyle, setRotaryParameters - */ - enum SliderStyle - { - LinearHorizontal, /**< A traditional horizontal slider. */ - LinearVertical, /**< A traditional vertical slider. */ - LinearBar, /**< A horizontal bar slider with the text label drawn on top of it. */ - Rotary, /**< A rotary control that you move by dragging the mouse in a circular motion, like a knob. - @see setRotaryParameters */ - RotaryHorizontalDrag, /**< A rotary control that you move by dragging the mouse left-to-right. - @see setRotaryParameters */ - RotaryVerticalDrag, /**< A rotary control that you move by dragging the mouse up-and-down. - @see setRotaryParameters */ - IncDecButtons, /**< A pair of buttons that increment or decrement the slider's value by the increment set in setRange(). */ - - TwoValueHorizontal, /**< A horizontal slider that has two thumbs instead of one, so it can show a minimum and maximum value. - @see setMinValue, setMaxValue */ - TwoValueVertical, /**< A vertical slider that has two thumbs instead of one, so it can show a minimum and maximum value. - @see setMinValue, setMaxValue */ - - ThreeValueHorizontal, /**< A horizontal slider that has three thumbs instead of one, so it can show a minimum and maximum - value, with the current value being somewhere between them. - @see setMinValue, setMaxValue */ - ThreeValueVertical, /**< A vertical slider that has three thumbs instead of one, so it can show a minimum and maximum - value, with the current value being somewhere between them. - @see setMinValue, setMaxValue */ - }; - - /** Changes the type of slider interface being used. - - @param newStyle the type of interface - @see setRotaryParameters, setVelocityBasedMode, - */ - void setSliderStyle (const SliderStyle newStyle); - - /** Returns the slider's current style. - - @see setSliderStyle - */ - SliderStyle getSliderStyle() const throw() { return style; } - - //============================================================================== - /** Changes the properties of a rotary slider. - - @param startAngleRadians the angle (in radians, clockwise from the top) at which - the slider's minimum value is represented - @param endAngleRadians the angle (in radians, clockwise from the top) at which - the slider's maximum value is represented. This must be - greater than startAngleRadians - @param stopAtEnd if true, then when the slider is dragged around past the - minimum or maximum, it'll stop there; if false, it'll wrap - back to the opposite value - */ - void setRotaryParameters (const float startAngleRadians, - const float endAngleRadians, - const bool stopAtEnd); - - /** Sets the distance the mouse has to move to drag the slider across - the full extent of its range. - - This only applies when in modes like RotaryHorizontalDrag, where it's using - relative mouse movements to adjust the slider. - */ - void setMouseDragSensitivity (const int distanceForFullScaleDrag); - - //============================================================================== - /** Changes the way the the mouse is used when dragging the slider. - - If true, this will turn on velocity-sensitive dragging, so that - the faster the mouse moves, the bigger the movement to the slider. This - helps when making accurate adjustments if the slider's range is quite large. - - If false, the slider will just try to snap to wherever the mouse is. - */ - void setVelocityBasedMode (const bool isVelocityBased) throw(); - - /** Changes aspects of the scaling used when in velocity-sensitive mode. - - These apply when you've used setVelocityBasedMode() to turn on velocity mode, - or if you're holding down ctrl. - - @param sensitivity higher values than 1.0 increase the range of acceleration used - @param threshold the minimum number of pixels that the mouse needs to move for it - to be treated as a movement - @param offset values greater than 0.0 increase the minimum speed that will be used when - the threshold is reached - @param userCanPressKeyToSwapMode if true, then the user can hold down the ctrl or command - key to toggle velocity-sensitive mode - */ - void setVelocityModeParameters (const double sensitivity = 1.0, - const int threshold = 1, - const double offset = 0.0, - const bool userCanPressKeyToSwapMode = true) throw(); - - //============================================================================== - /** Sets up a skew factor to alter the way values are distributed. - - You may want to use a range of values on the slider where more accuracy - is required towards one end of the range, so this will logarithmically - spread the values across the length of the slider. - - If the factor is < 1.0, the lower end of the range will fill more of the - slider's length; if the factor is > 1.0, the upper end of the range - will be expanded instead. A factor of 1.0 doesn't skew it at all. - - To set the skew position by using a mid-point, use the setSkewFactorFromMidPoint() - method instead. - - @see getSkewFactor, setSkewFactorFromMidPoint - */ - void setSkewFactor (const double factor) throw(); - - /** Sets up a skew factor to alter the way values are distributed. - - This allows you to specify the slider value that should appear in the - centre of the slider's visible range. - - @see setSkewFactor, getSkewFactor - */ - void setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw(); - - /** Returns the current skew factor. - - See setSkewFactor for more info. - - @see setSkewFactor, setSkewFactorFromMidPoint - */ - double getSkewFactor() const throw() { return skewFactor; } - - //============================================================================== - /** Used by setIncDecButtonsMode(). - */ - enum IncDecButtonMode - { - incDecButtonsNotDraggable, - incDecButtonsDraggable_AutoDirection, - incDecButtonsDraggable_Horizontal, - incDecButtonsDraggable_Vertical - }; - - /** When the style is IncDecButtons, this lets you turn on a mode where the mouse - can be dragged on the buttons to drag the values. - - By default this is turned off. When enabled, clicking on the buttons still works - them as normal, but by holding down the mouse on a button and dragging it a little - distance, it flips into a mode where the value can be dragged. The drag direction can - either be set explicitly to be vertical or horizontal, or can be set to - incDecButtonsDraggable_AutoDirection so that it depends on whether the buttons - are side-by-side or above each other. - */ - void setIncDecButtonsMode (const IncDecButtonMode mode); - - //============================================================================== - /** The position of the slider's text-entry box. - - @see setTextBoxStyle - */ - enum TextEntryBoxPosition - { - NoTextBox, /**< Doesn't display a text box. */ - TextBoxLeft, /**< Puts the text box to the left of the slider, vertically centred. */ - TextBoxRight, /**< Puts the text box to the right of the slider, vertically centred. */ - TextBoxAbove, /**< Puts the text box above the slider, horizontally centred. */ - TextBoxBelow /**< Puts the text box below the slider, horizontally centred. */ - }; - - /** Changes the location and properties of the text-entry box. - - @param newPosition where it should go (or NoTextBox to not have one at all) - @param isReadOnly if true, it's a read-only display - @param textEntryBoxWidth the width of the text-box in pixels. Make sure this leaves enough - room for the slider as well! - @param textEntryBoxHeight the height of the text-box in pixels. Make sure this leaves enough - room for the slider as well! - - @see setTextBoxIsEditable, getValueFromText, getTextFromValue - */ - void setTextBoxStyle (const TextEntryBoxPosition newPosition, - const bool isReadOnly, - const int textEntryBoxWidth, - const int textEntryBoxHeight); - - /** Returns the status of the text-box. - @see setTextBoxStyle - */ - const TextEntryBoxPosition getTextBoxPosition() const throw() { return textBoxPos; } - - /** Returns the width used for the text-box. - @see setTextBoxStyle - */ - int getTextBoxWidth() const throw() { return textBoxWidth; } - - /** Returns the height used for the text-box. - @see setTextBoxStyle - */ - int getTextBoxHeight() const throw() { return textBoxHeight; } - - /** Makes the text-box editable. - - By default this is true, and the user can enter values into the textbox, - but it can be turned off if that's not suitable. - - @see setTextBoxStyle, getValueFromText, getTextFromValue - */ - void setTextBoxIsEditable (const bool shouldBeEditable) throw(); - - /** Returns true if the text-box is read-only. - @see setTextBoxStyle - */ - bool isTextBoxEditable() const throw() { return editableText; } - - /** If the text-box is editable, this will give it the focus so that the user can - type directly into it. - - This is basically the effect as the user clicking on it. - */ - void showTextBox(); - - /** If the text-box currently has focus and is being edited, this resets it and takes keyboard - focus away from it. - - @param discardCurrentEditorContents if true, the slider's value will be left - unchanged; if false, the current contents of the - text editor will be used to set the slider position - before it is hidden. - */ - void hideTextBox (const bool discardCurrentEditorContents); - - - //============================================================================== - /** Changes the slider's current value. - - This will trigger a callback to SliderListener::sliderValueChanged() for any listeners - that are registered, and will synchronously call the valueChanged() method in case subclasses - want to handle it. - - @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and will be snapped to the - nearest interval if one has been set - @param sendUpdateMessage if false, a change to the value will not trigger a call to - any SliderListeners or the valueChanged() method - @param sendMessageSynchronously if true, then a call to the SliderListeners will be made - synchronously; if false, it will be asynchronous - */ - void setValue (double newValue, - const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false); - - /** Returns the slider's current value. */ - double getValue() const throw(); - - //============================================================================== - /** Sets the limits that the slider's value can take. - - @param newMinimum the lowest value allowed - @param newMaximum the highest value allowed - @param newInterval the steps in which the value is allowed to increase - if this - is not zero, the value will always be (newMinimum + (newInterval * an integer)). - */ - void setRange (const double newMinimum, - const double newMaximum, - const double newInterval = 0); - - /** Returns the current maximum value. - @see setRange - */ - double getMaximum() const throw() { return maximum; } - - /** Returns the current minimum value. - @see setRange - */ - double getMinimum() const throw() { return minimum; } - - /** Returns the current step-size for values. - @see setRange - */ - double getInterval() const throw() { return interval; } - - //============================================================================== - /** For a slider with two or three thumbs, this returns the lower of its values. - - For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). - A slider with three values also uses the normal getValue() and setValue() methods to - control the middle value. - - @see setMinValue, getMaxValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical - */ - double getMinValue() const throw(); - - /** For a slider with two or three thumbs, this sets the lower of its values. - - This will trigger a callback to SliderListener::sliderValueChanged() for any listeners - that are registered, and will synchronously call the valueChanged() method in case subclasses - want to handle it. - - @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and the max value (in a two-value - slider) or the mid value (in a three-value slider), and - will be snapped to the nearest interval if one has been set. - @param sendUpdateMessage if false, a change to the value will not trigger a call to - any SliderListeners or the valueChanged() method - @param sendMessageSynchronously if true, then a call to the SliderListeners will be made - synchronously; if false, it will be asynchronous - @see getMinValue, setMaxValue, setValue - */ - void setMinValue (double newValue, - const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false); - - /** For a slider with two or three thumbs, this returns the higher of its values. - - For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). - A slider with three values also uses the normal getValue() and setValue() methods to - control the middle value. - - @see getMinValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical - */ - double getMaxValue() const throw(); - - /** For a slider with two or three thumbs, this sets the lower of its values. - - This will trigger a callback to SliderListener::sliderValueChanged() for any listeners - that are registered, and will synchronously call the valueChanged() method in case subclasses - want to handle it. - - @param newValue the new value to set - this will be restricted by the - minimum and maximum range, and the max value (in a two-value - slider) or the mid value (in a three-value slider), and - will be snapped to the nearest interval if one has been set. - @param sendUpdateMessage if false, a change to the value will not trigger a call to - any SliderListeners or the valueChanged() method - @param sendMessageSynchronously if true, then a call to the SliderListeners will be made - synchronously; if false, it will be asynchronous - @see getMaxValue, setMinValue, setValue - */ - void setMaxValue (double newValue, - const bool sendUpdateMessage = true, - const bool sendMessageSynchronously = false); - - //============================================================================== - /** Adds a listener to be called when this slider's value changes. */ - void addListener (SliderListener* const listener) throw(); - - /** Removes a previously-registered listener. */ - void removeListener (SliderListener* const listener) throw(); - - //============================================================================== - /** This lets you choose whether double-clicking moves the slider to a given position. - - By default this is turned off, but it's handy if you want a double-click to act - as a quick way of resetting a slider. Just pass in the value you want it to - go to when double-clicked. - - @see getDoubleClickReturnValue - */ - void setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick) throw(); - - /** Returns the values last set by setDoubleClickReturnValue() method. - - Sets isEnabled to true if double-click is enabled, and returns the value - that was set. - - @see setDoubleClickReturnValue - */ - double getDoubleClickReturnValue (bool& isEnabled) const throw(); - - //============================================================================== - /** Tells the slider whether to keep sending change messages while the user - is dragging the slider. - - If set to true, a change message will only be sent when the user has - dragged the slider and let go. If set to false (the default), then messages - will be continuously sent as they drag it while the mouse button is still - held down. - */ - void setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw(); - - /** This lets you change whether the slider thumb jumps to the mouse position - when you click. - - By default, this is true. If it's false, then the slider moves with relative - motion when you drag it. - - This only applies to linear bars, and won't affect two- or three- value - sliders. - */ - void setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw(); - - /** If enabled, this gives the slider a pop-up bubble which appears while the - slider is being dragged. - - This can be handy if your slider doesn't have a text-box, so that users can - see the value just when they're changing it. - - If you pass a component as the parentComponentToUse parameter, the pop-up - bubble will be added as a child of that component when it's needed. If you - pass 0, the pop-up will be placed on the desktop instead (note that it's a - transparent window, so if you're using an OS that can't do transparent windows - you'll have to add it to a parent component instead). - */ - void setPopupDisplayEnabled (const bool isEnabled, - Component* const parentComponentToUse) throw(); - - /** If this is set to true, then right-clicking on the slider will pop-up - a menu to let the user change the way it works. - - By default this is turned off, but when turned on, the menu will include - things like velocity sensitivity, and for rotary sliders, whether they - use a linear or rotary mouse-drag to move them. - */ - void setPopupMenuEnabled (const bool menuEnabled) throw(); - - /** This can be used to stop the mouse scroll-wheel from moving the slider. - - By default it's enabled. - */ - void setScrollWheelEnabled (const bool enabled) throw(); - - /** Returns a number to indicate which thumb is currently being dragged by the - mouse. - - This will return 0 for the main thumb, 1 for the minimum-value thumb, 2 for - the maximum-value thumb, or -1 if none is currently down. - */ - int getThumbBeingDragged() const throw() { return sliderBeingDragged; } - - //============================================================================== - /** Callback to indicate that the user is about to start dragging the slider. - - @see SliderListener::sliderDragStarted - */ - virtual void startedDragging(); - - /** Callback to indicate that the user has just stopped dragging the slider. - - @see SliderListener::sliderDragEnded - */ - virtual void stoppedDragging(); - - /** Callback to indicate that the user has just moved the slider. - - @see SliderListener::sliderValueChanged - */ - virtual void valueChanged(); - - /** Callback to indicate that the user has just moved the slider. - Note - the valueChanged() method has changed its format and now no longer has - any parameters. Update your code to use the new version. - This version has been left here with an int as its return value to cause - a syntax error if you've got existing code that uses the old version. - */ - virtual int valueChanged (double) { jassertfalse; return 0; } - - //============================================================================== - /** Subclasses can override this to convert a text string to a value. - - When the user enters something into the text-entry box, this method is - called to convert it to a value. - - The default routine just tries to convert it to a double. - - @see getTextFromValue - */ - virtual double getValueFromText (const String& text); - - /** Turns the slider's current value into a text string. - - Subclasses can override this to customise the formatting of the text-entry box. - - The default implementation just turns the value into a string, using - a number of decimal places based on the range interval. If a suffix string - has been set using setTextValueSuffix(), this will be appended to the text. - - @see getValueFromText - */ - virtual const String getTextFromValue (double value); - - /** Sets a suffix to append to the end of the numeric value when it's displayed as - a string. - - This is used by the default implementation of getTextFromValue(), and is just - appended to the numeric value. For more advanced formatting, you can override - getTextFromValue() and do something else. - */ - void setTextValueSuffix (const String& suffix); - - //============================================================================== - /** Allows a user-defined mapping of distance along the slider to its value. - - The default implementation for this performs the skewing operation that - can be set up in the setSkewFactor() method. Override it if you need - some kind of custom mapping instead, but make sure you also implement the - inverse function in valueToProportionOfLength(). - - @param proportion a value 0 to 1.0, indicating a distance along the slider - @returns the slider value that is represented by this position - @see valueToProportionOfLength - */ - virtual double proportionOfLengthToValue (double proportion); - - /** Allows a user-defined mapping of value to the position of the slider along its length. - - The default implementation for this performs the skewing operation that - can be set up in the setSkewFactor() method. Override it if you need - some kind of custom mapping instead, but make sure you also implement the - inverse function in proportionOfLengthToValue(). - - @param value a valid slider value, between the range of values specified in - setRange() - @returns a value 0 to 1.0 indicating the distance along the slider that - represents this value - @see proportionOfLengthToValue - */ - virtual double valueToProportionOfLength (double value); - - /** Returns the X or Y coordinate of a value along the slider's length. - - If the slider is horizontal, this will be the X coordinate of the given - value, relative to the left of the slider. If it's vertical, then this will - be the Y coordinate, relative to the top of the slider. - - If the slider is rotary, this will throw an assertion and return 0. If the - value is out-of-range, it will be constrained to the length of the slider. - */ - float getPositionOfValue (const double value); - - //============================================================================== - /** This can be overridden to allow the slider to snap to user-definable values. - - If overridden, it will be called when the user tries to move the slider to - a given position, and allows a subclass to sanity-check this value, possibly - returning a different value to use instead. - - @param attemptedValue the value the user is trying to enter - @param userIsDragging true if the user is dragging with the mouse; false if - they are entering the value using the text box - @returns the value to use instead - */ - virtual double snapValue (double attemptedValue, const bool userIsDragging); - - - //============================================================================== - /** This can be called to force the text box to update its contents. - - (Not normally needed, as this is done automatically). - */ - void updateText(); - - - /** True if the slider moves horizontally. */ - bool isHorizontal() const throw(); - /** True if the slider moves vertically. */ - bool isVertical() const throw(); - - //============================================================================== - /** A set of colour IDs to use to change the colour of various aspects of the slider. - - These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() - methods. - - @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour - */ - enum ColourIds - { - backgroundColourId = 0x1001200, /**< A colour to use to fill the slider's background. */ - thumbColourId = 0x1001300, /**< The colour to draw the thumb with. It's up to the look - and feel class how this is used. */ - trackColourId = 0x1001310, /**< The colour to draw the groove that the thumb moves along. */ - rotarySliderFillColourId = 0x1001311, /**< For rotary sliders, this colour fills the outer curve. */ - rotarySliderOutlineColourId = 0x1001312, /**< For rotary sliders, this colour is used to draw the outer curve's outline. */ - - textBoxTextColourId = 0x1001400, /**< The colour for the text in the text-editor box used for editing the value. */ - textBoxBackgroundColourId = 0x1001500, /**< The background colour for the text-editor box. */ - textBoxHighlightColourId = 0x1001600, /**< The text highlight colour for the text-editor box. */ - textBoxOutlineColourId = 0x1001700 /**< The colour to use for a border around the text-editor box. */ - }; - - //============================================================================== - juce_UseDebuggingNewOperator - -protected: - /** @internal */ - void labelTextChanged (Label*); - /** @internal */ - void paint (Graphics& g); - /** @internal */ - void resized(); - /** @internal */ - void mouseDown (const MouseEvent& e); - /** @internal */ - void mouseUp (const MouseEvent& e); - /** @internal */ - void mouseDrag (const MouseEvent& e); - /** @internal */ - void mouseDoubleClick (const MouseEvent& e); - /** @internal */ - void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); - /** @internal */ - void modifierKeysChanged (const ModifierKeys& modifiers); - /** @internal */ - void buttonClicked (Button* button); - /** @internal */ - void lookAndFeelChanged(); - /** @internal */ - void enablementChanged(); - /** @internal */ - void focusOfChildComponentChanged (FocusChangeType cause); - /** @internal */ - void handleAsyncUpdate(); - /** @internal */ - void colourChanged(); - -private: - SortedSet listeners; - double currentValue, valueMin, valueMax; - double minimum, maximum, interval, doubleClickReturnValue; - double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle; - double velocityModeSensitivity, velocityModeOffset, minMaxDiff; - int velocityModeThreshold; - float rotaryStart, rotaryEnd; - int numDecimalPlaces, mouseXWhenLastDragged, mouseYWhenLastDragged; - int sliderRegionStart, sliderRegionSize; - int sliderBeingDragged; - int pixelsForFullDragExtent; - Rectangle sliderRect; - String textSuffix; - - SliderStyle style; - TextEntryBoxPosition textBoxPos; - int textBoxWidth, textBoxHeight; - IncDecButtonMode incDecButtonMode; - - bool editableText : 1, doubleClickToValue : 1; - bool isVelocityBased : 1, userKeyOverridesVelocity : 1, rotaryStop : 1; - bool incDecButtonsSideBySide : 1, sendChangeOnlyOnRelease : 1, popupDisplayEnabled : 1; - bool menuEnabled : 1, menuShown : 1, mouseWasHidden : 1, incDecDragged : 1; - bool scrollWheelEnabled : 1, snapsToMousePos : 1; - Font font; - Label* valueBox; - Button* incButton; - Button* decButton; - Component* popupDisplay; - Component* parentForPopupDisplay; - - float getLinearSliderPos (const double value); - void restoreMouseIfHidden(); - void sendDragStart(); - void sendDragEnd(); - double constrainedValue (double value) const throw(); - void triggerChangeMessage (const bool synchronous); - bool incDecDragDirectionIsHorizontal() const throw(); - - Slider (const Slider&); - const Slider& operator= (const Slider&); -}; - - -#endif // __JUCE_SLIDER_JUCEHEADER__ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#ifndef __JUCE_SLIDER_JUCEHEADER__ +#define __JUCE_SLIDER_JUCEHEADER__ + +#include "juce_SliderListener.h" +#include "juce_Label.h" +#include "../buttons/juce_Button.h" +#include "../../../events/juce_AsyncUpdater.h" +#include "../../../../juce_core/containers/juce_SortedSet.h" + + +//============================================================================== +/** + A slider control for changing a value. + + The slider can be horizontal, vertical, or rotary, and can optionally have + a text-box inside it to show an editable display of the current value. + + To use it, create a Slider object and use the setSliderStyle() method + to set up the type you want. To set up the text-entry box, use setTextBoxStyle(). + + To define the values that it can be set to, see the setRange() and setValue() methods. + + There are also lots of custom tweaks you can do by subclassing and overriding + some of the virtual methods, such as changing the scaling, changing the format of + the text display, custom ways of limiting the values, etc. + + You can register SliderListeners with a slider, which will be informed when the value + changes, or a subclass can override valueChanged() to be informed synchronously. + + @see SliderListener +*/ +class JUCE_API Slider : public Component, + public SettableTooltipClient, + private AsyncUpdater, + private ButtonListener, + private LabelListener +{ +public: + //============================================================================== + /** Creates a slider. + + When created, you'll need to set up the slider's style and range with setSliderStyle(), + setRange(), etc. + */ + Slider (const String& componentName); + + /** Destructor. */ + ~Slider(); + + //============================================================================== + /** The types of slider available. + + @see setSliderStyle, setRotaryParameters + */ + enum SliderStyle + { + LinearHorizontal, /**< A traditional horizontal slider. */ + LinearVertical, /**< A traditional vertical slider. */ + LinearBar, /**< A horizontal bar slider with the text label drawn on top of it. */ + Rotary, /**< A rotary control that you move by dragging the mouse in a circular motion, like a knob. + @see setRotaryParameters */ + RotaryHorizontalDrag, /**< A rotary control that you move by dragging the mouse left-to-right. + @see setRotaryParameters */ + RotaryVerticalDrag, /**< A rotary control that you move by dragging the mouse up-and-down. + @see setRotaryParameters */ + IncDecButtons, /**< A pair of buttons that increment or decrement the slider's value by the increment set in setRange(). */ + + TwoValueHorizontal, /**< A horizontal slider that has two thumbs instead of one, so it can show a minimum and maximum value. + @see setMinValue, setMaxValue */ + TwoValueVertical, /**< A vertical slider that has two thumbs instead of one, so it can show a minimum and maximum value. + @see setMinValue, setMaxValue */ + + ThreeValueHorizontal, /**< A horizontal slider that has three thumbs instead of one, so it can show a minimum and maximum + value, with the current value being somewhere between them. + @see setMinValue, setMaxValue */ + ThreeValueVertical, /**< A vertical slider that has three thumbs instead of one, so it can show a minimum and maximum + value, with the current value being somewhere between them. + @see setMinValue, setMaxValue */ + }; + + /** Changes the type of slider interface being used. + + @param newStyle the type of interface + @see setRotaryParameters, setVelocityBasedMode, + */ + void setSliderStyle (const SliderStyle newStyle); + + /** Returns the slider's current style. + + @see setSliderStyle + */ + SliderStyle getSliderStyle() const throw() { return style; } + + //============================================================================== + /** Changes the properties of a rotary slider. + + @param startAngleRadians the angle (in radians, clockwise from the top) at which + the slider's minimum value is represented + @param endAngleRadians the angle (in radians, clockwise from the top) at which + the slider's maximum value is represented. This must be + greater than startAngleRadians + @param stopAtEnd if true, then when the slider is dragged around past the + minimum or maximum, it'll stop there; if false, it'll wrap + back to the opposite value + */ + void setRotaryParameters (const float startAngleRadians, + const float endAngleRadians, + const bool stopAtEnd); + + /** Sets the distance the mouse has to move to drag the slider across + the full extent of its range. + + This only applies when in modes like RotaryHorizontalDrag, where it's using + relative mouse movements to adjust the slider. + */ + void setMouseDragSensitivity (const int distanceForFullScaleDrag); + + //============================================================================== + /** Changes the way the the mouse is used when dragging the slider. + + If true, this will turn on velocity-sensitive dragging, so that + the faster the mouse moves, the bigger the movement to the slider. This + helps when making accurate adjustments if the slider's range is quite large. + + If false, the slider will just try to snap to wherever the mouse is. + */ + void setVelocityBasedMode (const bool isVelocityBased) throw(); + + /** Changes aspects of the scaling used when in velocity-sensitive mode. + + These apply when you've used setVelocityBasedMode() to turn on velocity mode, + or if you're holding down ctrl. + + @param sensitivity higher values than 1.0 increase the range of acceleration used + @param threshold the minimum number of pixels that the mouse needs to move for it + to be treated as a movement + @param offset values greater than 0.0 increase the minimum speed that will be used when + the threshold is reached + @param userCanPressKeyToSwapMode if true, then the user can hold down the ctrl or command + key to toggle velocity-sensitive mode + */ + void setVelocityModeParameters (const double sensitivity = 1.0, + const int threshold = 1, + const double offset = 0.0, + const bool userCanPressKeyToSwapMode = true) throw(); + + //============================================================================== + /** Sets up a skew factor to alter the way values are distributed. + + You may want to use a range of values on the slider where more accuracy + is required towards one end of the range, so this will logarithmically + spread the values across the length of the slider. + + If the factor is < 1.0, the lower end of the range will fill more of the + slider's length; if the factor is > 1.0, the upper end of the range + will be expanded instead. A factor of 1.0 doesn't skew it at all. + + To set the skew position by using a mid-point, use the setSkewFactorFromMidPoint() + method instead. + + @see getSkewFactor, setSkewFactorFromMidPoint + */ + void setSkewFactor (const double factor) throw(); + + /** Sets up a skew factor to alter the way values are distributed. + + This allows you to specify the slider value that should appear in the + centre of the slider's visible range. + + @see setSkewFactor, getSkewFactor + */ + void setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint) throw(); + + /** Returns the current skew factor. + + See setSkewFactor for more info. + + @see setSkewFactor, setSkewFactorFromMidPoint + */ + double getSkewFactor() const throw() { return skewFactor; } + + //============================================================================== + /** Used by setIncDecButtonsMode(). + */ + enum IncDecButtonMode + { + incDecButtonsNotDraggable, + incDecButtonsDraggable_AutoDirection, + incDecButtonsDraggable_Horizontal, + incDecButtonsDraggable_Vertical + }; + + /** When the style is IncDecButtons, this lets you turn on a mode where the mouse + can be dragged on the buttons to drag the values. + + By default this is turned off. When enabled, clicking on the buttons still works + them as normal, but by holding down the mouse on a button and dragging it a little + distance, it flips into a mode where the value can be dragged. The drag direction can + either be set explicitly to be vertical or horizontal, or can be set to + incDecButtonsDraggable_AutoDirection so that it depends on whether the buttons + are side-by-side or above each other. + */ + void setIncDecButtonsMode (const IncDecButtonMode mode); + + //============================================================================== + /** The position of the slider's text-entry box. + + @see setTextBoxStyle + */ + enum TextEntryBoxPosition + { + NoTextBox, /**< Doesn't display a text box. */ + TextBoxLeft, /**< Puts the text box to the left of the slider, vertically centred. */ + TextBoxRight, /**< Puts the text box to the right of the slider, vertically centred. */ + TextBoxAbove, /**< Puts the text box above the slider, horizontally centred. */ + TextBoxBelow /**< Puts the text box below the slider, horizontally centred. */ + }; + + /** Changes the location and properties of the text-entry box. + + @param newPosition where it should go (or NoTextBox to not have one at all) + @param isReadOnly if true, it's a read-only display + @param textEntryBoxWidth the width of the text-box in pixels. Make sure this leaves enough + room for the slider as well! + @param textEntryBoxHeight the height of the text-box in pixels. Make sure this leaves enough + room for the slider as well! + + @see setTextBoxIsEditable, getValueFromText, getTextFromValue + */ + void setTextBoxStyle (const TextEntryBoxPosition newPosition, + const bool isReadOnly, + const int textEntryBoxWidth, + const int textEntryBoxHeight); + + /** Returns the status of the text-box. + @see setTextBoxStyle + */ + const TextEntryBoxPosition getTextBoxPosition() const throw() { return textBoxPos; } + + /** Returns the width used for the text-box. + @see setTextBoxStyle + */ + int getTextBoxWidth() const throw() { return textBoxWidth; } + + /** Returns the height used for the text-box. + @see setTextBoxStyle + */ + int getTextBoxHeight() const throw() { return textBoxHeight; } + + /** Makes the text-box editable. + + By default this is true, and the user can enter values into the textbox, + but it can be turned off if that's not suitable. + + @see setTextBoxStyle, getValueFromText, getTextFromValue + */ + void setTextBoxIsEditable (const bool shouldBeEditable) throw(); + + /** Returns true if the text-box is read-only. + @see setTextBoxStyle + */ + bool isTextBoxEditable() const throw() { return editableText; } + + /** If the text-box is editable, this will give it the focus so that the user can + type directly into it. + + This is basically the effect as the user clicking on it. + */ + void showTextBox(); + + /** If the text-box currently has focus and is being edited, this resets it and takes keyboard + focus away from it. + + @param discardCurrentEditorContents if true, the slider's value will be left + unchanged; if false, the current contents of the + text editor will be used to set the slider position + before it is hidden. + */ + void hideTextBox (const bool discardCurrentEditorContents); + + + //============================================================================== + /** Changes the slider's current value. + + This will trigger a callback to SliderListener::sliderValueChanged() for any listeners + that are registered, and will synchronously call the valueChanged() method in case subclasses + want to handle it. + + @param newValue the new value to set - this will be restricted by the + minimum and maximum range, and will be snapped to the + nearest interval if one has been set + @param sendUpdateMessage if false, a change to the value will not trigger a call to + any SliderListeners or the valueChanged() method + @param sendMessageSynchronously if true, then a call to the SliderListeners will be made + synchronously; if false, it will be asynchronous + */ + void setValue (double newValue, + const bool sendUpdateMessage = true, + const bool sendMessageSynchronously = false); + + /** Returns the slider's current value. */ + double getValue() const throw(); + + //============================================================================== + /** Sets the limits that the slider's value can take. + + @param newMinimum the lowest value allowed + @param newMaximum the highest value allowed + @param newInterval the steps in which the value is allowed to increase - if this + is not zero, the value will always be (newMinimum + (newInterval * an integer)). + */ + void setRange (const double newMinimum, + const double newMaximum, + const double newInterval = 0); + + /** Returns the current maximum value. + @see setRange + */ + double getMaximum() const throw() { return maximum; } + + /** Returns the current minimum value. + @see setRange + */ + double getMinimum() const throw() { return minimum; } + + /** Returns the current step-size for values. + @see setRange + */ + double getInterval() const throw() { return interval; } + + //============================================================================== + /** For a slider with two or three thumbs, this returns the lower of its values. + + For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). + A slider with three values also uses the normal getValue() and setValue() methods to + control the middle value. + + @see setMinValue, getMaxValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical + */ + double getMinValue() const throw(); + + /** For a slider with two or three thumbs, this sets the lower of its values. + + This will trigger a callback to SliderListener::sliderValueChanged() for any listeners + that are registered, and will synchronously call the valueChanged() method in case subclasses + want to handle it. + + @param newValue the new value to set - this will be restricted by the + minimum and maximum range, and will be snapped to the nearest + interval if one has been set. + @param sendUpdateMessage if false, a change to the value will not trigger a call to + any SliderListeners or the valueChanged() method + @param sendMessageSynchronously if true, then a call to the SliderListeners will be made + synchronously; if false, it will be asynchronous + @param allowNudgingOfOtherValues if false, this value will be restricted to being below the + max value (in a two-value slider) or the mid value (in a three-value + slider). If false, then if this value goes beyond those values, + it will push them along with it. + @see getMinValue, setMaxValue, setValue + */ + void setMinValue (double newValue, + const bool sendUpdateMessage = true, + const bool sendMessageSynchronously = false, + const bool allowNudgingOfOtherValues = false); + + /** For a slider with two or three thumbs, this returns the higher of its values. + + For a two-value slider, the values are controlled with getMinValue() and getMaxValue(). + A slider with three values also uses the normal getValue() and setValue() methods to + control the middle value. + + @see getMinValue, TwoValueHorizontal, TwoValueVertical, ThreeValueHorizontal, ThreeValueVertical + */ + double getMaxValue() const throw(); + + /** For a slider with two or three thumbs, this sets the lower of its values. + + This will trigger a callback to SliderListener::sliderValueChanged() for any listeners + that are registered, and will synchronously call the valueChanged() method in case subclasses + want to handle it. + + @param newValue the new value to set - this will be restricted by the + minimum and maximum range, and will be snapped to the nearest + interval if one has been set. + @param sendUpdateMessage if false, a change to the value will not trigger a call to + any SliderListeners or the valueChanged() method + @param sendMessageSynchronously if true, then a call to the SliderListeners will be made + synchronously; if false, it will be asynchronous + @param allowNudgingOfOtherValues if false, this value will be restricted to being above the + min value (in a two-value slider) or the mid value (in a three-value + slider). If false, then if this value goes beyond those values, + it will push them along with it. + @see getMaxValue, setMinValue, setValue + */ + void setMaxValue (double newValue, + const bool sendUpdateMessage = true, + const bool sendMessageSynchronously = false, + const bool allowNudgingOfOtherValues = false); + + //============================================================================== + /** Adds a listener to be called when this slider's value changes. */ + void addListener (SliderListener* const listener) throw(); + + /** Removes a previously-registered listener. */ + void removeListener (SliderListener* const listener) throw(); + + //============================================================================== + /** This lets you choose whether double-clicking moves the slider to a given position. + + By default this is turned off, but it's handy if you want a double-click to act + as a quick way of resetting a slider. Just pass in the value you want it to + go to when double-clicked. + + @see getDoubleClickReturnValue + */ + void setDoubleClickReturnValue (const bool isDoubleClickEnabled, + const double valueToSetOnDoubleClick) throw(); + + /** Returns the values last set by setDoubleClickReturnValue() method. + + Sets isEnabled to true if double-click is enabled, and returns the value + that was set. + + @see setDoubleClickReturnValue + */ + double getDoubleClickReturnValue (bool& isEnabled) const throw(); + + //============================================================================== + /** Tells the slider whether to keep sending change messages while the user + is dragging the slider. + + If set to true, a change message will only be sent when the user has + dragged the slider and let go. If set to false (the default), then messages + will be continuously sent as they drag it while the mouse button is still + held down. + */ + void setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw(); + + /** This lets you change whether the slider thumb jumps to the mouse position + when you click. + + By default, this is true. If it's false, then the slider moves with relative + motion when you drag it. + + This only applies to linear bars, and won't affect two- or three- value + sliders. + */ + void setSliderSnapsToMousePosition (const bool shouldSnapToMouse) throw(); + + /** If enabled, this gives the slider a pop-up bubble which appears while the + slider is being dragged. + + This can be handy if your slider doesn't have a text-box, so that users can + see the value just when they're changing it. + + If you pass a component as the parentComponentToUse parameter, the pop-up + bubble will be added as a child of that component when it's needed. If you + pass 0, the pop-up will be placed on the desktop instead (note that it's a + transparent window, so if you're using an OS that can't do transparent windows + you'll have to add it to a parent component instead). + */ + void setPopupDisplayEnabled (const bool isEnabled, + Component* const parentComponentToUse) throw(); + + /** If this is set to true, then right-clicking on the slider will pop-up + a menu to let the user change the way it works. + + By default this is turned off, but when turned on, the menu will include + things like velocity sensitivity, and for rotary sliders, whether they + use a linear or rotary mouse-drag to move them. + */ + void setPopupMenuEnabled (const bool menuEnabled) throw(); + + /** This can be used to stop the mouse scroll-wheel from moving the slider. + + By default it's enabled. + */ + void setScrollWheelEnabled (const bool enabled) throw(); + + /** Returns a number to indicate which thumb is currently being dragged by the + mouse. + + This will return 0 for the main thumb, 1 for the minimum-value thumb, 2 for + the maximum-value thumb, or -1 if none is currently down. + */ + int getThumbBeingDragged() const throw() { return sliderBeingDragged; } + + //============================================================================== + /** Callback to indicate that the user is about to start dragging the slider. + + @see SliderListener::sliderDragStarted + */ + virtual void startedDragging(); + + /** Callback to indicate that the user has just stopped dragging the slider. + + @see SliderListener::sliderDragEnded + */ + virtual void stoppedDragging(); + + /** Callback to indicate that the user has just moved the slider. + + @see SliderListener::sliderValueChanged + */ + virtual void valueChanged(); + + /** Callback to indicate that the user has just moved the slider. + Note - the valueChanged() method has changed its format and now no longer has + any parameters. Update your code to use the new version. + This version has been left here with an int as its return value to cause + a syntax error if you've got existing code that uses the old version. + */ + virtual int valueChanged (double) { jassertfalse; return 0; } + + //============================================================================== + /** Subclasses can override this to convert a text string to a value. + + When the user enters something into the text-entry box, this method is + called to convert it to a value. + + The default routine just tries to convert it to a double. + + @see getTextFromValue + */ + virtual double getValueFromText (const String& text); + + /** Turns the slider's current value into a text string. + + Subclasses can override this to customise the formatting of the text-entry box. + + The default implementation just turns the value into a string, using + a number of decimal places based on the range interval. If a suffix string + has been set using setTextValueSuffix(), this will be appended to the text. + + @see getValueFromText + */ + virtual const String getTextFromValue (double value); + + /** Sets a suffix to append to the end of the numeric value when it's displayed as + a string. + + This is used by the default implementation of getTextFromValue(), and is just + appended to the numeric value. For more advanced formatting, you can override + getTextFromValue() and do something else. + */ + void setTextValueSuffix (const String& suffix); + + //============================================================================== + /** Allows a user-defined mapping of distance along the slider to its value. + + The default implementation for this performs the skewing operation that + can be set up in the setSkewFactor() method. Override it if you need + some kind of custom mapping instead, but make sure you also implement the + inverse function in valueToProportionOfLength(). + + @param proportion a value 0 to 1.0, indicating a distance along the slider + @returns the slider value that is represented by this position + @see valueToProportionOfLength + */ + virtual double proportionOfLengthToValue (double proportion); + + /** Allows a user-defined mapping of value to the position of the slider along its length. + + The default implementation for this performs the skewing operation that + can be set up in the setSkewFactor() method. Override it if you need + some kind of custom mapping instead, but make sure you also implement the + inverse function in proportionOfLengthToValue(). + + @param value a valid slider value, between the range of values specified in + setRange() + @returns a value 0 to 1.0 indicating the distance along the slider that + represents this value + @see proportionOfLengthToValue + */ + virtual double valueToProportionOfLength (double value); + + /** Returns the X or Y coordinate of a value along the slider's length. + + If the slider is horizontal, this will be the X coordinate of the given + value, relative to the left of the slider. If it's vertical, then this will + be the Y coordinate, relative to the top of the slider. + + If the slider is rotary, this will throw an assertion and return 0. If the + value is out-of-range, it will be constrained to the length of the slider. + */ + float getPositionOfValue (const double value); + + //============================================================================== + /** This can be overridden to allow the slider to snap to user-definable values. + + If overridden, it will be called when the user tries to move the slider to + a given position, and allows a subclass to sanity-check this value, possibly + returning a different value to use instead. + + @param attemptedValue the value the user is trying to enter + @param userIsDragging true if the user is dragging with the mouse; false if + they are entering the value using the text box + @returns the value to use instead + */ + virtual double snapValue (double attemptedValue, const bool userIsDragging); + + + //============================================================================== + /** This can be called to force the text box to update its contents. + + (Not normally needed, as this is done automatically). + */ + void updateText(); + + + /** True if the slider moves horizontally. */ + bool isHorizontal() const throw(); + /** True if the slider moves vertically. */ + bool isVertical() const throw(); + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the slider. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + backgroundColourId = 0x1001200, /**< A colour to use to fill the slider's background. */ + thumbColourId = 0x1001300, /**< The colour to draw the thumb with. It's up to the look + and feel class how this is used. */ + trackColourId = 0x1001310, /**< The colour to draw the groove that the thumb moves along. */ + rotarySliderFillColourId = 0x1001311, /**< For rotary sliders, this colour fills the outer curve. */ + rotarySliderOutlineColourId = 0x1001312, /**< For rotary sliders, this colour is used to draw the outer curve's outline. */ + + textBoxTextColourId = 0x1001400, /**< The colour for the text in the text-editor box used for editing the value. */ + textBoxBackgroundColourId = 0x1001500, /**< The background colour for the text-editor box. */ + textBoxHighlightColourId = 0x1001600, /**< The text highlight colour for the text-editor box. */ + textBoxOutlineColourId = 0x1001700 /**< The colour to use for a border around the text-editor box. */ + }; + + //============================================================================== + juce_UseDebuggingNewOperator + +protected: + /** @internal */ + void labelTextChanged (Label*); + /** @internal */ + void paint (Graphics& g); + /** @internal */ + void resized(); + /** @internal */ + void mouseDown (const MouseEvent& e); + /** @internal */ + void mouseUp (const MouseEvent& e); + /** @internal */ + void mouseDrag (const MouseEvent& e); + /** @internal */ + void mouseDoubleClick (const MouseEvent& e); + /** @internal */ + void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); + /** @internal */ + void modifierKeysChanged (const ModifierKeys& modifiers); + /** @internal */ + void buttonClicked (Button* button); + /** @internal */ + void lookAndFeelChanged(); + /** @internal */ + void enablementChanged(); + /** @internal */ + void focusOfChildComponentChanged (FocusChangeType cause); + /** @internal */ + void handleAsyncUpdate(); + /** @internal */ + void colourChanged(); + +private: + SortedSet listeners; + double currentValue, valueMin, valueMax; + double minimum, maximum, interval, doubleClickReturnValue; + double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle; + double velocityModeSensitivity, velocityModeOffset, minMaxDiff; + int velocityModeThreshold; + float rotaryStart, rotaryEnd; + int numDecimalPlaces, mouseXWhenLastDragged, mouseYWhenLastDragged; + int mouseDragStartX, mouseDragStartY; + int sliderRegionStart, sliderRegionSize; + int sliderBeingDragged; + int pixelsForFullDragExtent; + Rectangle sliderRect; + String textSuffix; + + SliderStyle style; + TextEntryBoxPosition textBoxPos; + int textBoxWidth, textBoxHeight; + IncDecButtonMode incDecButtonMode; + + bool editableText : 1, doubleClickToValue : 1; + bool isVelocityBased : 1, userKeyOverridesVelocity : 1, rotaryStop : 1; + bool incDecButtonsSideBySide : 1, sendChangeOnlyOnRelease : 1, popupDisplayEnabled : 1; + bool menuEnabled : 1, menuShown : 1, mouseWasHidden : 1, incDecDragged : 1; + bool scrollWheelEnabled : 1, snapsToMousePos : 1; + Font font; + Label* valueBox; + Button* incButton; + Button* decButton; + Component* popupDisplay; + Component* parentForPopupDisplay; + + float getLinearSliderPos (const double value); + void restoreMouseIfHidden(); + void sendDragStart(); + void sendDragEnd(); + double constrainedValue (double value) const throw(); + void triggerChangeMessage (const bool synchronous); + bool incDecDragDirectionIsHorizontal() const throw(); + + Slider (const Slider&); + const Slider& operator= (const Slider&); +}; + + +#endif // __JUCE_SLIDER_JUCEHEADER__ diff --git a/src/juce_appframework/gui/components/controls/juce_TreeView.cpp b/src/juce_appframework/gui/components/controls/juce_TreeView.cpp index 8e27a1b2c6..6e7fddc396 100644 --- a/src/juce_appframework/gui/components/controls/juce_TreeView.cpp +++ b/src/juce_appframework/gui/components/controls/juce_TreeView.cpp @@ -803,6 +803,7 @@ TreeViewItem::TreeViewItem() selected (false), redrawNeeded (true), drawLinesInside (true), + drawsInLeftMargin (false), openness (opennessDefault) { static int nextUID = 0; @@ -1077,6 +1078,11 @@ int TreeViewItem::getIndentX() const throw() return x; } +void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) throw() +{ + drawsInLeftMargin = canDrawInLeftMargin; +} + void TreeViewItem::paintRecursively (Graphics& g, int width) { jassert (ownerView != 0); @@ -1152,7 +1158,8 @@ void TreeViewItem::paintRecursively (Graphics& g, int width) g.saveState(); g.setOrigin (indent, 0); - if (g.reduceClipRegion (0, 0, itemW, itemHeight)) + if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0, + drawsInLeftMargin ? itemW + indent : itemW, itemHeight)) paintItem (g, itemW, itemHeight); g.restoreState(); diff --git a/src/juce_appframework/gui/components/controls/juce_TreeView.h b/src/juce_appframework/gui/components/controls/juce_TreeView.h index 2d6a162c34..059d1907b2 100644 --- a/src/juce_appframework/gui/components/controls/juce_TreeView.h +++ b/src/juce_appframework/gui/components/controls/juce_TreeView.h @@ -345,6 +345,21 @@ public: */ virtual const String getDragSourceDescription(); + /** Sets a flag to indicate that the item wants to be allowed + to draw all the way across to the left edge of the treeview. + + By default this is false, which means that when the paintItem() + method is called, its graphics context is clipped to only allow + drawing within the item's rectangle. If this flag is set to true, + then the graphics context isn't clipped on its left side, so it + can draw all the way across to the left margin. Note that the + context will still have its origin in the same place though, so + the coordinates of anything to its left will be negative. It's + mostly useful if you want to draw a wider bar behind the + highlighted item. + */ + void setDrawsInLeftMargin (bool canDrawInLeftMargin) throw(); + //============================================================================== juce_UseDebuggingNewOperator @@ -357,6 +372,7 @@ private: bool selected : 1; bool redrawNeeded : 1; bool drawLinesInside : 1; + bool drawsInLeftMargin : 1; unsigned int openness : 2; friend class TreeView; diff --git a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp index cc3f17e35b..e02ba8e637 100644 --- a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp +++ b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.cpp @@ -187,6 +187,14 @@ void ComponentBoundsConstrainer::setBoundsForComponent (Component* const compone applyBoundsToComponent (component, x, y, w, h); } +void ComponentBoundsConstrainer::checkComponentBounds (Component* component) +{ + setBoundsForComponent (component, + component->getX(), component->getY(), + component->getWidth(), component->getHeight(), + false, false, false, false); +} + void ComponentBoundsConstrainer::applyBoundsToComponent (Component* component, int x, int y, int w, int h) { diff --git a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h index 0f26f538be..8acfb81bca 100644 --- a/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h +++ b/src/juce_appframework/gui/components/layout/juce_ComponentBoundsConstrainer.h @@ -172,6 +172,11 @@ public: const bool isStretchingBottom, const bool isStretchingRight); + /** Performs a check on the current size of a component, and moves or resizes + it if it fails the constraints. + */ + void checkComponentBounds (Component* component); + /** Called by setBoundsForComponent() to apply a new constrained size to a component. diff --git a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp index 25cecc07af..4c61297073 100644 --- a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp +++ b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp @@ -1,548 +1,550 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_TabbedComponent.h" -#include "../menus/juce_PopupMenu.h" -#include "../lookandfeel/juce_LookAndFeel.h" - - -//============================================================================== -TabBarButton::TabBarButton (const String& name, - TabbedButtonBar* const owner_, - const int index) - : Button (name), - owner (owner_), - tabIndex (index), - overlapPixels (0) -{ - shadow.setShadowProperties (2.2f, 0.7f, 0, 0); - setComponentEffect (&shadow); - setWantsKeyboardFocus (false); -} - -TabBarButton::~TabBarButton() -{ -} - -void TabBarButton::paintButton (Graphics& g, - bool isMouseOverButton, - bool isButtonDown) -{ - int x, y, w, h; - getActiveArea (x, y, w, h); - - g.setOrigin (x, y); - - getLookAndFeel() - .drawTabButton (g, w, h, - owner->getTabBackgroundColour (tabIndex), - tabIndex, getButtonText(), *this, - owner->getOrientation(), - isMouseOverButton, isButtonDown, - getToggleState()); -} - -void TabBarButton::clicked (const ModifierKeys& mods) -{ - if (mods.isPopupMenu()) - owner->popupMenuClickOnTab (tabIndex, getButtonText()); - else - owner->setCurrentTabIndex (tabIndex); -} - -bool TabBarButton::hitTest (int mx, int my) -{ - int x, y, w, h; - getActiveArea (x, y, w, h); - - if (owner->getOrientation() == TabbedButtonBar::TabsAtLeft - || owner->getOrientation() == TabbedButtonBar::TabsAtRight) - { - if (((unsigned int) mx) < (unsigned int) getWidth() - && my >= y + overlapPixels - && my < y + h - overlapPixels) - return true; - } - else - { - if (mx >= x + overlapPixels && mx < x + w - overlapPixels - && ((unsigned int) my) < (unsigned int) getHeight()) - return true; - } - - Path p; - getLookAndFeel() - .createTabButtonShape (p, w, h, tabIndex, getButtonText(), *this, - owner->getOrientation(), - false, false, getToggleState()); - - return p.contains ((float) (mx - x), - (float) (my - y)); -} - -int TabBarButton::getBestTabLength (const int depth) -{ - return jlimit (depth * 2, - depth * 7, - getLookAndFeel().getTabButtonBestWidth (tabIndex, getButtonText(), depth, *this)); -} - -void TabBarButton::getActiveArea (int& x, int& y, int& w, int& h) -{ - x = 0; - y = 0; - int r = getWidth(); - int b = getHeight(); - - const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage(); - - if (owner->getOrientation() != TabbedButtonBar::TabsAtLeft) - r -= spaceAroundImage; - - if (owner->getOrientation() != TabbedButtonBar::TabsAtRight) - x += spaceAroundImage; - - if (owner->getOrientation() != TabbedButtonBar::TabsAtBottom) - y += spaceAroundImage; - - if (owner->getOrientation() != TabbedButtonBar::TabsAtTop) - b -= spaceAroundImage; - - w = r - x; - h = b - y; -} - - -//============================================================================== -class TabAreaBehindFrontButtonComponent : public Component -{ -public: - TabAreaBehindFrontButtonComponent (TabbedButtonBar* const owner_) - : owner (owner_) - { - setInterceptsMouseClicks (false, false); - } - - ~TabAreaBehindFrontButtonComponent() - { - } - - void paint (Graphics& g) - { - getLookAndFeel() - .drawTabAreaBehindFrontButton (g, getWidth(), getHeight(), - *owner, owner->getOrientation()); - } - - void enablementChanged() - { - repaint(); - } - -private: - TabbedButtonBar* const owner; - - TabAreaBehindFrontButtonComponent (const TabAreaBehindFrontButtonComponent&); - const TabAreaBehindFrontButtonComponent& operator= (const TabAreaBehindFrontButtonComponent&); -}; - - -//============================================================================== -TabbedButtonBar::TabbedButtonBar (const Orientation orientation_) - : orientation (orientation_), - currentTabIndex (-1), - extraTabsButton (0) -{ - setInterceptsMouseClicks (false, true); - addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this)); - setFocusContainer (true); -} - -TabbedButtonBar::~TabbedButtonBar() -{ - deleteAllChildren(); -} - -//============================================================================== -void TabbedButtonBar::setOrientation (const Orientation newOrientation) -{ - orientation = newOrientation; - - for (int i = getNumChildComponents(); --i >= 0;) - getChildComponent (i)->resized(); - - resized(); -} - -TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int index) -{ - return new TabBarButton (name, this, index); -} - -//============================================================================== -void TabbedButtonBar::clearTabs() -{ - tabs.clear(); - tabColours.clear(); - currentTabIndex = -1; - - deleteAndZero (extraTabsButton); - removeChildComponent (behindFrontTab); - deleteAllChildren(); - addChildComponent (behindFrontTab); - - setCurrentTabIndex (-1); -} - -void TabbedButtonBar::addTab (const String& tabName, - const Colour& tabBackgroundColour, - int insertIndex) -{ - jassert (tabName.isNotEmpty()); // you have to give them all a name.. - - if (tabName.isNotEmpty()) - { - if (((unsigned int) insertIndex) > (unsigned int) tabs.size()) - insertIndex = tabs.size(); - - for (int i = tabs.size(); --i >= insertIndex;) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - tb->tabIndex++; - } - - tabs.insert (insertIndex, tabName); - tabColours.insert (insertIndex, tabBackgroundColour); - - TabBarButton* const tb = createTabButton (tabName, insertIndex); - jassert (tb != 0); // your createTabButton() mustn't return zero! - - addAndMakeVisible (tb, insertIndex); - - resized(); - - if (currentTabIndex < 0) - setCurrentTabIndex (0); - } -} - -void TabbedButtonBar::setTabName (const int tabIndex, - const String& newName) -{ - if (((unsigned int) tabIndex) < (unsigned int) tabs.size() - && tabs[tabIndex] != newName) - { - tabs.set (tabIndex, newName); - - TabBarButton* const tb = getTabButton (tabIndex); - - if (tb != 0) - tb->setButtonText (newName); - - resized(); - } -} - -void TabbedButtonBar::removeTab (const int tabIndex) -{ - if (((unsigned int) tabIndex) < (unsigned int) tabs.size()) - { - const int oldTabIndex = currentTabIndex; - if (currentTabIndex == tabIndex) - currentTabIndex = -1; - - tabs.remove (tabIndex); - tabColours.remove (tabIndex); - - TabBarButton* const tb = getTabButton (tabIndex); - - if (tb != 0) - delete tb; - - for (int i = tabIndex + 1; i <= tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - tb->tabIndex--; - } - - resized(); - - setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex)); - } -} - -void TabbedButtonBar::moveTab (const int currentIndex, - const int newIndex) -{ - tabs.move (currentIndex, newIndex); - tabColours.move (currentIndex, newIndex); - resized(); -} - -int TabbedButtonBar::getNumTabs() const -{ - return tabs.size(); -} - -const StringArray TabbedButtonBar::getTabNames() const -{ - return tabs; -} - -void TabbedButtonBar::setCurrentTabIndex (int newIndex) -{ - if (currentTabIndex != newIndex) - { - if (((unsigned int) newIndex) >= (unsigned int) tabs.size()) - newIndex = -1; - - currentTabIndex = newIndex; - - for (int i = 0; i < getNumChildComponents(); ++i) - { - TabBarButton* const tb = dynamic_cast (getChildComponent (i)); - - if (tb != 0) - tb->setToggleState (tb->tabIndex == newIndex, false); - } - - resized(); - sendChangeMessage (this); - - currentTabChanged (newIndex, newIndex >= 0 ? tabs [newIndex] : String::empty); - } -} - -TabBarButton* TabbedButtonBar::getTabButton (const int index) const -{ - for (int i = getNumChildComponents(); --i >= 0;) - { - TabBarButton* const tb = dynamic_cast (getChildComponent (i)); - - if (tb != 0 && tb->tabIndex == index) - return tb; - } - - return 0; -} - -void TabbedButtonBar::lookAndFeelChanged() -{ - deleteAndZero (extraTabsButton); - resized(); -} - -void TabbedButtonBar::resized() -{ - const double minimumScale = 0.7; - int depth = getWidth(); - int length = getHeight(); - - if (orientation == TabsAtTop || orientation == TabsAtBottom) - swapVariables (depth, length); - - const int overlap = getLookAndFeel().getTabButtonOverlap (depth) - + getLookAndFeel().getTabButtonSpaceAroundImage() * 2; - - int i, totalLength = overlap; - int numVisibleButtons = tabs.size(); - - for (i = 0; i < getNumChildComponents(); ++i) - { - TabBarButton* const tb = dynamic_cast (getChildComponent (i)); - - if (tb != 0) - { - totalLength += tb->getBestTabLength (depth) - overlap; - tb->overlapPixels = overlap / 2; - } - } - - double scale = 1.0; - - if (totalLength > length) - scale = jmax (minimumScale, length / (double) totalLength); - - const bool isTooBig = totalLength * scale > length; - int tabsButtonPos = 0; - - if (isTooBig) - { - if (extraTabsButton == 0) - { - addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton()); - extraTabsButton->addButtonListener (this); - extraTabsButton->setAlwaysOnTop (true); - extraTabsButton->setTriggeredOnMouseDown (true); - } - - const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f)); - extraTabsButton->setSize (buttonSize, buttonSize); - - if (orientation == TabsAtTop || orientation == TabsAtBottom) - { - tabsButtonPos = getWidth() - buttonSize / 2 - 1; - extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2); - } - else - { - tabsButtonPos = getHeight() - buttonSize / 2 - 1; - extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos); - } - - totalLength = 0; - - for (i = 0; i < tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - { - const int newLength = totalLength + tb->getBestTabLength (depth); - - if (i > 0 && newLength * minimumScale > tabsButtonPos) - { - totalLength += overlap; - break; - } - - numVisibleButtons = i + 1; - totalLength = newLength - overlap; - - } - } - - scale = jmax (minimumScale, tabsButtonPos / (double) totalLength); - } - else - { - deleteAndZero (extraTabsButton); - } - - int pos = 0; - - TabBarButton* frontTab = 0; - - for (i = 0; i < tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0) - { - const int bestLength = roundDoubleToInt (scale * tb->getBestTabLength (depth)); - - if (i < numVisibleButtons) - { - if (orientation == TabsAtTop || orientation == TabsAtBottom) - tb->setBounds (pos, 0, bestLength, getHeight()); - else - tb->setBounds (0, pos, getWidth(), bestLength); - - tb->toBack(); - - if (tb->tabIndex == currentTabIndex) - frontTab = tb; - - tb->setVisible (true); - } - else - { - tb->setVisible (false); - } - - pos += bestLength - overlap; - } - } - - behindFrontTab->setBounds (0, 0, getWidth(), getHeight()); - - if (frontTab != 0) - { - frontTab->toFront (false); - behindFrontTab->toBehind (frontTab); - } -} - -//============================================================================== -const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex) -{ - return tabColours [tabIndex]; -} - -void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour) -{ - if (((unsigned int) tabIndex) < (unsigned int) tabColours.size() - && tabColours [tabIndex] != newColour) - { - tabColours.set (tabIndex, newColour); - repaint(); - } -} - -void TabbedButtonBar::buttonClicked (Button* button) -{ - if (extraTabsButton == button) - { - PopupMenu m; - - for (int i = 0; i < tabs.size(); ++i) - { - TabBarButton* const tb = getTabButton (i); - - if (tb != 0 && ! tb->isVisible()) - m.addItem (tb->tabIndex + 1, tabs[i], true, i == currentTabIndex); - } - - const int res = m.showAt (extraTabsButton); - - if (res != 0) - setCurrentTabIndex (res - 1); - } -} - -//============================================================================== -void TabbedButtonBar::currentTabChanged (const int, const String&) -{ -} - -void TabbedButtonBar::popupMenuClickOnTab (const int, const String&) -{ -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_TabbedComponent.h" +#include "../menus/juce_PopupMenu.h" +#include "../lookandfeel/juce_LookAndFeel.h" + + +//============================================================================== +TabBarButton::TabBarButton (const String& name, + TabbedButtonBar* const owner_, + const int index) + : Button (name), + owner (owner_), + tabIndex (index), + overlapPixels (0) +{ + shadow.setShadowProperties (2.2f, 0.7f, 0, 0); + setComponentEffect (&shadow); + setWantsKeyboardFocus (false); +} + +TabBarButton::~TabBarButton() +{ +} + +void TabBarButton::paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) +{ + int x, y, w, h; + getActiveArea (x, y, w, h); + + g.setOrigin (x, y); + + getLookAndFeel() + .drawTabButton (g, w, h, + owner->getTabBackgroundColour (tabIndex), + tabIndex, getButtonText(), *this, + owner->getOrientation(), + isMouseOverButton, isButtonDown, + getToggleState()); +} + +void TabBarButton::clicked (const ModifierKeys& mods) +{ + if (mods.isPopupMenu()) + owner->popupMenuClickOnTab (tabIndex, getButtonText()); + else + owner->setCurrentTabIndex (tabIndex); +} + +bool TabBarButton::hitTest (int mx, int my) +{ + int x, y, w, h; + getActiveArea (x, y, w, h); + + if (owner->getOrientation() == TabbedButtonBar::TabsAtLeft + || owner->getOrientation() == TabbedButtonBar::TabsAtRight) + { + if (((unsigned int) mx) < (unsigned int) getWidth() + && my >= y + overlapPixels + && my < y + h - overlapPixels) + return true; + } + else + { + if (mx >= x + overlapPixels && mx < x + w - overlapPixels + && ((unsigned int) my) < (unsigned int) getHeight()) + return true; + } + + Path p; + getLookAndFeel() + .createTabButtonShape (p, w, h, tabIndex, getButtonText(), *this, + owner->getOrientation(), + false, false, getToggleState()); + + return p.contains ((float) (mx - x), + (float) (my - y)); +} + +int TabBarButton::getBestTabLength (const int depth) +{ + return jlimit (depth * 2, + depth * 7, + getLookAndFeel().getTabButtonBestWidth (tabIndex, getButtonText(), depth, *this)); +} + +void TabBarButton::getActiveArea (int& x, int& y, int& w, int& h) +{ + x = 0; + y = 0; + int r = getWidth(); + int b = getHeight(); + + const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage(); + + if (owner->getOrientation() != TabbedButtonBar::TabsAtLeft) + r -= spaceAroundImage; + + if (owner->getOrientation() != TabbedButtonBar::TabsAtRight) + x += spaceAroundImage; + + if (owner->getOrientation() != TabbedButtonBar::TabsAtBottom) + y += spaceAroundImage; + + if (owner->getOrientation() != TabbedButtonBar::TabsAtTop) + b -= spaceAroundImage; + + w = r - x; + h = b - y; +} + + +//============================================================================== +class TabAreaBehindFrontButtonComponent : public Component +{ +public: + TabAreaBehindFrontButtonComponent (TabbedButtonBar* const owner_) + : owner (owner_) + { + setInterceptsMouseClicks (false, false); + } + + ~TabAreaBehindFrontButtonComponent() + { + } + + void paint (Graphics& g) + { + getLookAndFeel() + .drawTabAreaBehindFrontButton (g, getWidth(), getHeight(), + *owner, owner->getOrientation()); + } + + void enablementChanged() + { + repaint(); + } + +private: + TabbedButtonBar* const owner; + + TabAreaBehindFrontButtonComponent (const TabAreaBehindFrontButtonComponent&); + const TabAreaBehindFrontButtonComponent& operator= (const TabAreaBehindFrontButtonComponent&); +}; + + +//============================================================================== +TabbedButtonBar::TabbedButtonBar (const Orientation orientation_) + : orientation (orientation_), + currentTabIndex (-1), + extraTabsButton (0) +{ + setInterceptsMouseClicks (false, true); + addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this)); + setFocusContainer (true); +} + +TabbedButtonBar::~TabbedButtonBar() +{ + deleteAllChildren(); +} + +//============================================================================== +void TabbedButtonBar::setOrientation (const Orientation newOrientation) +{ + orientation = newOrientation; + + for (int i = getNumChildComponents(); --i >= 0;) + getChildComponent (i)->resized(); + + resized(); +} + +TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int index) +{ + return new TabBarButton (name, this, index); +} + +//============================================================================== +void TabbedButtonBar::clearTabs() +{ + tabs.clear(); + tabColours.clear(); + currentTabIndex = -1; + + deleteAndZero (extraTabsButton); + removeChildComponent (behindFrontTab); + deleteAllChildren(); + addChildComponent (behindFrontTab); + + setCurrentTabIndex (-1); +} + +void TabbedButtonBar::addTab (const String& tabName, + const Colour& tabBackgroundColour, + int insertIndex) +{ + jassert (tabName.isNotEmpty()); // you have to give them all a name.. + + if (tabName.isNotEmpty()) + { + if (((unsigned int) insertIndex) > (unsigned int) tabs.size()) + insertIndex = tabs.size(); + + for (int i = tabs.size(); --i >= insertIndex;) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + tb->tabIndex++; + } + + tabs.insert (insertIndex, tabName); + tabColours.insert (insertIndex, tabBackgroundColour); + + TabBarButton* const tb = createTabButton (tabName, insertIndex); + jassert (tb != 0); // your createTabButton() mustn't return zero! + + addAndMakeVisible (tb, insertIndex); + + resized(); + + if (currentTabIndex < 0) + setCurrentTabIndex (0); + } +} + +void TabbedButtonBar::setTabName (const int tabIndex, + const String& newName) +{ + if (((unsigned int) tabIndex) < (unsigned int) tabs.size() + && tabs[tabIndex] != newName) + { + tabs.set (tabIndex, newName); + + TabBarButton* const tb = getTabButton (tabIndex); + + if (tb != 0) + tb->setButtonText (newName); + + resized(); + } +} + +void TabbedButtonBar::removeTab (const int tabIndex) +{ + if (((unsigned int) tabIndex) < (unsigned int) tabs.size()) + { + const int oldTabIndex = currentTabIndex; + if (currentTabIndex == tabIndex) + currentTabIndex = -1; + + tabs.remove (tabIndex); + tabColours.remove (tabIndex); + + TabBarButton* const tb = getTabButton (tabIndex); + + if (tb != 0) + delete tb; + + for (int i = tabIndex + 1; i <= tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + tb->tabIndex--; + } + + resized(); + + setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex)); + } +} + +void TabbedButtonBar::moveTab (const int currentIndex, + const int newIndex) +{ + tabs.move (currentIndex, newIndex); + tabColours.move (currentIndex, newIndex); + resized(); +} + +int TabbedButtonBar::getNumTabs() const +{ + return tabs.size(); +} + +const StringArray TabbedButtonBar::getTabNames() const +{ + return tabs; +} + +void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_) +{ + if (currentTabIndex != newIndex) + { + if (((unsigned int) newIndex) >= (unsigned int) tabs.size()) + newIndex = -1; + + currentTabIndex = newIndex; + + for (int i = 0; i < getNumChildComponents(); ++i) + { + TabBarButton* const tb = dynamic_cast (getChildComponent (i)); + + if (tb != 0) + tb->setToggleState (tb->tabIndex == newIndex, false); + } + + resized(); + + if (sendChangeMessage_) + sendChangeMessage (this); + + currentTabChanged (newIndex, newIndex >= 0 ? tabs [newIndex] : String::empty); + } +} + +TabBarButton* TabbedButtonBar::getTabButton (const int index) const +{ + for (int i = getNumChildComponents(); --i >= 0;) + { + TabBarButton* const tb = dynamic_cast (getChildComponent (i)); + + if (tb != 0 && tb->tabIndex == index) + return tb; + } + + return 0; +} + +void TabbedButtonBar::lookAndFeelChanged() +{ + deleteAndZero (extraTabsButton); + resized(); +} + +void TabbedButtonBar::resized() +{ + const double minimumScale = 0.7; + int depth = getWidth(); + int length = getHeight(); + + if (orientation == TabsAtTop || orientation == TabsAtBottom) + swapVariables (depth, length); + + const int overlap = getLookAndFeel().getTabButtonOverlap (depth) + + getLookAndFeel().getTabButtonSpaceAroundImage() * 2; + + int i, totalLength = overlap; + int numVisibleButtons = tabs.size(); + + for (i = 0; i < getNumChildComponents(); ++i) + { + TabBarButton* const tb = dynamic_cast (getChildComponent (i)); + + if (tb != 0) + { + totalLength += tb->getBestTabLength (depth) - overlap; + tb->overlapPixels = overlap / 2; + } + } + + double scale = 1.0; + + if (totalLength > length) + scale = jmax (minimumScale, length / (double) totalLength); + + const bool isTooBig = totalLength * scale > length; + int tabsButtonPos = 0; + + if (isTooBig) + { + if (extraTabsButton == 0) + { + addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton()); + extraTabsButton->addButtonListener (this); + extraTabsButton->setAlwaysOnTop (true); + extraTabsButton->setTriggeredOnMouseDown (true); + } + + const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f)); + extraTabsButton->setSize (buttonSize, buttonSize); + + if (orientation == TabsAtTop || orientation == TabsAtBottom) + { + tabsButtonPos = getWidth() - buttonSize / 2 - 1; + extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2); + } + else + { + tabsButtonPos = getHeight() - buttonSize / 2 - 1; + extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos); + } + + totalLength = 0; + + for (i = 0; i < tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + { + const int newLength = totalLength + tb->getBestTabLength (depth); + + if (i > 0 && newLength * minimumScale > tabsButtonPos) + { + totalLength += overlap; + break; + } + + numVisibleButtons = i + 1; + totalLength = newLength - overlap; + + } + } + + scale = jmax (minimumScale, tabsButtonPos / (double) totalLength); + } + else + { + deleteAndZero (extraTabsButton); + } + + int pos = 0; + + TabBarButton* frontTab = 0; + + for (i = 0; i < tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0) + { + const int bestLength = roundDoubleToInt (scale * tb->getBestTabLength (depth)); + + if (i < numVisibleButtons) + { + if (orientation == TabsAtTop || orientation == TabsAtBottom) + tb->setBounds (pos, 0, bestLength, getHeight()); + else + tb->setBounds (0, pos, getWidth(), bestLength); + + tb->toBack(); + + if (tb->tabIndex == currentTabIndex) + frontTab = tb; + + tb->setVisible (true); + } + else + { + tb->setVisible (false); + } + + pos += bestLength - overlap; + } + } + + behindFrontTab->setBounds (0, 0, getWidth(), getHeight()); + + if (frontTab != 0) + { + frontTab->toFront (false); + behindFrontTab->toBehind (frontTab); + } +} + +//============================================================================== +const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex) +{ + return tabColours [tabIndex]; +} + +void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour) +{ + if (((unsigned int) tabIndex) < (unsigned int) tabColours.size() + && tabColours [tabIndex] != newColour) + { + tabColours.set (tabIndex, newColour); + repaint(); + } +} + +void TabbedButtonBar::buttonClicked (Button* button) +{ + if (extraTabsButton == button) + { + PopupMenu m; + + for (int i = 0; i < tabs.size(); ++i) + { + TabBarButton* const tb = getTabButton (i); + + if (tb != 0 && ! tb->isVisible()) + m.addItem (tb->tabIndex + 1, tabs[i], true, i == currentTabIndex); + } + + const int res = m.showAt (extraTabsButton); + + if (res != 0) + setCurrentTabIndex (res - 1); + } +} + +//============================================================================== +void TabbedButtonBar::currentTabChanged (const int, const String&) +{ +} + +void TabbedButtonBar::popupMenuClickOnTab (const int, const String&) +{ +} + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.h b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.h index b02de45263..459f67fb98 100644 --- a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.h +++ b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.h @@ -197,7 +197,7 @@ public: To deselect all the tabs, use an index of -1. */ - void setCurrentTabIndex (int newTabIndex); + void setCurrentTabIndex (int newTabIndex, const bool sendChangeMessage = true); /** Returns the name of the currently selected tab. diff --git a/src/juce_appframework/gui/components/layout/juce_TabbedComponent.cpp b/src/juce_appframework/gui/components/layout/juce_TabbedComponent.cpp index add69b4de9..de7d9ea51c 100644 --- a/src/juce_appframework/gui/components/layout/juce_TabbedComponent.cpp +++ b/src/juce_appframework/gui/components/layout/juce_TabbedComponent.cpp @@ -216,9 +216,9 @@ void TabbedComponent::setTabBackgroundColour (const int tabIndex, const Colour& repaint(); } -void TabbedComponent::setCurrentTabIndex (const int newTabIndex) +void TabbedComponent::setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage) { - tabs->setCurrentTabIndex (newTabIndex); + tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage); } int TabbedComponent::getCurrentTabIndex() const diff --git a/src/juce_appframework/gui/components/layout/juce_TabbedComponent.h b/src/juce_appframework/gui/components/layout/juce_TabbedComponent.h index de3ba11b99..d5ddb02843 100644 --- a/src/juce_appframework/gui/components/layout/juce_TabbedComponent.h +++ b/src/juce_appframework/gui/components/layout/juce_TabbedComponent.h @@ -157,7 +157,7 @@ public: @see TabbedButtonBar::setCurrentTabIndex */ - void setCurrentTabIndex (const int newTabIndex); + void setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage = true); /** Returns the index of the currently selected tab. diff --git a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp index 444f537774..584e7ba545 100644 --- a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp +++ b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp @@ -1626,8 +1626,11 @@ void LookAndFeel::drawImageButton (Graphics& g, Image* image, int imageX, int imageY, int imageW, int imageH, const Colour& overlayColour, float imageOpacity, - ImageButton& /*button*/) + ImageButton& button) { + if (! button.isEnabled()) + imageOpacity *= 0.3f; + if (! overlayColour.isOpaque()) { g.setOpacity (imageOpacity); diff --git a/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp b/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp index 672fb1cc59..c78e989349 100644 --- a/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp +++ b/src/juce_appframework/gui/components/windows/juce_ComponentPeer.cpp @@ -1,786 +1,788 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "../../../application/juce_Application.h" -#include "../juce_Component.h" -#include "../juce_ComponentDeletionWatcher.h" -#include "../juce_Desktop.h" -#include "../../../events/juce_MessageManager.h" -#include "../../../../juce_core/basics/juce_Time.h" -#include "../../../../juce_core/basics/juce_Random.h" -#include "../layout/juce_ComponentBoundsConstrainer.h" -#include "../mouse/juce_FileDragAndDropTarget.h" - -//#define JUCE_ENABLE_REPAINT_DEBUGGING 1 - - -//============================================================================== -// these are over in juce_component.cpp -extern int64 juce_recentMouseDownTimes[4]; -extern int juce_recentMouseDownX [4]; -extern int juce_recentMouseDownY [4]; -extern Component* juce_recentMouseDownComponent [4]; -extern int juce_LastMousePosX; -extern int juce_LastMousePosY; -extern int juce_MouseClickCounter; -extern bool juce_MouseHasMovedSignificantlySincePressed; - -static const int fakeMouseMoveMessage = 0x7fff00ff; - -static VoidArray heavyweightPeers (4); - - -//============================================================================== -ComponentPeer::ComponentPeer (Component* const component_, - const int styleFlags_) throw() - : component (component_), - styleFlags (styleFlags_), - lastPaintTime (0), - constrainer (0), - lastFocusedComponent (0), - dragAndDropTargetComponent (0), - lastDragAndDropCompUnderMouse (0), - fakeMouseMessageSent (false), - isWindowMinimised (false) -{ - heavyweightPeers.add (this); -} - -ComponentPeer::~ComponentPeer() -{ - heavyweightPeers.removeValue (this); - delete dragAndDropTargetComponent; -} - -//============================================================================== -int ComponentPeer::getNumPeers() throw() -{ - return heavyweightPeers.size(); -} - -ComponentPeer* ComponentPeer::getPeer (const int index) throw() -{ - return (ComponentPeer*) heavyweightPeers [index]; -} - -ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) throw() -{ - for (int i = heavyweightPeers.size(); --i >= 0;) - { - ComponentPeer* const peer = (ComponentPeer*) heavyweightPeers.getUnchecked(i); - - if (peer->getComponent() == component) - return peer; - } - - return 0; -} - -bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) throw() -{ - return heavyweightPeers.contains (const_cast (peer)); -} - -void ComponentPeer::updateCurrentModifiers() throw() -{ - ModifierKeys::updateCurrentModifiers(); -} - -//============================================================================== -void ComponentPeer::handleMouseEnter (int x, int y, const int64 time) -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - Component* c = component->getComponentAt (x, y); - const ComponentDeletionWatcher deletionChecker (component); - - if (c != Component::componentUnderMouse && Component::componentUnderMouse != 0) - { - jassert (Component::componentUnderMouse->isValidComponent()); - - const int oldX = x; - const int oldY = y; - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseExit (x, y, time); - Component::componentUnderMouse = 0; - - if (deletionChecker.hasBeenDeleted()) - return; - - c = component->getComponentAt (oldX, oldY); - } - - Component::componentUnderMouse = c; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseEnter (x, y, time); - } -} - -void ComponentPeer::handleMouseMove (int x, int y, const int64 time) -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - fakeMouseMessageSent = false; - - const ComponentDeletionWatcher deletionChecker (component); - Component* c = component->getComponentAt (x, y); - - if (c != Component::componentUnderMouse) - { - const int oldX = x; - const int oldY = y; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseExit (x, y, time); - x = oldX; - y = oldY; - - Component::componentUnderMouse = 0; - - if (deletionChecker.hasBeenDeleted()) - return; // if this window has just been deleted.. - - c = component->getComponentAt (x, y); - } - - Component::componentUnderMouse = c; - - if (c != 0) - { - component->relativePositionToOtherComponent (c, x, y); - c->internalMouseEnter (x, y, time); - x = oldX; - y = oldY; - - if (deletionChecker.hasBeenDeleted()) - return; // if this window has just been deleted.. - } - } - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseMove (x, y, time); - } -} - -void ComponentPeer::handleMouseDown (int x, int y, const int64 time) -{ - ++juce_MouseClickCounter; - - updateCurrentModifiers(); - - int numMouseButtonsDown = 0; - - if (ModifierKeys::getCurrentModifiers().isLeftButtonDown()) - ++numMouseButtonsDown; - - if (ModifierKeys::getCurrentModifiers().isRightButtonDown()) - ++numMouseButtonsDown; - - if (ModifierKeys::getCurrentModifiers().isMiddleButtonDown()) - ++numMouseButtonsDown; - - if (numMouseButtonsDown == 1) - { - Component::componentUnderMouse = component->getComponentAt (x, y); - - if (Component::componentUnderMouse != 0) - { - // can't set these in the mouseDownInt() method, because it's re-entrant, so do it here.. - - for (int i = numElementsInArray (juce_recentMouseDownTimes); --i > 0;) - { - juce_recentMouseDownTimes [i] = juce_recentMouseDownTimes [i - 1]; - juce_recentMouseDownX [i] = juce_recentMouseDownX [i - 1]; - juce_recentMouseDownY [i] = juce_recentMouseDownY [i - 1]; - juce_recentMouseDownComponent [i] = juce_recentMouseDownComponent [i - 1]; - } - - juce_recentMouseDownTimes[0] = time; - juce_recentMouseDownX[0] = x; - juce_recentMouseDownY[0] = y; - juce_recentMouseDownComponent[0] = Component::componentUnderMouse; - relativePositionToGlobal (juce_recentMouseDownX[0], juce_recentMouseDownY[0]); - juce_MouseHasMovedSignificantlySincePressed = false; - - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseDown (x, y); - } - } -} - -void ComponentPeer::handleMouseDrag (int x, int y, const int64 time) -{ - updateCurrentModifiers(); - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - - Component::componentUnderMouse->internalMouseDrag (x, y, time); - } -} - -void ComponentPeer::handleMouseUp (const int oldModifiers, int x, int y, const int64 time) -{ - updateCurrentModifiers(); - - int numMouseButtonsDown = 0; - - if ((oldModifiers & ModifierKeys::leftButtonModifier) != 0) - ++numMouseButtonsDown; - - if ((oldModifiers & ModifierKeys::rightButtonModifier) != 0) - ++numMouseButtonsDown; - - if ((oldModifiers & ModifierKeys::middleButtonModifier) != 0) - ++numMouseButtonsDown; - - if (numMouseButtonsDown == 1) - { - const ComponentDeletionWatcher deletionChecker (component); - Component* c = component->getComponentAt (x, y); - - if (c != Component::componentUnderMouse) - { - const int oldX = x; - const int oldY = y; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); - x = oldX; - y = oldY; - - if (Component::componentUnderMouse != 0) - Component::componentUnderMouse->internalMouseExit (x, y, time); - - if (deletionChecker.hasBeenDeleted()) - return; - - c = component->getComponentAt (oldX, oldY); - } - - Component::componentUnderMouse = c; - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseEnter (x, y, time); - } - } - else - { - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); - } - } - } -} - -void ComponentPeer::handleMouseExit (int x, int y, const int64 time) -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - if (Component::componentUnderMouse != 0) - { - component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); - - Component::componentUnderMouse->internalMouseExit (x, y, time); - Component::componentUnderMouse = 0; - } -} - -void ComponentPeer::handleMouseWheel (const int amountX, const int amountY, const int64 time) -{ - updateCurrentModifiers(); - - if (Component::componentUnderMouse != 0) - Component::componentUnderMouse->internalMouseWheel (amountX, amountY, time); -} - -void ComponentPeer::sendFakeMouseMove() throw() -{ - if ((! fakeMouseMessageSent) - && component->flags.hasHeavyweightPeerFlag - && ! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) - { - if (! isMinimised()) - { - int realX, realY, realW, realH; - getBounds (realX, realY, realW, realH); - - component->bounds_.setBounds (realX, realY, realW, realH); - } - - int x, y; - component->getMouseXYRelative (x, y); - - if (((unsigned int) x) < (unsigned int) component->getWidth() - && ((unsigned int) y) < (unsigned int) component->getHeight() - && contains (x, y, false)) - { - postMessage (new Message (fakeMouseMoveMessage, x, y, 0)); - } - - fakeMouseMessageSent = true; - } -} - -void ComponentPeer::handleMessage (const Message& message) -{ - if (message.intParameter1 == fakeMouseMoveMessage) - { - if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) - handleMouseMove (message.intParameter2, - message.intParameter3, - Time::currentTimeMillis()); - } -} - -//============================================================================== -void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo) -{ - Graphics g (&contextToPaintTo); - -#if JUCE_ENABLE_REPAINT_DEBUGGING - g.saveState(); -#endif - - JUCE_TRY - { - component->paintEntireComponent (g); - } - JUCE_CATCH_EXCEPTION - -#if JUCE_ENABLE_REPAINT_DEBUGGING - // enabling this code will fill all areas that get repainted with a colour overlay, to show - // clearly when things are being repainted. - { - g.restoreState(); - - g.fillAll (Colour ((uint8) Random::getSystemRandom().nextInt (255), - (uint8) Random::getSystemRandom().nextInt (255), - (uint8) Random::getSystemRandom().nextInt (255), - (uint8) 0x50)); - } -#endif -} - -bool ComponentPeer::handleKeyPress (const int keyCode, - const juce_wchar textCharacter) -{ - updateCurrentModifiers(); - - Component* target = Component::currentlyFocusedComponent->isValidComponent() - ? Component::currentlyFocusedComponent - : component; - - if (target->isCurrentlyBlockedByAnotherModalComponent()) - { - Component* const currentModalComp = Component::getCurrentlyModalComponent(); - - if (currentModalComp != 0) - target = currentModalComp; - } - - const KeyPress keyInfo (keyCode, - ModifierKeys::getCurrentModifiers().getRawFlags() - & ModifierKeys::allKeyboardModifiers, - textCharacter); - - bool keyWasUsed = false; - - while (target != 0) - { - const ComponentDeletionWatcher deletionChecker (target); - - if (target->keyListeners_ != 0) - { - for (int i = target->keyListeners_->size(); --i >= 0;) - { - keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyPressed (keyInfo, target); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - return keyWasUsed; - - i = jmin (i, target->keyListeners_->size()); - } - } - - keyWasUsed = target->keyPressed (keyInfo); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - break; - - if (keyInfo.isKeyCode (KeyPress::tabKey) && Component::getCurrentlyFocusedComponent() != 0) - { - Component::getCurrentlyFocusedComponent() - ->moveKeyboardFocusToSibling (! keyInfo.getModifiers().isShiftDown()); - - keyWasUsed = true; - break; - } - - target = target->parentComponent_; - } - - return keyWasUsed; -} - -bool ComponentPeer::handleKeyUpOrDown() -{ - updateCurrentModifiers(); - - Component* target = Component::currentlyFocusedComponent->isValidComponent() - ? Component::currentlyFocusedComponent - : component; - - if (target->isCurrentlyBlockedByAnotherModalComponent()) - { - Component* const currentModalComp = Component::getCurrentlyModalComponent(); - - if (currentModalComp != 0) - target = currentModalComp; - } - - bool keyWasUsed = false; - - while (target != 0) - { - const ComponentDeletionWatcher deletionChecker (target); - - keyWasUsed = target->keyStateChanged(); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - break; - - if (target->keyListeners_ != 0) - { - for (int i = target->keyListeners_->size(); --i >= 0;) - { - keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyStateChanged (target); - - if (keyWasUsed || deletionChecker.hasBeenDeleted()) - return keyWasUsed; - - i = jmin (i, target->keyListeners_->size()); - } - } - - target = target->parentComponent_; - } - - return keyWasUsed; -} - -void ComponentPeer::handleModifierKeysChange() -{ - updateCurrentModifiers(); - - Component* target = Component::getComponentUnderMouse(); - - if (target == 0) - target = Component::getCurrentlyFocusedComponent(); - - if (target == 0) - target = component; - - if (target->isValidComponent()) - target->internalModifierKeysChanged(); -} - -//============================================================================== -void ComponentPeer::handleBroughtToFront() -{ - updateCurrentModifiers(); - - if (component != 0) - component->internalBroughtToFront(); -} - -void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) throw() -{ - constrainer = newConstrainer; -} - -void ComponentPeer::handleMovedOrResized() -{ - jassert (component->isValidComponent()); - updateCurrentModifiers(); - - const bool nowMinimised = isMinimised(); - - if (component->flags.hasHeavyweightPeerFlag && ! nowMinimised) - { - const ComponentDeletionWatcher deletionChecker (component); - - int realX, realY, realW, realH; - getBounds (realX, realY, realW, realH); - - const bool wasMoved = (component->getX() != realX || component->getY() != realY); - const bool wasResized = (component->getWidth() != realW || component->getHeight() != realH); - - if (wasMoved || wasResized) - { - component->bounds_.setBounds (realX, realY, realW, realH); - - if (wasResized) - component->repaint(); - - component->sendMovedResizedMessages (wasMoved, wasResized); - - if (deletionChecker.hasBeenDeleted()) - return; - } - } - - if (isWindowMinimised != nowMinimised) - { - isWindowMinimised = nowMinimised; - component->minimisationStateChanged (nowMinimised); - component->sendVisibilityChangeMessage(); - } - - if (! isFullScreen()) - lastNonFullscreenBounds = component->getBounds(); -} - -void ComponentPeer::handleFocusGain() -{ - updateCurrentModifiers(); - - if (component->isParentOf (lastFocusedComponent)) - { - Component::currentlyFocusedComponent = lastFocusedComponent; - Desktop::getInstance().triggerFocusCallback(); - lastFocusedComponent->internalFocusGain (Component::focusChangedDirectly); - } - else - { - if (! component->isCurrentlyBlockedByAnotherModalComponent()) - { - component->grabKeyboardFocus(); - } - else - { - Component* const currentModalComp = Component::getCurrentlyModalComponent(); - - if (currentModalComp != 0) - currentModalComp->toFront (! currentModalComp->hasKeyboardFocus (true)); - } - } -} - -void ComponentPeer::handleFocusLoss() -{ - updateCurrentModifiers(); - - if (component->hasKeyboardFocus (true)) - { - lastFocusedComponent = Component::currentlyFocusedComponent; - - if (lastFocusedComponent != 0) - { - Component::currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); - lastFocusedComponent->internalFocusLoss (Component::focusChangedByMouseClick); - } - } -} - -Component* ComponentPeer::getLastFocusedSubcomponent() const throw() -{ - return (component->isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing()) - ? lastFocusedComponent - : component; -} - -void ComponentPeer::handleScreenSizeChange() -{ - updateCurrentModifiers(); - - component->parentSizeChanged(); - handleMovedOrResized(); -} - -void ComponentPeer::setNonFullScreenBounds (const Rectangle& newBounds) throw() -{ - lastNonFullscreenBounds = newBounds; -} - -const Rectangle& ComponentPeer::getNonFullScreenBounds() const throw() -{ - return lastNonFullscreenBounds; -} - -//============================================================================== -static FileDragAndDropTarget* findDragAndDropTarget (Component* c, - const StringArray& files, - FileDragAndDropTarget* const lastOne) -{ - while (c != 0) - { - FileDragAndDropTarget* const t = dynamic_cast (c); - - if (t != 0 && (t == lastOne || t->isInterestedInFileDrag (files))) - return t; - - c = c->getParentComponent(); - } - - return 0; -} - -void ComponentPeer::handleFileDragMove (const StringArray& files, int x, int y) -{ - updateCurrentModifiers(); - - FileDragAndDropTarget* lastTarget = 0; - - if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) - lastTarget = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); - - FileDragAndDropTarget* newTarget = 0; - - Component* const compUnderMouse = component->getComponentAt (x, y); - - if (compUnderMouse != lastDragAndDropCompUnderMouse) - { - lastDragAndDropCompUnderMouse = compUnderMouse; - newTarget = findDragAndDropTarget (compUnderMouse, files, lastTarget); - - if (newTarget != lastTarget) - { - if (lastTarget != 0) - lastTarget->fileDragExit (files); - - deleteAndZero (dragAndDropTargetComponent); - - if (newTarget != 0) - { - Component* const targetComp = dynamic_cast (newTarget); - int mx = x, my = y; - component->relativePositionToOtherComponent (targetComp, mx, my); - - dragAndDropTargetComponent = new ComponentDeletionWatcher (dynamic_cast (newTarget)); - newTarget->fileDragEnter (files, mx, my); - } - } - } - else - { - newTarget = lastTarget; - } - - if (newTarget != 0) - { - Component* const targetComp = dynamic_cast (newTarget); - component->relativePositionToOtherComponent (targetComp, x, y); - - newTarget->fileDragMove (files, x, y); - } -} - -void ComponentPeer::handleFileDragExit (const StringArray& files) -{ - handleFileDragMove (files, -1, -1); - - jassert (dragAndDropTargetComponent == 0); - lastDragAndDropCompUnderMouse = 0; -} - -void ComponentPeer::handleFileDragDrop (const StringArray& files, int x, int y) -{ - handleFileDragMove (files, x, y); - - if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) - { - FileDragAndDropTarget* const target = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); - - deleteAndZero (dragAndDropTargetComponent); - lastDragAndDropCompUnderMouse = 0; - - if (target != 0) - { - Component* const targetComp = dynamic_cast (target); - - if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) - { - targetComp->internalModalInputAttempt(); - - if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) - return; - } - - component->relativePositionToOtherComponent (targetComp, x, y); - target->filesDropped (files, x, y); - } - } -} - -//============================================================================== -void ComponentPeer::handleUserClosingWindow() -{ - updateCurrentModifiers(); - - component->userTriedToCloseWindow(); -} - -//============================================================================== -void ComponentPeer::clearMaskedRegion() throw() -{ - maskedRegion.clear(); -} - -void ComponentPeer::addMaskedRegion (int x, int y, int w, int h) throw() -{ - maskedRegion.add (x, y, w, h); -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "../../../application/juce_Application.h" +#include "../juce_Component.h" +#include "../juce_ComponentDeletionWatcher.h" +#include "../juce_Desktop.h" +#include "../../../events/juce_MessageManager.h" +#include "../../../../juce_core/basics/juce_Time.h" +#include "../../../../juce_core/basics/juce_Random.h" +#include "../layout/juce_ComponentBoundsConstrainer.h" +#include "../mouse/juce_FileDragAndDropTarget.h" + +//#define JUCE_ENABLE_REPAINT_DEBUGGING 1 + + +//============================================================================== +// these are over in juce_component.cpp +extern int64 juce_recentMouseDownTimes[4]; +extern int juce_recentMouseDownX [4]; +extern int juce_recentMouseDownY [4]; +extern Component* juce_recentMouseDownComponent [4]; +extern int juce_LastMousePosX; +extern int juce_LastMousePosY; +extern int juce_MouseClickCounter; +extern bool juce_MouseHasMovedSignificantlySincePressed; + +static const int fakeMouseMoveMessage = 0x7fff00ff; + +static VoidArray heavyweightPeers (4); + + +//============================================================================== +ComponentPeer::ComponentPeer (Component* const component_, + const int styleFlags_) throw() + : component (component_), + styleFlags (styleFlags_), + lastPaintTime (0), + constrainer (0), + lastFocusedComponent (0), + dragAndDropTargetComponent (0), + lastDragAndDropCompUnderMouse (0), + fakeMouseMessageSent (false), + isWindowMinimised (false) +{ + heavyweightPeers.add (this); +} + +ComponentPeer::~ComponentPeer() +{ + heavyweightPeers.removeValue (this); + delete dragAndDropTargetComponent; + + Desktop::getInstance().triggerFocusCallback(); +} + +//============================================================================== +int ComponentPeer::getNumPeers() throw() +{ + return heavyweightPeers.size(); +} + +ComponentPeer* ComponentPeer::getPeer (const int index) throw() +{ + return (ComponentPeer*) heavyweightPeers [index]; +} + +ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) throw() +{ + for (int i = heavyweightPeers.size(); --i >= 0;) + { + ComponentPeer* const peer = (ComponentPeer*) heavyweightPeers.getUnchecked(i); + + if (peer->getComponent() == component) + return peer; + } + + return 0; +} + +bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) throw() +{ + return heavyweightPeers.contains (const_cast (peer)); +} + +void ComponentPeer::updateCurrentModifiers() throw() +{ + ModifierKeys::updateCurrentModifiers(); +} + +//============================================================================== +void ComponentPeer::handleMouseEnter (int x, int y, const int64 time) +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + Component* c = component->getComponentAt (x, y); + const ComponentDeletionWatcher deletionChecker (component); + + if (c != Component::componentUnderMouse && Component::componentUnderMouse != 0) + { + jassert (Component::componentUnderMouse->isValidComponent()); + + const int oldX = x; + const int oldY = y; + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseExit (x, y, time); + Component::componentUnderMouse = 0; + + if (deletionChecker.hasBeenDeleted()) + return; + + c = component->getComponentAt (oldX, oldY); + } + + Component::componentUnderMouse = c; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseEnter (x, y, time); + } +} + +void ComponentPeer::handleMouseMove (int x, int y, const int64 time) +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + fakeMouseMessageSent = false; + + const ComponentDeletionWatcher deletionChecker (component); + Component* c = component->getComponentAt (x, y); + + if (c != Component::componentUnderMouse) + { + const int oldX = x; + const int oldY = y; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseExit (x, y, time); + x = oldX; + y = oldY; + + Component::componentUnderMouse = 0; + + if (deletionChecker.hasBeenDeleted()) + return; // if this window has just been deleted.. + + c = component->getComponentAt (x, y); + } + + Component::componentUnderMouse = c; + + if (c != 0) + { + component->relativePositionToOtherComponent (c, x, y); + c->internalMouseEnter (x, y, time); + x = oldX; + y = oldY; + + if (deletionChecker.hasBeenDeleted()) + return; // if this window has just been deleted.. + } + } + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseMove (x, y, time); + } +} + +void ComponentPeer::handleMouseDown (int x, int y, const int64 time) +{ + ++juce_MouseClickCounter; + + updateCurrentModifiers(); + + int numMouseButtonsDown = 0; + + if (ModifierKeys::getCurrentModifiers().isLeftButtonDown()) + ++numMouseButtonsDown; + + if (ModifierKeys::getCurrentModifiers().isRightButtonDown()) + ++numMouseButtonsDown; + + if (ModifierKeys::getCurrentModifiers().isMiddleButtonDown()) + ++numMouseButtonsDown; + + if (numMouseButtonsDown == 1) + { + Component::componentUnderMouse = component->getComponentAt (x, y); + + if (Component::componentUnderMouse != 0) + { + // can't set these in the mouseDownInt() method, because it's re-entrant, so do it here.. + + for (int i = numElementsInArray (juce_recentMouseDownTimes); --i > 0;) + { + juce_recentMouseDownTimes [i] = juce_recentMouseDownTimes [i - 1]; + juce_recentMouseDownX [i] = juce_recentMouseDownX [i - 1]; + juce_recentMouseDownY [i] = juce_recentMouseDownY [i - 1]; + juce_recentMouseDownComponent [i] = juce_recentMouseDownComponent [i - 1]; + } + + juce_recentMouseDownTimes[0] = time; + juce_recentMouseDownX[0] = x; + juce_recentMouseDownY[0] = y; + juce_recentMouseDownComponent[0] = Component::componentUnderMouse; + relativePositionToGlobal (juce_recentMouseDownX[0], juce_recentMouseDownY[0]); + juce_MouseHasMovedSignificantlySincePressed = false; + + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseDown (x, y); + } + } +} + +void ComponentPeer::handleMouseDrag (int x, int y, const int64 time) +{ + updateCurrentModifiers(); + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + + Component::componentUnderMouse->internalMouseDrag (x, y, time); + } +} + +void ComponentPeer::handleMouseUp (const int oldModifiers, int x, int y, const int64 time) +{ + updateCurrentModifiers(); + + int numMouseButtonsDown = 0; + + if ((oldModifiers & ModifierKeys::leftButtonModifier) != 0) + ++numMouseButtonsDown; + + if ((oldModifiers & ModifierKeys::rightButtonModifier) != 0) + ++numMouseButtonsDown; + + if ((oldModifiers & ModifierKeys::middleButtonModifier) != 0) + ++numMouseButtonsDown; + + if (numMouseButtonsDown == 1) + { + const ComponentDeletionWatcher deletionChecker (component); + Component* c = component->getComponentAt (x, y); + + if (c != Component::componentUnderMouse) + { + const int oldX = x; + const int oldY = y; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); + x = oldX; + y = oldY; + + if (Component::componentUnderMouse != 0) + Component::componentUnderMouse->internalMouseExit (x, y, time); + + if (deletionChecker.hasBeenDeleted()) + return; + + c = component->getComponentAt (oldX, oldY); + } + + Component::componentUnderMouse = c; + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseEnter (x, y, time); + } + } + else + { + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + Component::componentUnderMouse->internalMouseUp (oldModifiers, x, y, time); + } + } + } +} + +void ComponentPeer::handleMouseExit (int x, int y, const int64 time) +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + if (Component::componentUnderMouse != 0) + { + component->relativePositionToOtherComponent (Component::componentUnderMouse, x, y); + + Component::componentUnderMouse->internalMouseExit (x, y, time); + Component::componentUnderMouse = 0; + } +} + +void ComponentPeer::handleMouseWheel (const int amountX, const int amountY, const int64 time) +{ + updateCurrentModifiers(); + + if (Component::componentUnderMouse != 0) + Component::componentUnderMouse->internalMouseWheel (amountX, amountY, time); +} + +void ComponentPeer::sendFakeMouseMove() throw() +{ + if ((! fakeMouseMessageSent) + && component->flags.hasHeavyweightPeerFlag + && ! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) + { + if (! isMinimised()) + { + int realX, realY, realW, realH; + getBounds (realX, realY, realW, realH); + + component->bounds_.setBounds (realX, realY, realW, realH); + } + + int x, y; + component->getMouseXYRelative (x, y); + + if (((unsigned int) x) < (unsigned int) component->getWidth() + && ((unsigned int) y) < (unsigned int) component->getHeight() + && contains (x, y, false)) + { + postMessage (new Message (fakeMouseMoveMessage, x, y, 0)); + } + + fakeMouseMessageSent = true; + } +} + +void ComponentPeer::handleMessage (const Message& message) +{ + if (message.intParameter1 == fakeMouseMoveMessage) + { + if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) + handleMouseMove (message.intParameter2, + message.intParameter3, + Time::currentTimeMillis()); + } +} + +//============================================================================== +void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo) +{ + Graphics g (&contextToPaintTo); + +#if JUCE_ENABLE_REPAINT_DEBUGGING + g.saveState(); +#endif + + JUCE_TRY + { + component->paintEntireComponent (g); + } + JUCE_CATCH_EXCEPTION + +#if JUCE_ENABLE_REPAINT_DEBUGGING + // enabling this code will fill all areas that get repainted with a colour overlay, to show + // clearly when things are being repainted. + { + g.restoreState(); + + g.fillAll (Colour ((uint8) Random::getSystemRandom().nextInt (255), + (uint8) Random::getSystemRandom().nextInt (255), + (uint8) Random::getSystemRandom().nextInt (255), + (uint8) 0x50)); + } +#endif +} + +bool ComponentPeer::handleKeyPress (const int keyCode, + const juce_wchar textCharacter) +{ + updateCurrentModifiers(); + + Component* target = Component::currentlyFocusedComponent->isValidComponent() + ? Component::currentlyFocusedComponent + : component; + + if (target->isCurrentlyBlockedByAnotherModalComponent()) + { + Component* const currentModalComp = Component::getCurrentlyModalComponent(); + + if (currentModalComp != 0) + target = currentModalComp; + } + + const KeyPress keyInfo (keyCode, + ModifierKeys::getCurrentModifiers().getRawFlags() + & ModifierKeys::allKeyboardModifiers, + textCharacter); + + bool keyWasUsed = false; + + while (target != 0) + { + const ComponentDeletionWatcher deletionChecker (target); + + if (target->keyListeners_ != 0) + { + for (int i = target->keyListeners_->size(); --i >= 0;) + { + keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyPressed (keyInfo, target); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + return keyWasUsed; + + i = jmin (i, target->keyListeners_->size()); + } + } + + keyWasUsed = target->keyPressed (keyInfo); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + break; + + if (keyInfo.isKeyCode (KeyPress::tabKey) && Component::getCurrentlyFocusedComponent() != 0) + { + Component::getCurrentlyFocusedComponent() + ->moveKeyboardFocusToSibling (! keyInfo.getModifiers().isShiftDown()); + + keyWasUsed = true; + break; + } + + target = target->parentComponent_; + } + + return keyWasUsed; +} + +bool ComponentPeer::handleKeyUpOrDown() +{ + updateCurrentModifiers(); + + Component* target = Component::currentlyFocusedComponent->isValidComponent() + ? Component::currentlyFocusedComponent + : component; + + if (target->isCurrentlyBlockedByAnotherModalComponent()) + { + Component* const currentModalComp = Component::getCurrentlyModalComponent(); + + if (currentModalComp != 0) + target = currentModalComp; + } + + bool keyWasUsed = false; + + while (target != 0) + { + const ComponentDeletionWatcher deletionChecker (target); + + keyWasUsed = target->keyStateChanged(); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + break; + + if (target->keyListeners_ != 0) + { + for (int i = target->keyListeners_->size(); --i >= 0;) + { + keyWasUsed = ((KeyListener*) target->keyListeners_->getUnchecked(i))->keyStateChanged (target); + + if (keyWasUsed || deletionChecker.hasBeenDeleted()) + return keyWasUsed; + + i = jmin (i, target->keyListeners_->size()); + } + } + + target = target->parentComponent_; + } + + return keyWasUsed; +} + +void ComponentPeer::handleModifierKeysChange() +{ + updateCurrentModifiers(); + + Component* target = Component::getComponentUnderMouse(); + + if (target == 0) + target = Component::getCurrentlyFocusedComponent(); + + if (target == 0) + target = component; + + if (target->isValidComponent()) + target->internalModifierKeysChanged(); +} + +//============================================================================== +void ComponentPeer::handleBroughtToFront() +{ + updateCurrentModifiers(); + + if (component != 0) + component->internalBroughtToFront(); +} + +void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) throw() +{ + constrainer = newConstrainer; +} + +void ComponentPeer::handleMovedOrResized() +{ + jassert (component->isValidComponent()); + updateCurrentModifiers(); + + const bool nowMinimised = isMinimised(); + + if (component->flags.hasHeavyweightPeerFlag && ! nowMinimised) + { + const ComponentDeletionWatcher deletionChecker (component); + + int realX, realY, realW, realH; + getBounds (realX, realY, realW, realH); + + const bool wasMoved = (component->getX() != realX || component->getY() != realY); + const bool wasResized = (component->getWidth() != realW || component->getHeight() != realH); + + if (wasMoved || wasResized) + { + component->bounds_.setBounds (realX, realY, realW, realH); + + if (wasResized) + component->repaint(); + + component->sendMovedResizedMessages (wasMoved, wasResized); + + if (deletionChecker.hasBeenDeleted()) + return; + } + } + + if (isWindowMinimised != nowMinimised) + { + isWindowMinimised = nowMinimised; + component->minimisationStateChanged (nowMinimised); + component->sendVisibilityChangeMessage(); + } + + if (! isFullScreen()) + lastNonFullscreenBounds = component->getBounds(); +} + +void ComponentPeer::handleFocusGain() +{ + updateCurrentModifiers(); + + if (component->isParentOf (lastFocusedComponent)) + { + Component::currentlyFocusedComponent = lastFocusedComponent; + Desktop::getInstance().triggerFocusCallback(); + lastFocusedComponent->internalFocusGain (Component::focusChangedDirectly); + } + else + { + if (! component->isCurrentlyBlockedByAnotherModalComponent()) + { + component->grabKeyboardFocus(); + } + else + { + Component* const currentModalComp = Component::getCurrentlyModalComponent(); + + if (currentModalComp != 0) + currentModalComp->toFront (! currentModalComp->hasKeyboardFocus (true)); + } + } +} + +void ComponentPeer::handleFocusLoss() +{ + updateCurrentModifiers(); + + if (component->hasKeyboardFocus (true)) + { + lastFocusedComponent = Component::currentlyFocusedComponent; + + if (lastFocusedComponent != 0) + { + Component::currentlyFocusedComponent = 0; + Desktop::getInstance().triggerFocusCallback(); + lastFocusedComponent->internalFocusLoss (Component::focusChangedByMouseClick); + } + } +} + +Component* ComponentPeer::getLastFocusedSubcomponent() const throw() +{ + return (component->isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing()) + ? lastFocusedComponent + : component; +} + +void ComponentPeer::handleScreenSizeChange() +{ + updateCurrentModifiers(); + + component->parentSizeChanged(); + handleMovedOrResized(); +} + +void ComponentPeer::setNonFullScreenBounds (const Rectangle& newBounds) throw() +{ + lastNonFullscreenBounds = newBounds; +} + +const Rectangle& ComponentPeer::getNonFullScreenBounds() const throw() +{ + return lastNonFullscreenBounds; +} + +//============================================================================== +static FileDragAndDropTarget* findDragAndDropTarget (Component* c, + const StringArray& files, + FileDragAndDropTarget* const lastOne) +{ + while (c != 0) + { + FileDragAndDropTarget* const t = dynamic_cast (c); + + if (t != 0 && (t == lastOne || t->isInterestedInFileDrag (files))) + return t; + + c = c->getParentComponent(); + } + + return 0; +} + +void ComponentPeer::handleFileDragMove (const StringArray& files, int x, int y) +{ + updateCurrentModifiers(); + + FileDragAndDropTarget* lastTarget = 0; + + if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) + lastTarget = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); + + FileDragAndDropTarget* newTarget = 0; + + Component* const compUnderMouse = component->getComponentAt (x, y); + + if (compUnderMouse != lastDragAndDropCompUnderMouse) + { + lastDragAndDropCompUnderMouse = compUnderMouse; + newTarget = findDragAndDropTarget (compUnderMouse, files, lastTarget); + + if (newTarget != lastTarget) + { + if (lastTarget != 0) + lastTarget->fileDragExit (files); + + deleteAndZero (dragAndDropTargetComponent); + + if (newTarget != 0) + { + Component* const targetComp = dynamic_cast (newTarget); + int mx = x, my = y; + component->relativePositionToOtherComponent (targetComp, mx, my); + + dragAndDropTargetComponent = new ComponentDeletionWatcher (dynamic_cast (newTarget)); + newTarget->fileDragEnter (files, mx, my); + } + } + } + else + { + newTarget = lastTarget; + } + + if (newTarget != 0) + { + Component* const targetComp = dynamic_cast (newTarget); + component->relativePositionToOtherComponent (targetComp, x, y); + + newTarget->fileDragMove (files, x, y); + } +} + +void ComponentPeer::handleFileDragExit (const StringArray& files) +{ + handleFileDragMove (files, -1, -1); + + jassert (dragAndDropTargetComponent == 0); + lastDragAndDropCompUnderMouse = 0; +} + +void ComponentPeer::handleFileDragDrop (const StringArray& files, int x, int y) +{ + handleFileDragMove (files, x, y); + + if (dragAndDropTargetComponent != 0 && ! dragAndDropTargetComponent->hasBeenDeleted()) + { + FileDragAndDropTarget* const target = const_cast (dynamic_cast (dragAndDropTargetComponent->getComponent())); + + deleteAndZero (dragAndDropTargetComponent); + lastDragAndDropCompUnderMouse = 0; + + if (target != 0) + { + Component* const targetComp = dynamic_cast (target); + + if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) + { + targetComp->internalModalInputAttempt(); + + if (targetComp->isCurrentlyBlockedByAnotherModalComponent()) + return; + } + + component->relativePositionToOtherComponent (targetComp, x, y); + target->filesDropped (files, x, y); + } + } +} + +//============================================================================== +void ComponentPeer::handleUserClosingWindow() +{ + updateCurrentModifiers(); + + component->userTriedToCloseWindow(); +} + +//============================================================================== +void ComponentPeer::clearMaskedRegion() throw() +{ + maskedRegion.clear(); +} + +void ComponentPeer::addMaskedRegion (int x, int y, int w, int h) throw() +{ + maskedRegion.add (x, y, w, h); +} + + +END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/graphics/contexts/juce_Graphics.cpp b/src/juce_appframework/gui/graphics/contexts/juce_Graphics.cpp index fc9e06f85e..f5f9f34135 100644 --- a/src/juce_appframework/gui/graphics/contexts/juce_Graphics.cpp +++ b/src/juce_appframework/gui/graphics/contexts/juce_Graphics.cpp @@ -488,7 +488,8 @@ void Graphics::drawBevel (const int x, const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour, - const bool useGradient) const throw() + const bool useGradient, + const bool sharpEdgeOnOutside) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); @@ -500,7 +501,7 @@ void Graphics::drawBevel (const int x, for (int i = bevelThickness; --i >= 0;) { - const float op = useGradient ? ramp * (bevelThickness - i) + const float op = useGradient ? ramp * (sharpEdgeOnOutside ? bevelThickness - i : i) : oldOpacity; context->fillRectWithColour (x + i, y + i, width - i * 2, 1, topLeftColour.withMultipliedAlpha (op), false); diff --git a/src/juce_appframework/gui/graphics/contexts/juce_Graphics.h b/src/juce_appframework/gui/graphics/contexts/juce_Graphics.h index a962116893..69395f1d8c 100644 --- a/src/juce_appframework/gui/graphics/contexts/juce_Graphics.h +++ b/src/juce_appframework/gui/graphics/contexts/juce_Graphics.h @@ -354,6 +354,11 @@ public: The top-left colour is used for the top- and left-hand edges of the bevel; the bottom-right colour is used for the bottom- and right-hand edges. + + If useGradient is true, then the bevel fades out to make it look more curved + and less angular. If sharpEdgeOnOutside is true, the outside of the bevel is + sharp, and it fades towards the centre; if sharpEdgeOnOutside is false, then + the centre edges are sharp and it fades towards the outside. */ void drawBevel (const int x, const int y, @@ -362,7 +367,8 @@ public: const int bevelThickness, const Colour& topLeftColour = Colours::white, const Colour& bottomRightColour = Colours::black, - const bool useGradient = true) const throw(); + const bool useGradient = true, + const bool sharpEdgeOnOutside = true) const throw(); /** Draws a pixel using the current colour or brush. */ diff --git a/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp b/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp index d2d7bbae42..32b12dffeb 100644 --- a/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp +++ b/src/juce_appframework/gui/graphics/imaging/image_file_formats/juce_JPEGLoader.cpp @@ -245,14 +245,14 @@ Image* juce_loadJPEGImageFromStream (InputStream& in) throw() } jpeg_finish_decompress (&jpegDecompStruct); + + in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); } jpeg_destroy_decompress (&jpegDecompStruct); } catch (...) {} - - in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); } return image; diff --git a/src/juce_core/containers/juce_BitArray.cpp b/src/juce_core/containers/juce_BitArray.cpp index 38b9d19007..8029e2f979 100644 --- a/src/juce_core/containers/juce_BitArray.cpp +++ b/src/juce_core/containers/juce_BitArray.cpp @@ -1,955 +1,957 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - - -#include "juce_BitArray.h" -#include "juce_MemoryBlock.h" -#include "../basics/juce_Random.h" - - -//============================================================================== -BitArray::BitArray() throw() - : numValues (4), - highestBit (-1), - negative (false) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); -} - -BitArray::BitArray (const int value) throw() - : numValues (4), - highestBit (31), - negative (value < 0) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - values[0] = abs (value); - highestBit = getHighestBit(); -} - -BitArray::BitArray (int64 value) throw() - : numValues (4), - highestBit (63), - negative (value < 0) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - - if (value < 0) - value = -value; - - values[0] = (unsigned int) value; - values[1] = (unsigned int) (value >> 32); - highestBit = getHighestBit(); -} - -BitArray::BitArray (const unsigned int value) throw() - : numValues (4), - highestBit (31), - negative (false) -{ - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - values[0] = value; - highestBit = getHighestBit(); -} - -BitArray::BitArray (const BitArray& other) throw() - : numValues (jmax (4, (other.highestBit >> 5) + 1)), - highestBit (other.getHighestBit()), - negative (other.negative) -{ - const int bytes = sizeof (unsigned int) * (numValues + 1); - values = (unsigned int*) juce_malloc (bytes); - memcpy (values, other.values, bytes); -} - -BitArray::~BitArray() throw() -{ - juce_free (values); -} - -const BitArray& BitArray::operator= (const BitArray& other) throw() -{ - if (this != &other) - { - juce_free (values); - - highestBit = other.getHighestBit(); - numValues = jmax (4, (highestBit >> 5) + 1); - negative = other.negative; - const int memSize = sizeof (unsigned int) * (numValues + 1); - values = (unsigned int*)juce_malloc (memSize); - memcpy (values, other.values, memSize); - } - - return *this; -} - -// result == 0 = the same -// result < 0 = this number is smaller -// result > 0 = this number is bigger -int BitArray::compare (const BitArray& other) const throw() -{ - if (isNegative() == other.isNegative()) - { - const int absComp = compareAbsolute (other); - return isNegative() ? -absComp : absComp; - } - else - { - return isNegative() ? -1 : 1; - } -} - -int BitArray::compareAbsolute (const BitArray& other) const throw() -{ - const int h1 = getHighestBit(); - const int h2 = other.getHighestBit(); - - if (h1 > h2) - return 1; - else if (h1 < h2) - return -1; - - for (int i = (h1 >> 5) + 1; --i >= 0;) - if (values[i] != other.values[i]) - return (values[i] > other.values[i]) ? 1 : -1; - - return 0; -} - -bool BitArray::operator== (const BitArray& other) const throw() -{ - return compare (other) == 0; -} - -bool BitArray::operator!= (const BitArray& other) const throw() -{ - return compare (other) != 0; -} - -bool BitArray::operator[] (const int bit) const throw() -{ - return bit >= 0 && bit <= highestBit - && ((values [bit >> 5] & (1 << (bit & 31))) != 0); -} - -bool BitArray::isEmpty() const throw() -{ - return getHighestBit() < 0; -} - -void BitArray::clear() throw() -{ - if (numValues > 16) - { - juce_free (values); - numValues = 4; - values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); - } - else - { - zeromem (values, sizeof (unsigned int) * (numValues + 1)); - } - - highestBit = -1; - negative = false; -} - -void BitArray::setBit (const int bit) throw() -{ - if (bit >= 0) - { - if (bit > highestBit) - { - ensureSize (bit >> 5); - highestBit = bit; - } - - values [bit >> 5] |= (1 << (bit & 31)); - } -} - -void BitArray::setBit (const int bit, - const bool shouldBeSet) throw() -{ - if (shouldBeSet) - setBit (bit); - else - clearBit (bit); -} - -void BitArray::clearBit (const int bit) throw() -{ - if (bit >= 0 && bit <= highestBit) - values [bit >> 5] &= ~(1 << (bit & 31)); -} - -void BitArray::setRange (int startBit, - int numBits, - const bool shouldBeSet) throw() -{ - while (--numBits >= 0) - setBit (startBit++, shouldBeSet); -} - -void BitArray::insertBit (const int bit, - const bool shouldBeSet) throw() -{ - if (bit >= 0) - shiftBits (1, bit); - - setBit (bit, shouldBeSet); -} - -//============================================================================== -void BitArray::andWith (const BitArray& other) throw() -{ - // this operation will only work with the absolute values - jassert (isNegative() == other.isNegative()); - - int n = numValues; - - while (n > other.numValues) - values[--n] = 0; - - while (--n >= 0) - values[n] &= other.values[n]; - - if (other.highestBit < highestBit) - highestBit = other.highestBit; - - highestBit = getHighestBit(); -} - -void BitArray::orWith (const BitArray& other) throw() -{ - if (other.highestBit < 0) - return; - - // this operation will only work with the absolute values - jassert (isNegative() == other.isNegative()); - - ensureSize (other.highestBit >> 5); - - int n = (other.highestBit >> 5) + 1; - - while (--n >= 0) - values[n] |= other.values[n]; - - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - highestBit = getHighestBit(); -} - -void BitArray::xorWith (const BitArray& other) throw() -{ - if (other.highestBit < 0) - return; - - // this operation will only work with the absolute values - jassert (isNegative() == other.isNegative()); - - ensureSize (other.highestBit >> 5); - - int n = (other.highestBit >> 5) + 1; - - while (--n >= 0) - values[n] ^= other.values[n]; - - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - highestBit = getHighestBit(); -} - -//============================================================================== -void BitArray::add (const BitArray& other) throw() -{ - if (other.isNegative()) - { - BitArray o (other); - o.negate(); - subtract (o); - return; - } - - if (isNegative()) - { - if (compareAbsolute (other) < 0) - { - BitArray temp (*this); - temp.negate(); - *this = other; - subtract (temp); - } - else - { - negate(); - subtract (other); - negate(); - } - - return; - } - - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - ++highestBit; - - const int numInts = (highestBit >> 5) + 1; - ensureSize (numInts); - - int64 remainder = 0; - - for (int i = 0; i <= numInts; ++i) - { - if (i < numValues) - remainder += values[i]; - - if (i < other.numValues) - remainder += other.values[i]; - - values[i] = (unsigned int) remainder; - remainder >>= 32; - } - - jassert (remainder == 0); - highestBit = getHighestBit(); -} - -void BitArray::subtract (const BitArray& other) throw() -{ - if (other.isNegative()) - { - BitArray o (other); - o.negate(); - add (o); - return; - } - - if (! isNegative()) - { - if (compareAbsolute (other) < 0) - { - BitArray temp (*this); - *this = other; - subtract (temp); - negate(); - return; - } - } - else - { - negate(); - add (other); - negate(); - return; - } - - const int numInts = (highestBit >> 5) + 1; - const int maxOtherInts = (other.highestBit >> 5) + 1; - int64 amountToSubtract = 0; - - for (int i = 0; i <= numInts; ++i) - { - if (i <= maxOtherInts) - amountToSubtract += (int64)other.values[i]; - - if (values[i] >= amountToSubtract) - { - values[i] = (unsigned int) (values[i] - amountToSubtract); - amountToSubtract = 0; - } - else - { - const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; - values[i] = (unsigned int) n; - amountToSubtract = 1; - } - } -} - -void BitArray::multiplyBy (const BitArray& other) throw() -{ - BitArray total; - highestBit = getHighestBit(); - const bool wasNegative = isNegative(); - setNegative (false); - - for (int i = 0; i <= highestBit; ++i) - { - if (operator[](i)) - { - BitArray n (other); - n.setNegative (false); - n.shiftBits (i); - total.add (n); - } - } - - *this = total; - negative = wasNegative ^ other.isNegative(); -} - -void BitArray::divideBy (const BitArray& divisor, BitArray& remainder) throw() -{ - jassert (this != &remainder); // (can't handle passing itself in to get the remainder) - - const int divHB = divisor.getHighestBit(); - const int ourHB = getHighestBit(); - - if (divHB < 0 || ourHB < 0) - { - // division by zero - remainder.clear(); - clear(); - } - else - { - remainder = *this; - remainder.setNegative (false); - const bool wasNegative = isNegative(); - clear(); - - BitArray temp (divisor); - temp.setNegative (false); - - int leftShift = ourHB - divHB; - temp.shiftBits (leftShift); - - while (leftShift >= 0) - { - if (remainder.compareAbsolute (temp) >= 0) - { - remainder.subtract (temp); - setBit (leftShift); - } - - if (--leftShift >= 0) - temp.shiftBits (-1); - } - - negative = wasNegative ^ divisor.isNegative(); - remainder.setNegative (wasNegative); - } -} - -void BitArray::modulo (const BitArray& divisor) throw() -{ - BitArray remainder; - divideBy (divisor, remainder); - *this = remainder; -} - -static const BitArray simpleGCD (BitArray* m, BitArray* n) throw() -{ - while (! m->isEmpty()) - { - if (n->compareAbsolute (*m) > 0) - swapVariables (m, n); - - m->subtract (*n); - } - - return *n; -} - -const BitArray BitArray::findGreatestCommonDivisor (BitArray n) const throw() -{ - BitArray m (*this); - - while (! n.isEmpty()) - { - if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) - return simpleGCD (&m, &n); - - BitArray temp1 (m), temp2; - temp1.divideBy (n, temp2); - - m = n; - n = temp2; - } - - return m; -} - -void BitArray::exponentModulo (const BitArray& exponent, - const BitArray& modulus) throw() -{ - BitArray exp (exponent); - exp.modulo (modulus); - - BitArray value (*this); - value.modulo (modulus); - - clear(); - setBit (0); - - while (! exp.isEmpty()) - { - if (exp [0]) - { - multiplyBy (value); - this->modulo (modulus); - } - - value.multiplyBy (value); - value.modulo (modulus); - - exp.shiftBits (-1); - } -} - -void BitArray::inverseModulo (const BitArray& modulus) throw() -{ - const BitArray one (1); - - if (modulus == one || modulus.isNegative()) - { - clear(); - return; - } - - if (isNegative() || compareAbsolute (modulus) >= 0) - this->modulo (modulus); - - if (*this == one) - return; - - if (! (*this)[0]) - { - // not invertible - clear(); - return; - } - - BitArray a1 (modulus); - BitArray a2 (*this); - BitArray b1 (modulus); - BitArray b2 (1); - - while (a2 != one) - { - BitArray temp1, temp2, multiplier (a1); - multiplier.divideBy (a2, temp1); - - temp1 = a2; - temp1.multiplyBy (multiplier); - temp2 = a1; - temp2.subtract (temp1); - a1 = a2; - a2 = temp2; - - temp1 = b2; - temp1.multiplyBy (multiplier); - temp2 = b1; - temp2.subtract (temp1); - b1 = b2; - b2 = temp2; - } - - while (b2.isNegative()) - b2.add (modulus); - - b2.modulo (modulus); - *this = b2; -} - -//============================================================================== -void BitArray::shiftBits (int bits, const int startBit) throw() -{ - if (highestBit < 0) - return; - - if (startBit > 0) - { - if (bits < 0) - { - // right shift - for (int i = startBit; i <= highestBit; ++i) - setBit (i, operator[] (i - bits)); - - highestBit = getHighestBit(); - } - else if (bits > 0) - { - // left shift - for (int i = highestBit + 1; --i >= startBit;) - setBit (i + bits, operator[] (i)); - - while (--bits >= 0) - clearBit (bits + startBit); - } - } - else - { - if (bits < 0) - { - // right shift - bits = -bits; - - if (bits > highestBit) - { - clear(); - } - else - { - const int wordsToMove = bits >> 5; - int top = 1 + (highestBit >> 5) - wordsToMove; - highestBit -= bits; - - if (wordsToMove > 0) - { - int i; - for (i = 0; i < top; ++i) - values [i] = values [i + wordsToMove]; - - for (i = 0; i < wordsToMove; ++i) - values [top + i] = 0; - - bits &= 31; - } - - if (bits != 0) - { - const int invBits = 32 - bits; - - --top; - for (int i = 0; i < top; ++i) - values[i] = (values[i] >> bits) | (values [i + 1] << invBits); - - values[top] = (values[top] >> bits); - } - - highestBit = getHighestBit(); - } - } - else if (bits > 0) - { - // left shift - ensureSize (((highestBit + bits) >> 5) + 1); - - const int wordsToMove = bits >> 5; - int top = 1 + (highestBit >> 5); - highestBit += bits; - - if (wordsToMove > 0) - { - int i; - for (i = top; --i >= 0;) - values [i + wordsToMove] = values [i]; - - for (i = 0; i < wordsToMove; ++i) - values [i] = 0; - - bits &= 31; - } - - if (bits != 0) - { - const int invBits = 32 - bits; - - for (int i = top + 1 + wordsToMove; --i > wordsToMove;) - values[i] = (values[i] << bits) | (values [i - 1] >> invBits); - - values [wordsToMove] = values [wordsToMove] << bits; - } - - highestBit = getHighestBit(); - } - } -} - -const BitArray BitArray::getBitRange (int startBit, int numBits) const throw() -{ - BitArray r; - numBits = jmin (numBits, getHighestBit() + 1 - startBit); - r.ensureSize (numBits >> 5); - r.highestBit = numBits; - - int i = 0; - while (numBits > 0) - { - r.values[i++] = getBitRangeAsInt (startBit, jmin (32, numBits)); - numBits -= 32; - startBit += 32; - } - - r.highestBit = r.getHighestBit(); - - return r; -} - -int BitArray::getBitRangeAsInt (const int startBit, int numBits) const throw() -{ - if (numBits > 32) - { - jassertfalse // use getBitRange() if you need more than 32 bits.. - numBits = 32; - } - - numBits = jmin (numBits, highestBit + 1 - startBit); - - if (numBits <= 0) - return 0; - - const int pos = startBit >> 5; - const int offset = startBit & 31; - const int endSpace = 32 - numBits; - - uint32 n = ((uint32) values [pos]) >> offset; - - if (offset > endSpace) - n |= ((uint32) values [pos + 1]) << (32 - offset); - - return (int) (n & (((uint32) 0xffffffff) >> endSpace)); -} - -void BitArray::setBitRangeAsInt (const int startBit, int numBits, unsigned int valueToSet) throw() -{ - if (numBits > 32) - { - jassertfalse - numBits = 32; - } - - for (int i = 0; i < numBits; ++i) - { - setBit (startBit + i, (valueToSet & 1) != 0); - valueToSet >>= 1; - } -} - -//============================================================================== -bool BitArray::isNegative() const throw() -{ - return negative && ! isEmpty(); -} - -void BitArray::setNegative (const bool neg) throw() -{ - negative = neg; -} - -void BitArray::negate() throw() -{ - negative = (! negative) && ! isEmpty(); -} - -int BitArray::countNumberOfSetBits() const throw() -{ - int total = 0; - - for (int i = (highestBit >> 5) + 1; --i >= 0;) - { - unsigned int n = values[i]; - - if (n == 0xffffffff) - { - total += 32; - } - else - { - while (n != 0) - { - total += (n & 1); - n >>= 1; - } - } - } - - return total; -} - -int BitArray::getHighestBit() const throw() -{ - for (int i = highestBit + 1; --i >= 0;) - if ((values [i >> 5] & (1 << (i & 31))) != 0) - return i; - - return -1; -} - -int BitArray::findNextSetBit (int i) const throw() -{ - for (; i <= highestBit; ++i) - if ((values [i >> 5] & (1 << (i & 31))) != 0) - return i; - - return -1; -} - -int BitArray::findNextClearBit (int i) const throw() -{ - for (; i <= highestBit; ++i) - if ((values [i >> 5] & (1 << (i & 31))) == 0) - break; - - return i; -} - -void BitArray::ensureSize (const int numVals) throw() -{ - if (numVals + 2 >= numValues) - { - int oldSize = numValues; - numValues = ((numVals + 2) * 3) / 2; - values = (unsigned int*) juce_realloc (values, sizeof (unsigned int) * numValues + 4); - - while (oldSize < numValues) - values [oldSize++] = 0; - } -} - -//============================================================================== -const String BitArray::toString (const int base) const throw() -{ - String s; - BitArray v (*this); - - if (base == 2 || base == 8 || base == 16) - { - const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); - static const tchar* const hexDigits = T("0123456789abcdef"); - - for (;;) - { - const int remainder = v.getBitRangeAsInt (0, bits); - - v.shiftBits (-bits); - - if (remainder == 0 && v.isEmpty()) - break; - - s = String::charToString (hexDigits [remainder]) + s; - } - } - else if (base == 10) - { - const BitArray ten (10); - BitArray remainder; - - for (;;) - { - v.divideBy (ten, remainder); - - if (remainder.isEmpty() && v.isEmpty()) - break; - - s = String (remainder.getBitRangeAsInt (0, 8)) + s; - } - } - else - { - jassertfalse // can't do the specified base - return String::empty; - } - - if (s.isEmpty()) - return T("0"); - - return isNegative() ? T("-") + s : s; -} - -void BitArray::parseString (const String& text, - const int base) throw() -{ - clear(); - const tchar* t = (const tchar*) text; - - if (base == 2 || base == 8 || base == 16) - { - const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); - - for (;;) - { - const tchar c = *t++; - const int digit = CharacterFunctions::getHexDigitValue (c); - - if (((unsigned int) digit) < (unsigned int) base) - { - shiftBits (bits); - add (digit); - } - else if (c == 0) - { - break; - } - } - } - else if (base == 10) - { - const BitArray ten ((unsigned int) 10); - - for (;;) - { - const tchar c = *t++; - - if (c >= T('0') && c <= T('9')) - { - multiplyBy (ten); - add ((int) (c - T('0'))); - } - else if (c == 0) - { - break; - } - } - } - - setNegative (text.trimStart().startsWithChar (T('-'))); -} - -const MemoryBlock BitArray::toMemoryBlock() const throw() -{ - const int numBytes = (getHighestBit() + 8) >> 3; - MemoryBlock mb (numBytes); - - for (int i = 0; i < numBytes; ++i) - mb[i] = (uint8) getBitRangeAsInt (i << 3, 8); - - return mb; -} - -void BitArray::loadFromMemoryBlock (const MemoryBlock& data) throw() -{ - clear(); - - for (int i = data.getSize(); --i >= 0;) - this->setBitRangeAsInt (i << 3, 8, data [i]); -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + + +#include "juce_BitArray.h" +#include "juce_MemoryBlock.h" +#include "../basics/juce_Random.h" + + +//============================================================================== +BitArray::BitArray() throw() + : numValues (4), + highestBit (-1), + negative (false) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); +} + +BitArray::BitArray (const int value) throw() + : numValues (4), + highestBit (31), + negative (value < 0) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + values[0] = abs (value); + highestBit = getHighestBit(); +} + +BitArray::BitArray (int64 value) throw() + : numValues (4), + highestBit (63), + negative (value < 0) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + + if (value < 0) + value = -value; + + values[0] = (unsigned int) value; + values[1] = (unsigned int) (value >> 32); + highestBit = getHighestBit(); +} + +BitArray::BitArray (const unsigned int value) throw() + : numValues (4), + highestBit (31), + negative (false) +{ + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + values[0] = value; + highestBit = getHighestBit(); +} + +BitArray::BitArray (const BitArray& other) throw() + : numValues (jmax (4, (other.highestBit >> 5) + 1)), + highestBit (other.getHighestBit()), + negative (other.negative) +{ + const int bytes = sizeof (unsigned int) * (numValues + 1); + values = (unsigned int*) juce_malloc (bytes); + memcpy (values, other.values, bytes); +} + +BitArray::~BitArray() throw() +{ + juce_free (values); +} + +const BitArray& BitArray::operator= (const BitArray& other) throw() +{ + if (this != &other) + { + juce_free (values); + + highestBit = other.getHighestBit(); + numValues = jmax (4, (highestBit >> 5) + 1); + negative = other.negative; + const int memSize = sizeof (unsigned int) * (numValues + 1); + values = (unsigned int*)juce_malloc (memSize); + memcpy (values, other.values, memSize); + } + + return *this; +} + +// result == 0 = the same +// result < 0 = this number is smaller +// result > 0 = this number is bigger +int BitArray::compare (const BitArray& other) const throw() +{ + if (isNegative() == other.isNegative()) + { + const int absComp = compareAbsolute (other); + return isNegative() ? -absComp : absComp; + } + else + { + return isNegative() ? -1 : 1; + } +} + +int BitArray::compareAbsolute (const BitArray& other) const throw() +{ + const int h1 = getHighestBit(); + const int h2 = other.getHighestBit(); + + if (h1 > h2) + return 1; + else if (h1 < h2) + return -1; + + for (int i = (h1 >> 5) + 1; --i >= 0;) + if (values[i] != other.values[i]) + return (values[i] > other.values[i]) ? 1 : -1; + + return 0; +} + +bool BitArray::operator== (const BitArray& other) const throw() +{ + return compare (other) == 0; +} + +bool BitArray::operator!= (const BitArray& other) const throw() +{ + return compare (other) != 0; +} + +bool BitArray::operator[] (const int bit) const throw() +{ + return bit >= 0 && bit <= highestBit + && ((values [bit >> 5] & (1 << (bit & 31))) != 0); +} + +bool BitArray::isEmpty() const throw() +{ + return getHighestBit() < 0; +} + +void BitArray::clear() throw() +{ + if (numValues > 16) + { + juce_free (values); + numValues = 4; + values = (unsigned int*) juce_calloc (sizeof (unsigned int) * (numValues + 1)); + } + else + { + zeromem (values, sizeof (unsigned int) * (numValues + 1)); + } + + highestBit = -1; + negative = false; +} + +void BitArray::setBit (const int bit) throw() +{ + if (bit >= 0) + { + if (bit > highestBit) + { + ensureSize (bit >> 5); + highestBit = bit; + } + + values [bit >> 5] |= (1 << (bit & 31)); + } +} + +void BitArray::setBit (const int bit, + const bool shouldBeSet) throw() +{ + if (shouldBeSet) + setBit (bit); + else + clearBit (bit); +} + +void BitArray::clearBit (const int bit) throw() +{ + if (bit >= 0 && bit <= highestBit) + values [bit >> 5] &= ~(1 << (bit & 31)); +} + +void BitArray::setRange (int startBit, + int numBits, + const bool shouldBeSet) throw() +{ + while (--numBits >= 0) + setBit (startBit++, shouldBeSet); +} + +void BitArray::insertBit (const int bit, + const bool shouldBeSet) throw() +{ + if (bit >= 0) + shiftBits (1, bit); + + setBit (bit, shouldBeSet); +} + +//============================================================================== +void BitArray::andWith (const BitArray& other) throw() +{ + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + int n = numValues; + + while (n > other.numValues) + values[--n] = 0; + + while (--n >= 0) + values[n] &= other.values[n]; + + if (other.highestBit < highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); +} + +void BitArray::orWith (const BitArray& other) throw() +{ + if (other.highestBit < 0) + return; + + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + ensureSize (other.highestBit >> 5); + + int n = (other.highestBit >> 5) + 1; + + while (--n >= 0) + values[n] |= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); +} + +void BitArray::xorWith (const BitArray& other) throw() +{ + if (other.highestBit < 0) + return; + + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + ensureSize (other.highestBit >> 5); + + int n = (other.highestBit >> 5) + 1; + + while (--n >= 0) + values[n] ^= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); +} + +//============================================================================== +void BitArray::add (const BitArray& other) throw() +{ + if (other.isNegative()) + { + BitArray o (other); + o.negate(); + subtract (o); + return; + } + + if (isNegative()) + { + if (compareAbsolute (other) < 0) + { + BitArray temp (*this); + temp.negate(); + *this = other; + subtract (temp); + } + else + { + negate(); + subtract (other); + negate(); + } + + return; + } + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + ++highestBit; + + const int numInts = (highestBit >> 5) + 1; + ensureSize (numInts); + + int64 remainder = 0; + + for (int i = 0; i <= numInts; ++i) + { + if (i < numValues) + remainder += values[i]; + + if (i < other.numValues) + remainder += other.values[i]; + + values[i] = (unsigned int) remainder; + remainder >>= 32; + } + + jassert (remainder == 0); + highestBit = getHighestBit(); +} + +void BitArray::subtract (const BitArray& other) throw() +{ + if (other.isNegative()) + { + BitArray o (other); + o.negate(); + add (o); + return; + } + + if (! isNegative()) + { + if (compareAbsolute (other) < 0) + { + BitArray temp (*this); + *this = other; + subtract (temp); + negate(); + return; + } + } + else + { + negate(); + add (other); + negate(); + return; + } + + const int numInts = (highestBit >> 5) + 1; + const int maxOtherInts = (other.highestBit >> 5) + 1; + int64 amountToSubtract = 0; + + for (int i = 0; i <= numInts; ++i) + { + if (i <= maxOtherInts) + amountToSubtract += (int64)other.values[i]; + + if (values[i] >= amountToSubtract) + { + values[i] = (unsigned int) (values[i] - amountToSubtract); + amountToSubtract = 0; + } + else + { + const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; + values[i] = (unsigned int) n; + amountToSubtract = 1; + } + } +} + +void BitArray::multiplyBy (const BitArray& other) throw() +{ + BitArray total; + highestBit = getHighestBit(); + const bool wasNegative = isNegative(); + setNegative (false); + + for (int i = 0; i <= highestBit; ++i) + { + if (operator[](i)) + { + BitArray n (other); + n.setNegative (false); + n.shiftBits (i); + total.add (n); + } + } + + *this = total; + negative = wasNegative ^ other.isNegative(); +} + +void BitArray::divideBy (const BitArray& divisor, BitArray& remainder) throw() +{ + jassert (this != &remainder); // (can't handle passing itself in to get the remainder) + + const int divHB = divisor.getHighestBit(); + const int ourHB = getHighestBit(); + + if (divHB < 0 || ourHB < 0) + { + // division by zero + remainder.clear(); + clear(); + } + else + { + remainder = *this; + remainder.setNegative (false); + const bool wasNegative = isNegative(); + clear(); + + BitArray temp (divisor); + temp.setNegative (false); + + int leftShift = ourHB - divHB; + temp.shiftBits (leftShift); + + while (leftShift >= 0) + { + if (remainder.compareAbsolute (temp) >= 0) + { + remainder.subtract (temp); + setBit (leftShift); + } + + if (--leftShift >= 0) + temp.shiftBits (-1); + } + + negative = wasNegative ^ divisor.isNegative(); + remainder.setNegative (wasNegative); + } +} + +void BitArray::modulo (const BitArray& divisor) throw() +{ + BitArray remainder; + divideBy (divisor, remainder); + *this = remainder; +} + +static const BitArray simpleGCD (BitArray* m, BitArray* n) throw() +{ + while (! m->isEmpty()) + { + if (n->compareAbsolute (*m) > 0) + swapVariables (m, n); + + m->subtract (*n); + } + + return *n; +} + +const BitArray BitArray::findGreatestCommonDivisor (BitArray n) const throw() +{ + BitArray m (*this); + + while (! n.isEmpty()) + { + if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) + return simpleGCD (&m, &n); + + BitArray temp1 (m), temp2; + temp1.divideBy (n, temp2); + + m = n; + n = temp2; + } + + return m; +} + +void BitArray::exponentModulo (const BitArray& exponent, + const BitArray& modulus) throw() +{ + BitArray exp (exponent); + exp.modulo (modulus); + + BitArray value (*this); + value.modulo (modulus); + + clear(); + setBit (0); + + while (! exp.isEmpty()) + { + if (exp [0]) + { + multiplyBy (value); + this->modulo (modulus); + } + + value.multiplyBy (value); + value.modulo (modulus); + + exp.shiftBits (-1); + } +} + +void BitArray::inverseModulo (const BitArray& modulus) throw() +{ + const BitArray one (1); + + if (modulus == one || modulus.isNegative()) + { + clear(); + return; + } + + if (isNegative() || compareAbsolute (modulus) >= 0) + this->modulo (modulus); + + if (*this == one) + return; + + if (! (*this)[0]) + { + // not invertible + clear(); + return; + } + + BitArray a1 (modulus); + BitArray a2 (*this); + BitArray b1 (modulus); + BitArray b2 (1); + + while (a2 != one) + { + BitArray temp1, temp2, multiplier (a1); + multiplier.divideBy (a2, temp1); + + temp1 = a2; + temp1.multiplyBy (multiplier); + temp2 = a1; + temp2.subtract (temp1); + a1 = a2; + a2 = temp2; + + temp1 = b2; + temp1.multiplyBy (multiplier); + temp2 = b1; + temp2.subtract (temp1); + b1 = b2; + b2 = temp2; + } + + while (b2.isNegative()) + b2.add (modulus); + + b2.modulo (modulus); + *this = b2; +} + +//============================================================================== +void BitArray::shiftBits (int bits, const int startBit) throw() +{ + if (highestBit < 0) + return; + + if (startBit > 0) + { + if (bits < 0) + { + // right shift + for (int i = startBit; i <= highestBit; ++i) + setBit (i, operator[] (i - bits)); + + highestBit = getHighestBit(); + } + else if (bits > 0) + { + // left shift + for (int i = highestBit + 1; --i >= startBit;) + setBit (i + bits, operator[] (i)); + + while (--bits >= 0) + clearBit (bits + startBit); + } + } + else + { + if (bits < 0) + { + // right shift + bits = -bits; + + if (bits > highestBit) + { + clear(); + } + else + { + const int wordsToMove = bits >> 5; + int top = 1 + (highestBit >> 5) - wordsToMove; + highestBit -= bits; + + if (wordsToMove > 0) + { + int i; + for (i = 0; i < top; ++i) + values [i] = values [i + wordsToMove]; + + for (i = 0; i < wordsToMove; ++i) + values [top + i] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + --top; + for (int i = 0; i < top; ++i) + values[i] = (values[i] >> bits) | (values [i + 1] << invBits); + + values[top] = (values[top] >> bits); + } + + highestBit = getHighestBit(); + } + } + else if (bits > 0) + { + // left shift + ensureSize (((highestBit + bits) >> 5) + 1); + + const int wordsToMove = bits >> 5; + int top = 1 + (highestBit >> 5); + highestBit += bits; + + if (wordsToMove > 0) + { + int i; + for (i = top; --i >= 0;) + values [i + wordsToMove] = values [i]; + + for (i = 0; i < wordsToMove; ++i) + values [i] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + for (int i = top + 1 + wordsToMove; --i > wordsToMove;) + values[i] = (values[i] << bits) | (values [i - 1] >> invBits); + + values [wordsToMove] = values [wordsToMove] << bits; + } + + highestBit = getHighestBit(); + } + } +} + +const BitArray BitArray::getBitRange (int startBit, int numBits) const throw() +{ + BitArray r; + numBits = jmin (numBits, getHighestBit() + 1 - startBit); + r.ensureSize (numBits >> 5); + r.highestBit = numBits; + + int i = 0; + while (numBits > 0) + { + r.values[i++] = getBitRangeAsInt (startBit, jmin (32, numBits)); + numBits -= 32; + startBit += 32; + } + + r.highestBit = r.getHighestBit(); + + return r; +} + +int BitArray::getBitRangeAsInt (const int startBit, int numBits) const throw() +{ + if (numBits > 32) + { + jassertfalse // use getBitRange() if you need more than 32 bits.. + numBits = 32; + } + + numBits = jmin (numBits, highestBit + 1 - startBit); + + if (numBits <= 0) + return 0; + + const int pos = startBit >> 5; + const int offset = startBit & 31; + const int endSpace = 32 - numBits; + + uint32 n = ((uint32) values [pos]) >> offset; + + if (offset > endSpace) + n |= ((uint32) values [pos + 1]) << (32 - offset); + + return (int) (n & (((uint32) 0xffffffff) >> endSpace)); +} + +void BitArray::setBitRangeAsInt (const int startBit, int numBits, unsigned int valueToSet) throw() +{ + if (numBits > 32) + { + jassertfalse + numBits = 32; + } + + for (int i = 0; i < numBits; ++i) + { + setBit (startBit + i, (valueToSet & 1) != 0); + valueToSet >>= 1; + } +} + +//============================================================================== +bool BitArray::isNegative() const throw() +{ + return negative && ! isEmpty(); +} + +void BitArray::setNegative (const bool neg) throw() +{ + negative = neg; +} + +void BitArray::negate() throw() +{ + negative = (! negative) && ! isEmpty(); +} + +int BitArray::countNumberOfSetBits() const throw() +{ + int total = 0; + + for (int i = (highestBit >> 5) + 1; --i >= 0;) + { + unsigned int n = values[i]; + + if (n == 0xffffffff) + { + total += 32; + } + else + { + while (n != 0) + { + total += (n & 1); + n >>= 1; + } + } + } + + return total; +} + +int BitArray::getHighestBit() const throw() +{ + for (int i = highestBit + 1; --i >= 0;) + if ((values [i >> 5] & (1 << (i & 31))) != 0) + return i; + + return -1; +} + +int BitArray::findNextSetBit (int i) const throw() +{ + for (; i <= highestBit; ++i) + if ((values [i >> 5] & (1 << (i & 31))) != 0) + return i; + + return -1; +} + +int BitArray::findNextClearBit (int i) const throw() +{ + for (; i <= highestBit; ++i) + if ((values [i >> 5] & (1 << (i & 31))) == 0) + break; + + return i; +} + +void BitArray::ensureSize (const int numVals) throw() +{ + if (numVals + 2 >= numValues) + { + int oldSize = numValues; + numValues = ((numVals + 2) * 3) / 2; + values = (unsigned int*) juce_realloc (values, sizeof (unsigned int) * numValues + 4); + + while (oldSize < numValues) + values [oldSize++] = 0; + } +} + +//============================================================================== +const String BitArray::toString (const int base, const int minimumNumCharacters) const throw() +{ + String s; + BitArray v (*this); + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + static const tchar* const hexDigits = T("0123456789abcdef"); + + for (;;) + { + const int remainder = v.getBitRangeAsInt (0, bits); + + v.shiftBits (-bits); + + if (remainder == 0 && v.isEmpty()) + break; + + s = String::charToString (hexDigits [remainder]) + s; + } + } + else if (base == 10) + { + const BitArray ten (10); + BitArray remainder; + + for (;;) + { + v.divideBy (ten, remainder); + + if (remainder.isEmpty() && v.isEmpty()) + break; + + s = String (remainder.getBitRangeAsInt (0, 8)) + s; + } + } + else + { + jassertfalse // can't do the specified base + return String::empty; + } + + const int length = s.length(); + + if (length < minimumNumCharacters) + s = String::repeatedString (T("0"), minimumNumCharacters - length); + + return isNegative() ? T("-") + s : s; +} + +void BitArray::parseString (const String& text, + const int base) throw() +{ + clear(); + const tchar* t = (const tchar*) text; + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + + for (;;) + { + const tchar c = *t++; + const int digit = CharacterFunctions::getHexDigitValue (c); + + if (((unsigned int) digit) < (unsigned int) base) + { + shiftBits (bits); + add (digit); + } + else if (c == 0) + { + break; + } + } + } + else if (base == 10) + { + const BitArray ten ((unsigned int) 10); + + for (;;) + { + const tchar c = *t++; + + if (c >= T('0') && c <= T('9')) + { + multiplyBy (ten); + add ((int) (c - T('0'))); + } + else if (c == 0) + { + break; + } + } + } + + setNegative (text.trimStart().startsWithChar (T('-'))); +} + +const MemoryBlock BitArray::toMemoryBlock() const throw() +{ + const int numBytes = (getHighestBit() + 8) >> 3; + MemoryBlock mb (numBytes); + + for (int i = 0; i < numBytes; ++i) + mb[i] = (uint8) getBitRangeAsInt (i << 3, 8); + + return mb; +} + +void BitArray::loadFromMemoryBlock (const MemoryBlock& data) throw() +{ + clear(); + + for (int i = data.getSize(); --i >= 0;) + this->setBitRangeAsInt (i << 3, 8, data [i]); +} + +END_JUCE_NAMESPACE diff --git a/src/juce_core/containers/juce_BitArray.h b/src/juce_core/containers/juce_BitArray.h index 1a896bbf7c..d1a29fa497 100644 --- a/src/juce_core/containers/juce_BitArray.h +++ b/src/juce_core/containers/juce_BitArray.h @@ -301,8 +301,11 @@ public: /** Converts the array to a number string. Specify a base such as 2 (binary), 8 (octal), 10 (decimal), 16 (hex). + + If minuimumNumCharacters is greater than 0, the returned string will be + padded with leading zeros to reach at least that length. */ - const String toString (const int base) const throw(); + const String toString (const int base, const int minimumNumCharacters = 1) const throw(); /** Converts a number string to an array. diff --git a/src/juce_core/text/juce_StringPairArray.cpp b/src/juce_core/text/juce_StringPairArray.cpp index 95987ecb22..d0cabacb47 100644 --- a/src/juce_core/text/juce_StringPairArray.cpp +++ b/src/juce_core/text/juce_StringPairArray.cpp @@ -1,144 +1,158 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - - -#include "juce_StringPairArray.h" - - -//============================================================================== -StringPairArray::StringPairArray (const bool ignoreCase_) throw() - : ignoreCase (ignoreCase_) -{ -} - -StringPairArray::StringPairArray (const StringPairArray& other) throw() - : keys (other.keys), - values (other.values), - ignoreCase (other.ignoreCase) -{ -} - -StringPairArray::~StringPairArray() throw() -{ -} - -const StringPairArray& StringPairArray::operator= (const StringPairArray& other) throw() -{ - keys = other.keys; - values = other.values; - - return *this; -} - -bool StringPairArray::operator== (const StringPairArray& other) const throw() -{ - for (int i = keys.size(); --i >= 0;) - if (other [keys[i]] != values[i]) - return false; - - return true; -} - -bool StringPairArray::operator!= (const StringPairArray& other) const throw() -{ - return ! operator== (other); -} - -const String& StringPairArray::operator[] (const String& key) const throw() -{ - return values [keys.indexOf (key, ignoreCase)]; -} - -const String StringPairArray::getValue (const String& key, const String& defaultReturnValue) const -{ - const int i = keys.indexOf (key, ignoreCase); - - if (i >= 0) - return values[i]; - - return defaultReturnValue; -} - -void StringPairArray::set (const String& key, - const String& value) throw() -{ - const int i = keys.indexOf (key, ignoreCase); - - if (i >= 0) - { - values.set (i, value); - } - else - { - keys.add (key); - values.add (value); - } -} - -void StringPairArray::addArray (const StringPairArray& other) -{ - for (int i = 0; i < other.size(); ++i) - set (other.keys[i], other.values[i]); -} - -void StringPairArray::clear() throw() -{ - keys.clear(); - values.clear(); -} - -void StringPairArray::remove (const String& key) throw() -{ - remove (keys.indexOf (key, ignoreCase)); -} - -void StringPairArray::remove (const int index) throw() -{ - keys.remove (index); - values.remove (index); -} - -void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) throw() -{ - ignoreCase = shouldIgnoreCase; -} - -void StringPairArray::minimiseStorageOverheads() throw() -{ - keys.minimiseStorageOverheads(); - values.minimiseStorageOverheads(); -} - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + + +#include "juce_StringPairArray.h" + + +//============================================================================== +StringPairArray::StringPairArray (const bool ignoreCase_) throw() + : ignoreCase (ignoreCase_) +{ +} + +StringPairArray::StringPairArray (const StringPairArray& other) throw() + : keys (other.keys), + values (other.values), + ignoreCase (other.ignoreCase) +{ +} + +StringPairArray::~StringPairArray() throw() +{ +} + +const StringPairArray& StringPairArray::operator= (const StringPairArray& other) throw() +{ + keys = other.keys; + values = other.values; + + return *this; +} + +bool StringPairArray::operator== (const StringPairArray& other) const throw() +{ + for (int i = keys.size(); --i >= 0;) + if (other [keys[i]] != values[i]) + return false; + + return true; +} + +bool StringPairArray::operator!= (const StringPairArray& other) const throw() +{ + return ! operator== (other); +} + +const String& StringPairArray::operator[] (const String& key) const throw() +{ + return values [keys.indexOf (key, ignoreCase)]; +} + +const String StringPairArray::getValue (const String& key, const String& defaultReturnValue) const +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + return values[i]; + + return defaultReturnValue; +} + +void StringPairArray::set (const String& key, + const String& value) throw() +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + { + values.set (i, value); + } + else + { + keys.add (key); + values.add (value); + } +} + +void StringPairArray::addArray (const StringPairArray& other) +{ + for (int i = 0; i < other.size(); ++i) + set (other.keys[i], other.values[i]); +} + +void StringPairArray::clear() throw() +{ + keys.clear(); + values.clear(); +} + +void StringPairArray::remove (const String& key) throw() +{ + remove (keys.indexOf (key, ignoreCase)); +} + +void StringPairArray::remove (const int index) throw() +{ + keys.remove (index); + values.remove (index); +} + +void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) throw() +{ + ignoreCase = shouldIgnoreCase; +} + +const String StringPairArray::getDescription() const +{ + String s; + + for (int i = 0; i < keys.size(); ++i) + { + s << keys[i] << T(" = ") << values[i]; + if (i < keys.size()) + s << T(", "); + } + + return s; +} + +void StringPairArray::minimiseStorageOverheads() throw() +{ + keys.minimiseStorageOverheads(); + values.minimiseStorageOverheads(); +} + +END_JUCE_NAMESPACE diff --git a/src/juce_core/text/juce_StringPairArray.h b/src/juce_core/text/juce_StringPairArray.h index 88beca11ae..64482bdc8c 100644 --- a/src/juce_core/text/juce_StringPairArray.h +++ b/src/juce_core/text/juce_StringPairArray.h @@ -143,6 +143,13 @@ public: */ void setIgnoresCase (const bool shouldIgnoreCase) throw(); + //============================================================================== + /** Returns a descriptive string containing the items. + + This is handy for dumping the contents of an array. + */ + const String getDescription() const; + //============================================================================== /** Reduces the amount of storage being used by the array.